diff --git a/.github/workflows/cherry-pick.yml b/.github/workflows/cherry-pick.yml index 27fb025ae..03ca6df20 100644 --- a/.github/workflows/cherry-pick.yml +++ b/.github/workflows/cherry-pick.yml @@ -1,25 +1,27 @@ name: cherry-pick - on: issue_comment: types: [created] - permissions: contents: write pull-requests: write actions: write - jobs: cherry-pick: if: github.event.issue.pull_request && contains(github.event.comment.body, '/cherry-pick') runs-on: iac-arc steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.GH_APP_ID }} + private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} - name: Check team membership uses: tspascoal/get-user-teams-membership@v2 id: actorTeams with: username: ${{ github.actor }} - GITHUB_TOKEN: ${{ secrets.PAT }} + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} - name: Check if user belongs to team run: | if [[ "${{ steps.actorTeams.outputs.teams }}" == *"infra-release-managers"* ]]; then @@ -44,7 +46,7 @@ jobs: echo "Branch $branch" echo "branch=$branch" >> $GITHUB_OUTPUT - name: Authenticate GH CLI - run: gh auth login --with-token <<< ${{ secrets.PAT }} + run: gh auth login --with-token <<< ${{ steps.app-token.outputs.token }} - name: Check if PR is merged id: check @@ -63,7 +65,7 @@ jobs: exit 1 fi env: - GH_TOKEN: ${{ secrets.PAT }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} - name: Set Git identity run: | @@ -79,7 +81,7 @@ jobs: - name: Push changes uses: ad-m/github-push-action@master with: - github_token: ${{ secrets.PAT }} + github_token: ${{ steps.app-token.outputs.token }} branch: ${{ steps.input.outputs.branch }} env: GIT_COMMITTER_NAME: ${{ github.actor }} @@ -87,7 +89,7 @@ jobs: - name: Add comment to PR uses: actions/github-script@v3 with: - github-token: ${{secrets.PAT}} + github-token: ${{ steps.app-token.outputs.token }} script: | const issueComment = `Commit cherry-picked to ref [${{ steps.input.outputs.branch }}](https://github.com/${{ github.repository }}/tree/${{ steps.input.outputs.branch }}).` github.issues.createComment({ diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml new file mode 100644 index 000000000..13f06aab0 --- /dev/null +++ b/.github/workflows/preview.yml @@ -0,0 +1,101 @@ +name: Preview Module + +on: + workflow_dispatch: + inputs: + intent: + description: 'Intent for the module' + required: true + flavor: + description: 'Flavor for the module' + required: true + version: + description: 'Version of the module' + required: true + url: + description: 'Control Plane URL' + required: false + username: + description: 'Control Plane Username' + required: false + token: + description: 'Control Plane Token' + required: false + +jobs: + run-command: + runs-on: iac-arc + + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + ref: ${{ github.ref }} + + - name: Create control planes JSON file + id: set_control_planes + run: | + set -e + set -o pipefail + JSON_CONTENT='${{ vars.DEV_CONTROL_PLANES }}' + echo "$JSON_CONTENT" > control_planes.json + + - name: Get module directory path + id: get_module_path + run: | + set -e + MODULE_DIR_PATH=$(jq -r ".[] | select(.intent == \"${{ github.event.inputs.intent }}\" and .flavor == \"${{ github.event.inputs.flavor }}\" and .version == \"${{ github.event.inputs.version }}\") | .relativePath" index.json) + + # Check if MODULE_DIR_PATH is found + if [ -z "$MODULE_DIR_PATH" ]; then + echo "Error: Module directory path not found for the intent ${{ github.event.inputs.intent }} flavor ${{ github.event.inputs.flavor }} version ${{ github.event.inputs.version }}." + exit 1 + fi + + echo "MODULE_DIR_PATH=$MODULE_DIR_PATH" >> $GITHUB_ENV + echo "MODULE_DIR_PATH=$MODULE_DIR_PATH" + + - name: Update facets.yaml version + run: | + set -e + set -o pipefail + GITHUB_REF="${{ github.ref }}" + FACETS_YAML_PATH="$MODULE_DIR_PATH/facets.yaml" + # Check if facets.yaml exists + if [ ! -f "$FACETS_YAML_PATH" ]; then + echo "Error: facets.yaml not found in the module directory." + exit 1 + fi + + echo "Updating version in $FACETS_YAML_PATH" + # Append the GitHub ref to the version in facets.yaml + yq eval -i ".version += \"-${GITHUB_REF}\"" "$FACETS_YAML_PATH" + yq eval -i ".sample.version += \"-${GITHUB_REF}\"" "$FACETS_YAML_PATH" + + - name: Execute command + env: + ALL_SECRETS: ${{ toJson(secrets) }} + run: | + set -e + set -o pipefail + if [ -n "${{ github.event.inputs.url }}" ] && [ -n "${{ github.event.inputs.username }}" ] && [ -n "${{ github.event.inputs.token }}" ]; then + # Use provided URL, Username, and Token + URL="${{ github.event.inputs.url }}" + USERNAME="${{ github.event.inputs.username }}" + TOKEN="${{ github.event.inputs.token }}" + echo "Processing specified control plane with provided token." + curl -s https://facets-cloud.github.io/facets-schemas/scripts/module_register.sh | bash -s -- -c "$URL" -u "$USERNAME" -t "$TOKEN" -p "$MODULE_DIR_PATH" + else + echo "Processing all control planes" + for key in $(jq -r 'keys[]' control_planes.json); do + URL=$(jq -r ".$key.URL" control_planes.json) + USERNAME=$(jq -r ".$key.Username" control_planes.json) + TOKEN_REF_NAME=$(jq -r ".$key.TokenRef" control_planes.json) + TOKEN=$(echo "$ALL_SECRETS" | jq -r ".\"$TOKEN_REF_NAME\"") + echo "Registering to control plane $key" + curl -s https://facets-cloud.github.io/facets-schemas/scripts/module_register.sh | bash -s -- -c "$URL" -u "$USERNAME" -t "$TOKEN" -p "$MODULE_DIR_PATH" + done + + # Wait for all background processes and check for errors + wait + fi diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..ce405bade --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,44 @@ +name: Publish Modules to S3 + +on: + push: + branches: + - master + workflow_dispatch: + +concurrency: + group: publish-module-zips + +jobs: + publish: + runs-on: iac-arc + + steps: + - name: Set BRANCH_NAME variable + run: echo "BRANCH_NAME=${{ github.ref }}" >> $GITHUB_ENV + + - name: Checkout repository + uses: actions/checkout@v2 + with: + ref: ${{ env.BRANCH_NAME }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r .github/workflows/zip_modules/requirements.txt + + - name: Generate Module Zips + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + S3_BUCKET_NAME: ${{ vars.FACETS_MODULES_BUCKET_NAME }} + run: python .github/workflows/zip_modules/zip_modules.py + + - name: Post Success to a Slack channel + id: slack + uses: slackapi/slack-github-action@v1.24.0 + with: + channel-id: ${{ secrets.CHANNEL_ID }} + slack-message: 'Facets Modules have been successfully synced! ' + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_TOKEN }} diff --git a/.github/workflows/validate-bootstrapper.py b/.github/workflows/validate-bootstrapper.py new file mode 100644 index 000000000..36aba026c --- /dev/null +++ b/.github/workflows/validate-bootstrapper.py @@ -0,0 +1,49 @@ +import requests +import os +from requests.auth import HTTPBasicAuth + +# Configuration +url = "https://root.console.facets.cloud/cc-ui/v1/modules/bootstrap" + +def post(url, username, password, payload): + """Makes a POST API call and returns the response data or prints an error if the response is non-200.""" + try: + response = requests.post(url, auth=HTTPBasicAuth(username, password), json=payload) + + # Check if the response status code is not 200 + if response.status_code != 200: + print(f"Error: Received status code {response.status_code}.") + print("Response message:", response.text) # Print the error message from the response + return None + + return response.json() + except requests.exceptions.RequestException as e: + print(f"Error during API call: {e}") + return None + +def check_for_errors(data): + """Checks for errors in the data and prints the full response body if errors exist.""" + error_found = False + for key, value in data.items(): + if isinstance(value, list) and value: + print("Errors found in bootstrap modules:") + print(data) # Print the full response body + error_found = True + break # Break the loop after printing the response + + if not error_found: + print("No errors found.") + +if __name__ == "__main__": + # Take username and password from environment variables + USERNAME = os.getenv('ROOT_USER') # Get the username from environment variable + PASSWORD = os.getenv('ROOT_TOKEN') # Get the password from environment variable + + # Define the payload you want to send with the POST request + payload = { + # Add your key-value pairs here as needed for the API + } + + data = post(url, USERNAME, PASSWORD, payload) + if data: + check_for_errors(data) diff --git a/.github/workflows/validate-bootstrapper.yml b/.github/workflows/validate-bootstrapper.yml new file mode 100644 index 000000000..db2d8bb18 --- /dev/null +++ b/.github/workflows/validate-bootstrapper.yml @@ -0,0 +1,52 @@ +name: Validate Bootstrapper + +on: + workflow_dispatch: + workflow_run: + workflows: ["Publish Modules to S3"] + types: + - completed + +concurrency: + group: validate-bootstrapper + +jobs: + validate-bootstrapper: + runs-on: iac-arc + if: ${{ github.event.workflow_run.conclusion == 'success' }} + outputs: + script_output: ${{ steps.run.outputs.script_output }} + exit_status: ${{ steps.run.outputs.exit_status }} + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Run validate-bootstrapper script + id: run + env: + ROOT_USER: ${{ secrets.ROOT_USER }} + ROOT_TOKEN: ${{ secrets.ROOT_TOKEN }} + run: | + set +e + output=$(python .github/workflows/validate-bootstrapper.py) + exit_status=${PIPESTATUS[0]} + echo "script_output<> $GITHUB_OUTPUT + echo "$output" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + echo "exit_status=$exit_status" >> $GITHUB_OUTPUT + on-failure: + needs: validate-bootstrapper + runs-on: iac-arc + if: ${{ needs.validate-bootstrapper.outputs.exit_status != '0' }} + steps: + - name: Post Failure to a Slack channel + id: slack + uses: slackapi/slack-github-action@v1.24.0 + with: + channel-id: ${{ secrets.CHANNEL_ID }} + slack-message: ':alert-siren:Bootstrap Module Failed: ${{needs.validate-bootstrapper.outputs.script_output}}' + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_TOKEN }} + - name: Exiting with error + run: exit 1 \ No newline at end of file diff --git a/.github/workflows/zip_modules/requirements.txt b/.github/workflows/zip_modules/requirements.txt new file mode 100644 index 000000000..1c1ad0a42 --- /dev/null +++ b/.github/workflows/zip_modules/requirements.txt @@ -0,0 +1,2 @@ +boto3 +pyyaml diff --git a/.github/workflows/zip_modules/zip_modules.py b/.github/workflows/zip_modules/zip_modules.py new file mode 100644 index 000000000..97532a243 --- /dev/null +++ b/.github/workflows/zip_modules/zip_modules.py @@ -0,0 +1,87 @@ +import json +import os +import zipfile +import yaml +import boto3 +from boto3.session import Session +from concurrent.futures import ThreadPoolExecutor, as_completed + +# Load index.json file +with open('index.json') as f: + data = json.load(f) + +# Specify your AWS S3 bucket from environment variable +s3_bucket_name = os.environ.get('S3_BUCKET_NAME') # Bucket name sourced from environment variables + +# Create a root zips directory +zip_root_dir = 'zips' +os.makedirs(zip_root_dir, exist_ok=True) + +def upload_to_s3(file_path, s3_client, bucket_name, s3_key): + s3_client.upload_file(file_path, bucket_name, s3_key) + +# Create a boto3 S3 client using environment variables for credentials +session = Session( + aws_access_key_id=os.environ.get('AWS_ACCESS_KEY_ID'), + aws_secret_access_key=os.environ.get('AWS_SECRET_ACCESS_KEY') +) +s3_client = session.client('s3') + +# Initialize a ThreadPoolExecutor for concurrent uploads +with ThreadPoolExecutor() as executor: + # This will hold all future upload tasks + upload_futures = [] + + # Go through each entry in the JSON data + for entry in data: + relative_path = entry['relativePath'] + + # Check if there are any .tf or .tf.json files in the relative path + terraform_files = [file for file in os.listdir(relative_path) if file.endswith('.tf') or file.endswith('.tf.json')] + + if terraform_files: + # Construct the path to facets.yaml + facets_yaml_path = os.path.join(relative_path, 'facets.yaml') + + # Check if facets.yaml exists + if os.path.exists(facets_yaml_path): + # Load facets.yaml file + with open(facets_yaml_path) as yaml_file: + facets_data = yaml.safe_load(yaml_file) + + # Extract intent, flavor, and version + intent = facets_data.get('intent') + flavor = facets_data.get('flavor') + version = facets_data.get('version') + + # Check if all required fields are present + if intent and flavor and version: + print(f"Zipping intent {intent} flavor {flavor} version {version}") + # Create the target zip file path + zip_dir_path = os.path.join(zip_root_dir, intent, flavor, version) + os.makedirs(zip_dir_path, exist_ok=True) + + # Create zip file name + zip_file_name = os.path.join(zip_dir_path, 'module.zip') + + # Create the zip file + with zipfile.ZipFile(zip_file_name, 'w') as zipf: + # Walk through the directory and add files + for root, _, files in os.walk(relative_path): + for file in files: + file_path = os.path.join(root, file) + # Write the file into the zip, preserving the original structure + zipf.write(file_path, os.path.relpath(file_path, os.path.dirname(relative_path))) + + # Prepare to upload the zip file to S3 + s3_key = os.path.relpath(zip_file_name, zip_root_dir) # Path within the S3 bucket + print(f"Uploading {s3_key}") + + # Fire a thread to upload the zip file to S3 + upload_future = executor.submit(upload_to_s3, zip_file_name, s3_client, s3_bucket_name, s3_key) + + upload_futures.append(upload_future) + + # Wait for all upload tasks to complete + for future in as_completed(upload_futures): + future.result() # This will raise any exceptions that occurred during the upload diff --git a/.gitignore b/.gitignore index dfaffa28d..123823604 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,12 @@ .idea/ /.idea/ -.venv \ No newline at end of file +.venv +.terraform/ +*.tfstate +*.tfstate.* +*.tfvars +*.tfvars.json +**/test/** +.DS_store +zips/* +.aider* diff --git a/index.json b/index.json index 439b67aa5..018fe0ed7 100644 --- a/index.json +++ b/index.json @@ -1520,5 +1520,70 @@ "azure", "aws" ] + }, + { + "intent": "artifactories", + "flavor": "default", + "version": "0.1", + "gitUrl": "https://github.com/Facets-cloud/facets-modules", + "relativePath": "./modules/artifactories/default/0.1/", + "clouds": [ + "gcp", + "kubernetes", + "azure", + "aws" + ] + }, + { + "intent": "postgres_user", + "flavor": "default", + "version": "0.2", + "gitUrl": "https://github.com/Facets-cloud/facets-modules", + "relativePath": "./modules/postgres_user/default/0.2/", + "clouds": [ + "aws", + "gcp", + "azure", + "kubernetes" + ] + }, + { + "intent": "mysql_user", + "flavor": "default", + "version": "0.2", + "gitUrl": "https://github.com/Facets-cloud/facets-modules", + "relativePath": "./modules/mysql_user/default/0.2/", + "clouds": [ + "aws", + "gcp", + "azure", + "kubernetes" + ] + }, + { + "intent": "kafka_user", + "flavor": "default", + "version": "0.2", + "gitUrl": "https://github.com/Facets-cloud/facets-modules", + "relativePath": "./modules/kafka_user/default/0.2/", + "clouds": [ + "aws", + "gcp", + "azure", + "kubernetes" + ] + }, + { + "intent": "mongo_user", + "flavor": "default", + "version": "0.2", + "gitUrl": "https://github.com/Facets-cloud/facets-modules", + "relativePath": "./modules/mongo_user/default/0.2/", + "clouds": [ + "aws", + "gcp", + "azure", + "kubernetes" + ] } ] diff --git a/intents.index.json b/intents.index.json index 0f2038201..b4f4377c9 100644 --- a/intents.index.json +++ b/intents.index.json @@ -310,5 +310,13 @@ { "gitUrl": "https://github.com/Facets-cloud/facets-modules", "relativePath": "./intents/ecs_service/" + }, + { + "gitUrl": "https://github.com/Facets-cloud/facets-modules", + "relativePath": "./intents/artifactories/" + }, + { + "gitUrl": "https://github.com/Facets-cloud/facets-modules", + "relativePath": "./intents/misc/" } ] diff --git a/intents/alb/facets.yaml b/intents/alb/facets.yaml index d62c7163d..8632ec58a 100644 --- a/intents/alb/facets.yaml +++ b/intents/alb/facets.yaml @@ -2,3 +2,4 @@ name: alb type: default displayName: ALB description: Application Load Balancer that distributes traffic to improve availability, security, and fault tolerance. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333e183320d4691560d5_Default-Alb.svg \ No newline at end of file diff --git a/intents/alert_group/facets.yaml b/intents/alert_group/facets.yaml index f684f5573..e7cbec41f 100644 --- a/intents/alert_group/facets.yaml +++ b/intents/alert_group/facets.yaml @@ -2,3 +2,4 @@ name: alert_group type: default displayName: Alert Group description: Collection of alerts to notify on specific triggers, ensuring timely action and incident management. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/669fa0ccc5a55d8bbcc134b7_alert_group.svg diff --git a/intents/artifactories/facets.yaml b/intents/artifactories/facets.yaml new file mode 100644 index 000000000..dc7873059 --- /dev/null +++ b/intents/artifactories/facets.yaml @@ -0,0 +1,8 @@ +name: artifactories +type: default +displayName: Artifactories +description: Provides an Artifactories resource for creating Container Registry secrets within the cluster. +outputs: + - name: default + type: "@output/artifactories" + diff --git a/intents/aws_api_gateway/facets.yaml b/intents/aws_api_gateway/facets.yaml index 232988d13..b8af633f3 100644 --- a/intents/aws_api_gateway/facets.yaml +++ b/intents/aws_api_gateway/facets.yaml @@ -2,3 +2,4 @@ name: aws_api_gateway type: default displayName: AWS API Gateway description: Fully managed service to create, publish, and secure APIs at any scale. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333e29c72d2d0c6819c3_Default-aws_api_gateway.svg \ No newline at end of file diff --git a/intents/aws_efs/facets.yaml b/intents/aws_efs/facets.yaml index 69bb6521c..961095d79 100644 --- a/intents/aws_efs/facets.yaml +++ b/intents/aws_efs/facets.yaml @@ -2,3 +2,4 @@ name: aws_efs type: default displayName: AWS EFS description: Provides an Elastic File System (EFS) File System resource. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333efae79e81bc2a0d1e_Default-aws_efs.svg \ No newline at end of file diff --git a/intents/aws_event_bus/facets.yaml b/intents/aws_event_bus/facets.yaml index ad2488b41..e8a51ffe8 100644 --- a/intents/aws_event_bus/facets.yaml +++ b/intents/aws_event_bus/facets.yaml @@ -2,3 +2,4 @@ name: aws_event_bus type: default displayName: AWS Event Bus description: Event routing service that connects applications with event producers and consumers. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333e08611346d019ddff_Default-aws_event_bus.svg \ No newline at end of file diff --git a/intents/aws_eventbridge/facets.yaml b/intents/aws_eventbridge/facets.yaml index 18c9cdba4..f743d593c 100644 --- a/intents/aws_eventbridge/facets.yaml +++ b/intents/aws_eventbridge/facets.yaml @@ -2,3 +2,4 @@ name: aws_eventbridge type: default displayName: AWS EventBridge description: Event-driven service that enables integration across AWS services and custom applications. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333e2522e5be67941b5c_Default-aws_eventbridge.svg \ No newline at end of file diff --git a/intents/aws_hostedzone/facets.yaml b/intents/aws_hostedzone/facets.yaml index 927130065..bbe57465d 100644 --- a/intents/aws_hostedzone/facets.yaml +++ b/intents/aws_hostedzone/facets.yaml @@ -1,4 +1,5 @@ name: hostedzone type: Hostedzone displayName: Hostedzone -description: AWS Private Hosted Zone Service \ No newline at end of file +description: AWS Private Hosted Zone Service +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3337b8f7cc65239f49eac_unknown-hostedzone.svg \ No newline at end of file diff --git a/intents/aws_lambda/facets.yaml b/intents/aws_lambda/facets.yaml index 4aa6d247f..5bd96cc23 100644 --- a/intents/aws_lambda/facets.yaml +++ b/intents/aws_lambda/facets.yaml @@ -2,3 +2,4 @@ name: aws_lambda type: default displayName: AWS Lambda description: Serverless compute service that automatically runs code in response to triggers or events. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333eae25640a6072e81b_Default-aws_lambda.svg \ No newline at end of file diff --git a/intents/aws_secret_manager/facets.yaml b/intents/aws_secret_manager/facets.yaml index dc437dad5..46571ffed 100644 --- a/intents/aws_secret_manager/facets.yaml +++ b/intents/aws_secret_manager/facets.yaml @@ -2,3 +2,4 @@ name: aws_secret_manager type: default displayName: AWS Secret Manager description: Secrets Manager to manage access to applications, services, and IT resources. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333eabb358800faf8ff5_Default-aws_secret_manager.svg \ No newline at end of file diff --git a/intents/aws_waf/facets.yaml b/intents/aws_waf/facets.yaml index 71c65a9ce..0ca007a68 100644 --- a/intents/aws_waf/facets.yaml +++ b/intents/aws_waf/facets.yaml @@ -2,3 +2,4 @@ name: aws_waf type: default displayName: AWS WAF description: Web Application Firewall that protects applications against common web exploits. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333eb375c629d7047388_Default-aws_waf.svg \ No newline at end of file diff --git a/intents/azure_functions/facets.yaml b/intents/azure_functions/facets.yaml index 5f146d1a7..6b65759a3 100644 --- a/intents/azure_functions/facets.yaml +++ b/intents/azure_functions/facets.yaml @@ -2,3 +2,4 @@ name: azure_functions type: default displayName: Azure Functions description: Serverless compute service that runs code without infrastructure management, triggered by events. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333efc1a9f80dfe6bcd3_Default-azure_functions.svg \ No newline at end of file diff --git a/intents/cassandra/facets.yaml b/intents/cassandra/facets.yaml index 69c00104a..e57f7c14d 100644 --- a/intents/cassandra/facets.yaml +++ b/intents/cassandra/facets.yaml @@ -2,3 +2,4 @@ name: cassandra type: K8s displayName: Cassandra description: Distributed NoSQL database system designed for high availability and scalability. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333eb2b9a3d71535dd68_K8s-cassandra.svg \ No newline at end of file diff --git a/intents/cloudflare/facets.yaml b/intents/cloudflare/facets.yaml index 38cccdd6f..593537d4e 100644 --- a/intents/cloudflare/facets.yaml +++ b/intents/cloudflare/facets.yaml @@ -2,3 +2,4 @@ name: cloudflare type: default displayName: CloudFlare description: Global network service that enhances website performance, security, and reliability. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333e4e5fbb80fd60422d_Default-cloudflare.svg \ No newline at end of file diff --git a/intents/cloudfront/facets.yaml b/intents/cloudfront/facets.yaml index e043a632d..795a30f1e 100644 --- a/intents/cloudfront/facets.yaml +++ b/intents/cloudfront/facets.yaml @@ -1,4 +1,5 @@ name: cloudfront type: default displayName: CloudFront -description: Content delivery network (CDN) that securely delivers data, videos, and applications to users globally. \ No newline at end of file +description: Content delivery network (CDN) that securely delivers data, videos, and applications to users globally. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333e8f2316a71e745e3d_Default-cloudfront.svg \ No newline at end of file diff --git a/intents/config_map/facets.yaml b/intents/config_map/facets.yaml index c3c393981..a321c403c 100644 --- a/intents/config_map/facets.yaml +++ b/intents/config_map/facets.yaml @@ -2,3 +2,4 @@ name: config_map type: K8s displayName: Config Map description: A Kubernetes resource that defines workloads, services, and configurations, enabling orchestration of containerized applications. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/669fa0cc958e099a4e3561c5_config_map.svg diff --git a/intents/dynamodb/facets.yaml b/intents/dynamodb/facets.yaml index 382baf507..55259c595 100644 --- a/intents/dynamodb/facets.yaml +++ b/intents/dynamodb/facets.yaml @@ -2,3 +2,4 @@ name: dynamodb type: default displayName: DynamoDB description: Fully managed NoSQL database service that offers fast and predictable performance at any scale. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/669fa0ccade3d320ff1e7a9e_dynamodb.svg diff --git a/intents/ecs_service/facets.yaml b/intents/ecs_service/facets.yaml index 3d468a2c9..cbac9eab5 100644 --- a/intents/ecs_service/facets.yaml +++ b/intents/ecs_service/facets.yaml @@ -2,3 +2,4 @@ name: ecs_service type: Service displayName: ECS Service description: An Amazon ECS service for running and managing containers in a scalable and reliable manner. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333e3ee06b2739a66cd0_unknown-ecs_service.svg \ No newline at end of file diff --git a/intents/google_cloud_storage/facets.yaml b/intents/google_cloud_storage/facets.yaml index 4c42265a0..cef9ed854 100644 --- a/intents/google_cloud_storage/facets.yaml +++ b/intents/google_cloud_storage/facets.yaml @@ -2,3 +2,4 @@ name: google_cloud_storage type: Storage displayName: Google Cloud Storage description: A unified, scalable, and highly durable object storage solution by Google Cloud for storing and retrieving any amount of data at any time. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333e02da2c978f0b0452_Storage-google_cloud_storage.svg \ No newline at end of file diff --git a/intents/grafana_dashboard/facets.yaml b/intents/grafana_dashboard/facets.yaml index be5b1c2fc..eec9a13fe 100644 --- a/intents/grafana_dashboard/facets.yaml +++ b/intents/grafana_dashboard/facets.yaml @@ -1,4 +1,5 @@ name: grafana_dashboard type: default displayName: Grafana Dashboard -description: A customizable interface for visualizing and monitoring metrics, logs, and data from various sources in Grafana. \ No newline at end of file +description: A customizable interface for visualizing and monitoring metrics, logs, and data from various sources in Grafana. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/669fa0cc9cf7aaf56636106e_grafana_dashboard.svg \ No newline at end of file diff --git a/intents/kafka/facets.yaml b/intents/kafka/facets.yaml index ba6d4ab48..5a4a1c621 100644 --- a/intents/kafka/facets.yaml +++ b/intents/kafka/facets.yaml @@ -3,4 +3,6 @@ type: Queue displayName: Kafka description: A distributed streaming platform designed for building real-time data pipelines and streaming applications, known for its high throughput and fault tolerance. iconUrl: https://uploads-ssl.webflow.com/6252ef50a9f5d4afb6983bc3/669fa0cce7d0e1166a2f16f6_kafka.svg - +outputs: + - name: default + type: "@output/kafka" diff --git a/intents/kafka_user/facets.yaml b/intents/kafka_user/facets.yaml index 66f6d9ce3..ab631662e 100644 --- a/intents/kafka_user/facets.yaml +++ b/intents/kafka_user/facets.yaml @@ -1,4 +1,8 @@ name: kafka_user type: default displayName: Kafka User -description: Database user for Kafka instances, defining access permissions and authentication. \ No newline at end of file +description: Database user for Kafka instances, defining access permissions and authentication. +outputs: + - name: default + type: "@output/kafka_user" +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333e1b1bc0b0d17ddcf2_Default-kafka_user.svg \ No newline at end of file diff --git a/intents/kong/facets.yaml b/intents/kong/facets.yaml index 1a7ba66f1..85e05cf2b 100644 --- a/intents/kong/facets.yaml +++ b/intents/kong/facets.yaml @@ -2,3 +2,4 @@ name: kong type: K8s displayName: Kong description: A kubernetes deployment of kong +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333e590c1939e39d9042_unknown-kong.svg \ No newline at end of file diff --git a/intents/kubernetes_cluster/facets.yaml b/intents/kubernetes_cluster/facets.yaml index 32c715d47..a03060281 100644 --- a/intents/kubernetes_cluster/facets.yaml +++ b/intents/kubernetes_cluster/facets.yaml @@ -1,4 +1,7 @@ name: kubernetes_cluster type: default displayName: Kubernetes Cluster -description: A group of computing nodes, or worker machines, that run containerized applications. \ No newline at end of file +description: A group of computing nodes, or worker machines, that run containerized applications. +outputs: + - name: default + type: "@output/kubernetes" diff --git a/intents/kubernetes_node_pool/facets.yaml b/intents/kubernetes_node_pool/facets.yaml index 97f1eadee..22d7bc7c3 100644 --- a/intents/kubernetes_node_pool/facets.yaml +++ b/intents/kubernetes_node_pool/facets.yaml @@ -2,3 +2,4 @@ name: kubernetes_node_pool type: default displayName: Kubernetes Node Pool description: Group of nodes within a Kubernetes cluster that can be scaled and configured independently. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/669fa0ccd8fbce5640b8bb0a_kubernetes_node_pool.svg \ No newline at end of file diff --git a/intents/kubernetes_secret/facets.yaml b/intents/kubernetes_secret/facets.yaml index cad1936f5..fbd960767 100644 --- a/intents/kubernetes_secret/facets.yaml +++ b/intents/kubernetes_secret/facets.yaml @@ -2,3 +2,4 @@ name: kubernetes_secret type: K8s displayName: Kubernetes Secret description: A Kubernetes resource that defines workloads, services, and configurations, enabling orchestration of containerized applications. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333eb375c629d70473a5_K8s-secret.svg \ No newline at end of file diff --git a/intents/loki_recording_rules/facets.yaml b/intents/loki_recording_rules/facets.yaml index 449d5789a..a2db27900 100644 --- a/intents/loki_recording_rules/facets.yaml +++ b/intents/loki_recording_rules/facets.yaml @@ -2,3 +2,4 @@ name: loki_recording_rules type: K8s displayName: Loki Recording Rules description: Precompute frequently needed queries in Loki for faster access and optimized performance. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333e791d1a114977cba2_K8s-loki_recording.svg \ No newline at end of file diff --git a/intents/misc/facets.yaml b/intents/misc/facets.yaml new file mode 100644 index 000000000..0016a3ce6 --- /dev/null +++ b/intents/misc/facets.yaml @@ -0,0 +1,4 @@ +name: misc +type: default +displayName: Miscellaneous +description: Miscellaneous automations diff --git a/intents/mongo_user/facets.yaml b/intents/mongo_user/facets.yaml index dd99a9e21..ce876700b 100644 --- a/intents/mongo_user/facets.yaml +++ b/intents/mongo_user/facets.yaml @@ -1,4 +1,7 @@ name: mongo_user type: default displayName: Mongo User -description: Database user for MongoDB instances, defining access permissions and authentication. \ No newline at end of file +description: Database user for MongoDB instances, defining access permissions and authentication. +outputs: + - name: default + type: "@output/mongo_user" \ No newline at end of file diff --git a/intents/mysql_user/facets.yaml b/intents/mysql_user/facets.yaml index c8d91c314..c94d6fb5e 100644 --- a/intents/mysql_user/facets.yaml +++ b/intents/mysql_user/facets.yaml @@ -1,4 +1,7 @@ name: mysql_user type: sharded_user displayName: MySQL User -description: User account in MySQL database, specifying roles and access privileges. \ No newline at end of file +description: User account in MySQL database, specifying roles and access privileges. +outputs: + - name: default + type: "@output/mysql_user" \ No newline at end of file diff --git a/intents/peering/facets.yaml b/intents/peering/facets.yaml index 753afb605..ad974558c 100644 --- a/intents/peering/facets.yaml +++ b/intents/peering/facets.yaml @@ -1,4 +1,5 @@ name: peering type: default displayName: Peering -description: Networking connection between VPCs, enabling private communication across environments. \ No newline at end of file +description: Networking connection between VPCs, enabling private communication across environments. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/669fa0cc958e099a4e3561cc_peering.svg \ No newline at end of file diff --git a/intents/postgres_user/facets.yaml b/intents/postgres_user/facets.yaml index c1c187dc0..40e66ce04 100644 --- a/intents/postgres_user/facets.yaml +++ b/intents/postgres_user/facets.yaml @@ -1,4 +1,7 @@ name: postgres_user type: default displayName: Postgres User -description: User account in PostgreSQL, controlling access to databases and managing privileges. \ No newline at end of file +description: User account in PostgreSQL, controlling access to databases and managing privileges. +outputs: + - name: default + type: "@output/postgres_user" diff --git a/intents/pvc/facets.yaml b/intents/pvc/facets.yaml index 7d0537069..bb075cf59 100644 --- a/intents/pvc/facets.yaml +++ b/intents/pvc/facets.yaml @@ -5,3 +5,4 @@ description: Persistent Volume Claim for storage in Kubernetes, dynamically requ outputs: - name: default type: "@output/pvc" +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333ece2efe941434643a_K8s-pvc.svg \ No newline at end of file diff --git a/intents/rabbitmq/facets.yaml b/intents/rabbitmq/facets.yaml index d1bd79ef3..5eabc39d8 100644 --- a/intents/rabbitmq/facets.yaml +++ b/intents/rabbitmq/facets.yaml @@ -1,4 +1,5 @@ name: rabbitmq type: Indexing displayName: RabbitMQ -description: An open source general-purpose message broker that is designed for consistent, highly-available messaging scenarios. \ No newline at end of file +description: An open source general-purpose message broker that is designed for consistent, highly-available messaging scenarios. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/669fa0cccfd2aae6c3ac9d36_queue.svg \ No newline at end of file diff --git a/intents/schemahero_database/facets.yaml b/intents/schemahero_database/facets.yaml index 6322d77eb..ab554b44c 100644 --- a/intents/schemahero_database/facets.yaml +++ b/intents/schemahero_database/facets.yaml @@ -2,3 +2,4 @@ name: schemahero_database type: K8s displayName: Schemahero Database description: Intent to configure a database for schema migrations. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/669fa0cc0cc2c2fb2f213976_schemahero_database.svg diff --git a/intents/schemahero_table/facets.yaml b/intents/schemahero_table/facets.yaml index 0b39c5571..38b4ed51f 100644 --- a/intents/schemahero_table/facets.yaml +++ b/intents/schemahero_table/facets.yaml @@ -2,3 +2,4 @@ name: schemahero_table type: K8s displayName: Schemahero Table description: Intent to configure table of a database for schema migration. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333e0fc0d5850a27a2f7_K8s-schemahero_table.svg \ No newline at end of file diff --git a/intents/service_bus/facets.yaml b/intents/service_bus/facets.yaml index 8bbc326db..f3732629c 100644 --- a/intents/service_bus/facets.yaml +++ b/intents/service_bus/facets.yaml @@ -1,4 +1,5 @@ name: service_bus type: azure_service_bus displayName: Service Bus -description: Messaging service that enables communication between applications and services. \ No newline at end of file +description: Messaging service that enables communication between applications and services. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/67a3333eabb358800faf8ff0_Azure%20Service%20Bus.svg \ No newline at end of file diff --git a/intents/sqs/facets.yaml b/intents/sqs/facets.yaml index 643596330..2ad2bfbc0 100644 --- a/intents/sqs/facets.yaml +++ b/intents/sqs/facets.yaml @@ -2,6 +2,7 @@ name: sqs type: default displayName: SQS description: Fully managed AWS message queuing service. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/669fa0cd417c10aff8f0f2a5_sqs.svg outputs: - name: attributes.consumer_policy_arn title: "SQS Consumer Permission" diff --git a/intents/tcp_lb/facets.yaml b/intents/tcp_lb/facets.yaml index f665d37af..61ce7b800 100644 --- a/intents/tcp_lb/facets.yaml +++ b/intents/tcp_lb/facets.yaml @@ -2,3 +2,4 @@ name: tcp_lb type: nlb displayName: TCP LB description: TCP Load Balancer that distributes TCP traffic across backend servers. +iconUrl: https://cdn.prod.website-files.com/6252ef50a9f5d4afb6983bc3/669fa0cc7aaca5f9683545a9_tcp_lb.svg \ No newline at end of file diff --git a/modules/artifactories/default/0.1/README.md b/modules/artifactories/default/0.1/README.md new file mode 100644 index 000000000..9c6809ede --- /dev/null +++ b/modules/artifactories/default/0.1/README.md @@ -0,0 +1,19 @@ +# Terraform Artifactories Module + +This is a artifactories per instance module + +## Run this module manually + +- `cd test` +- Make sure you add/update details in [provider.tf](test/providers.tf) +- Run `terraform init` +- Run `terraform apply` + - **Note:** _Optional flag `-auto-approve` to skip interactive approval of plan before applying_ +- When you're done, run `terraform destroy` + - **Note:** _Optional flag `-auto-approve` to skip interactive approval of plan before destroying_ + +## Running automated tests against this module + +- `cd test` +- Make sure to update `config_path` in var block inside go [test file](test/artifactories_test.go) +- Run `go test -v -run Artifactories` diff --git a/modules/artifactories/default/0.1/ecr-token-refresher-command b/modules/artifactories/default/0.1/ecr-token-refresher-command new file mode 100644 index 000000000..f971c17f4 --- /dev/null +++ b/modules/artifactories/default/0.1/ecr-token-refresher-command @@ -0,0 +1,7 @@ +export DOCKER_USER=AWS +export DOCKER_PASSWORD=`aws ecr get-login --region ${AWS_REGION} --registry-ids ${AWS_ACCOUNT} | cut -d' ' -f6` +kubectl delete secret ${KEY_NAME} --namespace ${NAMESPACE} || true +kubectl create secret docker-registry ${KEY_NAME} --docker-server=$DOCKER_REGISTRY_SERVER --docker-username=$DOCKER_USER --docker-password=$DOCKER_PASSWORD --docker-email=no@email.local --namespace ${NAMESPACE} +if [ -n "${INSTANCE_LABELS}" ]; then + kubectl label secret ${KEY_NAME} --namespace ${NAMESPACE} "${INSTANCE_LABELS}" +fi diff --git a/modules/artifactories/default/0.1/ecr-token-refresher.tf b/modules/artifactories/default/0.1/ecr-token-refresher.tf new file mode 100644 index 000000000..2bec44ce7 --- /dev/null +++ b/modules/artifactories/default/0.1/ecr-token-refresher.tf @@ -0,0 +1,220 @@ +resource "kubernetes_secret_v1" "ecr-token-refresher-configs" { + for_each = local.artifactories_ecr + metadata { + name = "ecr-tkn-renewer-${local.name}-${each.key}" + namespace = local.namespace + } + data = { + aws_access_key_id = each.value["awsKey"] + aws_access_secret_key = each.value["awsSecret"] + aws_account = each.value["awsAccountId"] + aws_region = each.value["awsRegion"] + registry_url = each.value["uri"] + registry_name = each.key + secret_name = "${local.name}-${each.key}" + } +} + +resource "kubernetes_cron_job_v1" "ecr-token-refresher-cron" { + for_each = local.artifactories_ecr + metadata { + name = "ecr-tkn-renewer-${local.name}-${each.key}" + namespace = local.namespace + } + spec { + concurrency_policy = "Allow" + failed_jobs_history_limit = 1 + schedule = "5 */3 * * *" + starting_deadline_seconds = 20 + successful_jobs_history_limit = 1 + suspend = false + job_template { + metadata {} + spec { + backoff_limit = 4 + template { + metadata {} + spec { + priority_class_name = "facets-critical" + toleration { + operator = "Exists" + } + service_account_name = kubernetes_service_account.ecr-token-refresher-sa[each.key].metadata.0.name + automount_service_account_token = true + node_selector = { + "kubernetes.io/arch" = "amd64" + } + container { + name = "kubectl" + image = "xynova/aws-kubectl" + image_pull_policy = "Always" + command = ["/bin/sh", "-c", file("${path.module}/ecr-token-refresher-command")] + env { + name = "AWS_ACCOUNT" + value_from { + secret_key_ref { + key = "aws_account" + name = "ecr-tkn-renewer-${local.name}-${each.key}" + } + } + } + env { + name = "AWS_ACCESS_KEY_ID" + value_from { + secret_key_ref { + key = "aws_access_key_id" + name = "ecr-tkn-renewer-${local.name}-${each.key}" + } + } + } + env { + name = "AWS_SECRET_ACCESS_KEY" + value_from { + secret_key_ref { + key = "aws_access_secret_key" + name = "ecr-tkn-renewer-${local.name}-${each.key}" + } + } + } + env { + name = "AWS_REGION" + value_from { + secret_key_ref { + key = "aws_region" + name = "ecr-tkn-renewer-${local.name}-${each.key}" + } + } + } + env { + name = "KEY_NAME" + value_from { + secret_key_ref { + key = "secret_name" + name = "ecr-tkn-renewer-${local.name}-${each.key}" + } + } + } + env { + name = "DOCKER_REGISTRY_SERVER" + value_from { + secret_key_ref { + key = "registry_url" + name = "ecr-tkn-renewer-${local.name}-${each.key}" + } + } + } + env { + name = "NAMESPACE" + value = local.namespace + } + env { + name = "INSTANCE_LABELS" + value = local.labels + } + } + restart_policy = "Never" + } + } + } + } + } +} + +resource "kubernetes_role_v1" "ecr-token-refresher-role" { + for_each = local.artifactories_ecr + metadata { + name = "ecr-tkn-renewer-${local.name}-${each.key}" + namespace = local.namespace + } + rule { + api_groups = [""] + resources = ["secrets"] + verbs = ["get", "create", "delete", "patch"] + } + rule { + api_groups = [""] + resources = ["serviceaccounts"] + verbs = ["get", "patch", "list"] + } +} + +resource "kubernetes_service_account" "ecr-token-refresher-sa" { + for_each = local.artifactories_ecr + metadata { + name = "ecr-tkn-renewer-${local.name}-${each.key}" + namespace = local.namespace + } +} + +resource "kubernetes_role_binding_v1" "ecr-token-refresher-crb" { + for_each = local.artifactories_ecr + metadata { + name = "ecr-tkn-renewer-${local.name}-${each.key}" + namespace = local.namespace + } + role_ref { + api_group = "rbac.authorization.k8s.io" + kind = "Role" + name = kubernetes_role_v1.ecr-token-refresher-role[each.key].metadata.0.name + } + subject { + kind = "ServiceAccount" + name = kubernetes_service_account.ecr-token-refresher-sa[each.key].metadata.0.name + namespace = local.namespace + } +} + +resource "null_resource" "patch_default_sa" { + triggers = { + host = local.host + token = local.token + "registries" = jsonencode(local.artifactory_list) + } + depends_on = [null_resource.wait-for-ecr-token-patch] + provisioner "local-exec" { + command = "/bin/bash ../tfmain/scripts/run_with_kubeconfig.sh kubectl patch sa default -p '${jsonencode({ "imagePullSecrets" = local.registry_secrets_list })}'" + environment = { + SERVER = local.host + CA = local.cluster_ca_certificate + TOKEN = local.token + } + } +} + +resource "null_resource" "patch_admin_sa" { + count = var.cluster.cloud == "AWS" ? 1 : 0 + triggers = { + host = local.host + token = local.token + "registries" = jsonencode(local.artifactory_list) + } + depends_on = [null_resource.wait-for-ecr-token-patch] + provisioner "local-exec" { + command = "/bin/bash ../tfmain/scripts/run_with_kubeconfig.sh kubectl patch sa capillary-cloud-admin -p '${jsonencode({ "imagePullSecrets" = local.registry_secrets_list })}'" + environment = { + SERVER = local.host + CA = local.cluster_ca_certificate + TOKEN = local.token + } + } +} + +resource "null_resource" "wait-for-ecr-token-patch" { + triggers = { + host = local.host + token = local.token + registries = jsonencode(local.artifactory_list) + } + for_each = local.artifactories_ecr + depends_on = [kubernetes_cron_job_v1.ecr-token-refresher-cron] + provisioner "local-exec" { + command = "/bin/bash ../tfmain/scripts/trigger-ecr-token-refresh.sh" + environment = { + SERVER = local.host + CA = local.cluster_ca_certificate + TOKEN = local.token + CRON_NAME = kubernetes_cron_job_v1.ecr-token-refresher-cron[each.key].metadata[0].name + NAMESPACE = local.namespace + } + } +} diff --git a/modules/artifactories/default/0.1/facets.yaml b/modules/artifactories/default/0.1/facets.yaml new file mode 100644 index 000000000..b5cf63559 --- /dev/null +++ b/modules/artifactories/default/0.1/facets.yaml @@ -0,0 +1,57 @@ +intent: artifactories +flavor: default +version: '0.1' +description: Adds artifactories - default flavor +clouds: +- gcp +- kubernetes +- azure +- aws +spec: + title: Artifactories + type: object + description: Specification of the Artifactories resource intent + properties: + include_all: + type: boolean + title: Include All + description: "Include all container registries mapped to this project." + artifactories: + title: Artifactories + type: object + description: "A map of Container Registries." + x-ui-visible-if: + field: spec.include_all + values: [false] + patternProperties: + keyPattern: "^[a-zA-Z0-9_.-]*$" + title: Artifactories Object Block + description: The Name of the Container Registry for creating a secret. + type: object + properties: + name: + title: Name + type: string + description: "Container Registry Name" + x-ui-api-source: + endpoint: "/cc-ui/v1/artifactories" + method: GET + params: + includeContent: false + labelKey: name + valueKey: name +sample: + $schema: https://facets-cloud.github.io/facets-schemas/schemas/artifactories/artifactories.schema.json + version: '0.1' + flavor: default + kind: artifactories + disabled: true + spec: + include_all: false +inputs: + kubernetes_details: + optional: false + type: "@output/kubernetes" + default: + resource_type: kubernetes_cluster + resource_name: default diff --git a/modules/artifactories/default/0.1/locals.tf b/modules/artifactories/default/0.1/locals.tf new file mode 100644 index 000000000..4f074d591 --- /dev/null +++ b/modules/artifactories/default/0.1/locals.tf @@ -0,0 +1,63 @@ +locals { + spec = lookup(var.instance, "spec", {}) + metadata = lookup(var.instance, "metadata", {}) + name = lookup(local.metadata, "name", var.instance_name) + namespace = lookup(local.metadata, "namespace", lookup(var.cluster, "namespace", "default")) + artifactories = lookup(local.spec, "artifactories", {}) + include_all = lookup(local.spec, "include_all", length(local.artifactories) > 0 ? "false" : "true") + kubernetes_details = var.inputs.kubernetes_details.attributes.legacy_outputs.k8s_details + host = local.kubernetes_details.auth.host + cluster_ca_certificate = base64encode(local.kubernetes_details.auth.cluster_ca_certificate) + token = local.kubernetes_details.auth.token + artifactory_list = jsondecode(file("../deploymentcontext.json"))["artifactoryDetails"] + artifactories_ecr = { + for artifactory in local.artifactory_list : + artifactory["name"] => artifactory if lookup(artifactory, "artifactoryType", "ECR") == "ECR" && (local.include_all || contains([for key, value in local.artifactories : value["name"]], artifactory["name"])) + } + artifactories_dockerhub = { + for artifactory in local.artifactory_list : + artifactory["name"] => artifactory if lookup(artifactory, "artifactoryType", "ECR") != "ECR" && (local.include_all || contains([for key, value in local.artifactories : value["name"]], artifactory["name"])) + } + ecr_secret_objects = { + for artifactory in local.artifactories_ecr : artifactory["name"] => [{ name : "${local.name}-${artifactory["name"]}" }] + } + + artifact_uri = [for artifact in local.artifactories_dockerhub : artifact["uri"]] + has_duplicate_uri = length(distinct(local.artifact_uri)) == length(local.artifact_uri) ? false : true + secret_metadata = !local.has_duplicate_uri ? { + "${local.name}" = { + name = local.name + dockerconfigjson = jsonencode({ + auths = { + for key, value in local.artifactories_dockerhub : + value.uri => { + username = value.username + password = value.password + email = lookup(value, "email", "no@email.com") + auth = base64encode("${value.username}:${value.password}") + } + } + }) + } + } : { for key, value in local.artifactories_dockerhub : "${local.name}-${key}" => { + name = "${local.name}-${key}" + dockerconfigjson = jsonencode({ + auths = { + "${value.uri}" = { + username = value.username + password = value.password + email = lookup(value, "email", "no@email.com") + auth = base64encode("${value.username}:${value.password}") + } + } + }) + } } + dockerhub_secret_objects = { + for dockerhub_artifactory in local.artifactories_dockerhub : dockerhub_artifactory["name"] => [{ name : !local.has_duplicate_uri ? local.name : "${local.name}-${dockerhub_artifactory["name"]}" }] + } + + registry_secret_objects = merge(local.ecr_secret_objects, local.dockerhub_secret_objects) + registry_secrets_list = flatten([for k, v in merge(local.registry_secret_objects) : v]) + + labels = join(",", [for k, v in lookup(local.metadata, "labels", {}) : "${k}=${v}"]) +} diff --git a/modules/artifactories/default/0.1/outputs.tf b/modules/artifactories/default/0.1/outputs.tf new file mode 100644 index 000000000..8ef21f1ef --- /dev/null +++ b/modules/artifactories/default/0.1/outputs.tf @@ -0,0 +1,7 @@ +locals { + output_interfaces = {} + output_attributes = { + registry_secrets_list = local.registry_secrets_list + registry_secret_objects = local.registry_secret_objects + } +} diff --git a/modules/artifactories/default/0.1/registry_secret.tf b/modules/artifactories/default/0.1/registry_secret.tf new file mode 100644 index 000000000..083eba584 --- /dev/null +++ b/modules/artifactories/default/0.1/registry_secret.tf @@ -0,0 +1,12 @@ +resource "kubernetes_secret_v1" "registry_secret" { + for_each = local.artifactories_dockerhub != {} ? local.secret_metadata : {} + metadata { + name = each.value.name + namespace = local.namespace + labels = lookup(local.metadata, "labels", {}) + } + data = { + ".dockerconfigjson" : each.value.dockerconfigjson + } + type = "kubernetes.io/dockerconfigjson" +} diff --git a/modules/artifactories/default/0.1/variables.tf b/modules/artifactories/default/0.1/variables.tf new file mode 100644 index 000000000..42373ee8d --- /dev/null +++ b/modules/artifactories/default/0.1/variables.tf @@ -0,0 +1,63 @@ +variable "cluster" { + type = any + +} + +variable "baseinfra" { + type = any + +} + +variable "cc_metadata" { + type = any +} + +variable "instance" { + type = any + default = {} +} + +variable "advanced" { + type = any + default = {} +} + +variable "instance_name" { + type = string + default = "" +} + +variable "inputs" { + type = any + default = [] +} + +variable "environment" { + type = any + default = {} +} + +variable "release_metadata" { + type = any + default = {} +} + +variable "instance_type" { + type = string + default = "" +} + +variable "iac_version" { + type = string + default = "" +} + +variable "generate_release_metadata" { + type = bool + default = false +} + +variable "settings" { + type = any + default = {} +} \ No newline at end of file diff --git a/modules/ecs_cluster/default/0.1/main.tf b/modules/ecs_cluster/default/0.1/main.tf new file mode 100644 index 000000000..892021c77 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/main.tf @@ -0,0 +1,61 @@ +locals { + metadata = lookup(var.instance, "metadata", {}) + tags = merge(var.environment.cloud_tags, lookup(local.metadata, "tags", {})) + spec = lookup(var.instance, "spec", {}) + cluster = lookup(local.spec, "cluster", {}) + lifecycle = upper(lookup(local.cluster, "lifecycle", "spot")) + fargate_capacity_providers = local.lifecycle == "SPOT" ? { + FARGATE_SPOT = { + name = "FARGATE_SPOT" + default_capacity_provider_strategy = { + weight = 100 + } + } + } : { + FARGATE = { + name = "FARGATE" + default_capacity_provider_strategy = { + weight = 100 + } + } + } +} + +module "name" { + source = "github.com/Facets-cloud/facets-utility-modules//name" + environment = var.environment + limit = 255 + resource_name = var.instance_name + resource_type = "container_service" + globally_unique = true + is_k8s = false +} + +module "ecs" { + source = "./terraform-aws-ecs/modules/cluster" + autoscaling_capacity_providers = {} + cloudwatch_log_group_kms_key_id = lookup(local.spec, "cloudwatch_log_group_kms_key_id", null) + cloudwatch_log_group_name = lookup(local.spec, "cloudwatch_log_group_name", null) + cloudwatch_log_group_retention_in_days = lookup(local.spec, "cloudwatch_log_group_retention_in_days", 90) + cloudwatch_log_group_tags = local.tags + cluster_configuration = lookup(local.spec, "cluster_configuration", {}) + cluster_name = lookup(local.metadata, "name", module.name.name) + cluster_service_connect_defaults = lookup(local.spec, "cluster_service_connect_defaults", {}) + cluster_settings = lookup(local.spec, "cluster_settings", []) + create = true + create_cloudwatch_log_group = lookup(local.spec, "create_cloudwatch_log_group", false) + create_task_exec_iam_role = false + create_task_exec_policy = false + default_capacity_provider_use_fargate = true + fargate_capacity_providers = local.fargate_capacity_providers + tags = local.tags + task_exec_iam_role_description = "" + task_exec_iam_role_name = module.name.name + task_exec_iam_role_path = null + task_exec_iam_role_permissions_boundary = null + task_exec_iam_role_policies = {} + task_exec_iam_role_tags = local.tags + task_exec_iam_role_use_name_prefix = true + task_exec_secret_arns = [] + task_exec_ssm_param_arns = [] +} \ No newline at end of file diff --git a/modules/ecs_cluster/default/0.1/outputs.tf b/modules/ecs_cluster/default/0.1/outputs.tf new file mode 100644 index 000000000..f82de2fc2 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/outputs.tf @@ -0,0 +1,7 @@ +locals { + output_attributes = { + cluster_arn = module.ecs.arn + cluster_name = module.ecs.name + } + output_interfaces = {} +} \ No newline at end of file diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/.editorconfig b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/.editorconfig new file mode 100644 index 000000000..88cb25190 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/.editorconfig @@ -0,0 +1,30 @@ +# EditorConfig is awesome: http://EditorConfig.org +# Uses editorconfig to maintain consistent coding styles + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +max_line_length = 80 +trim_trailing_whitespace = true + +[*.{tf,tfvars}] +indent_size = 2 +indent_style = space + +[*.md] +max_line_length = 0 +trim_trailing_whitespace = false + +[Makefile] +tab_width = 2 +indent_style = tab + +[COMMIT_EDITMSG] +max_line_length = 0 diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/.pre-commit-config.yaml b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/.pre-commit-config.yaml new file mode 100644 index 000000000..7e4e7dafc --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/.pre-commit-config.yaml @@ -0,0 +1,31 @@ +repos: + - repo: https://github.com/antonbabenko/pre-commit-terraform + rev: v1.96.1 + hooks: + - id: terraform_fmt + - id: terraform_wrapper_module_for_each + - id: terraform_docs + args: + - '--args=--lockfile=false' + - id: terraform_tflint + args: + - '--args=--only=terraform_deprecated_interpolation' + - '--args=--only=terraform_deprecated_index' + - '--args=--only=terraform_unused_declarations' + - '--args=--only=terraform_comment_syntax' + - '--args=--only=terraform_documented_outputs' + - '--args=--only=terraform_documented_variables' + - '--args=--only=terraform_typed_variables' + - '--args=--only=terraform_module_pinned_source' + - '--args=--only=terraform_naming_convention' + - '--args=--only=terraform_required_version' + - '--args=--only=terraform_required_providers' + - '--args=--only=terraform_standard_module_structure' + - '--args=--only=terraform_workspace_remote' + - id: terraform_validate + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-merge-conflict + - id: end-of-file-fixer + - id: trailing-whitespace diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/.releaserc.json b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/.releaserc.json new file mode 100644 index 000000000..66b3eefd6 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/.releaserc.json @@ -0,0 +1,45 @@ +{ + "branches": [ + "main", + "master" + ], + "ci": false, + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits" + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits" + } + ], + [ + "@semantic-release/github", + { + "successComment": "This ${issue.pull_request ? 'PR is included' : 'issue has been resolved'} in version ${nextRelease.version} :tada:", + "labels": false, + "releasedLabels": false + } + ], + [ + "@semantic-release/changelog", + { + "changelogFile": "CHANGELOG.md", + "changelogTitle": "# Changelog\n\nAll notable changes to this project will be documented in this file." + } + ], + [ + "@semantic-release/git", + { + "assets": [ + "CHANGELOG.md" + ], + "message": "chore(release): version ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" + } + ] + ] +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/CHANGELOG.md b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/CHANGELOG.md new file mode 100644 index 000000000..ed8bdd9d2 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/CHANGELOG.md @@ -0,0 +1,453 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [5.11.4](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.11.3...v5.11.4) (2024-08-07) + + +### Bug Fixes + +* Local `cluster_name` error when `var.cluster_arn` is empty ([#218](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/218)) ([42f11fe](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/42f11fe46b1f2a00125af0ee98813bff25d0bc46)) + +## [5.11.3](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.11.2...v5.11.3) (2024-07-05) + + +### Bug Fixes + +* Add missing `pid_mode` to root module ([#204](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/204)) ([c9cab6f](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/c9cab6f005379e25ee91d17c7b8d4cb9d8ca9af8)) + +## [5.11.2](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.11.1...v5.11.2) (2024-05-31) + + +### Bug Fixes + +* Make service, task, and task sets wait for their respective policy attachment to ensure permissions are available ([#201](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/201)) ([2033858](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/20338580482518fa086e90d9f74a54e8046fcb9a)) + +## [5.11.1](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.11.0...v5.11.1) (2024-04-10) + + +### Bug Fixes + +* Add aws to required providers ([#181](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/181)) ([59fd4fa](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/59fd4fa7b34daede721a27ba7fda44acfca8de29)) + +## [5.11.0](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.10.1...v5.11.0) (2024-04-03) + + +### Features + +* Allow configuring max_session_duration for the ECS Task Execution role ([#186](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/186)) ([1b8cad1](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/1b8cad10d2f414ecfdeb48e1dee73eccadefad1a)) + +## [5.10.1](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.10.0...v5.10.1) (2024-04-01) + + +### Bug Fixes + +* Dynamic network configuration in service module for external deployments with awsvpc networkmode ([#185](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/185)) ([c817ed9](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/c817ed992ed75d73a54f69bf130c3730af8ba709)), closes [#184](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/184) + +## [5.10.0](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.9.3...v5.10.0) (2024-03-12) + + +### Features + +* Allow disabling service creation to support creating just a task definition ([#176](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/176)) ([94c992a](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/94c992aef62e6c8249acb1109b49a5cf98457288)), closes [#162](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/162) + +## [5.9.3](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.9.2...v5.9.3) (2024-03-07) + + +### Bug Fixes + +* Update CI workflow versions to remove deprecated runtime warnings ([#178](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/178)) ([a1fd9ef](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/a1fd9ef2199a0a5e851e4d131f8bca84d0723065)) + +### [5.9.2](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.9.1...v5.9.2) (2024-03-04) + + +### Bug Fixes + +* Add missing `Name` tag to service security group ([#177](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/177)) ([b3600de](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/b3600def4e922e00483596fb84153ec82b77fc20)) + +### [5.9.1](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.9.0...v5.9.1) (2024-02-19) + + +### Bug Fixes + +* Pass CloudWatch log group name from the service module to the container definition module ([#168](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/168)) ([9a7c9da](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/9a7c9da0fbe686bbe0f9c687986065c4655ba923)) + +## [5.9.0](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.8.1...v5.9.0) (2024-02-12) + + +### Features + +* Add support for cluster and container definition custom CloudWatch log group names ([#160](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/160)) ([9a8c7d3](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/9a8c7d3cb799ec297d8ae1891616bc2872799ab7)) + +### [5.8.1](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.8.0...v5.8.1) (2024-02-12) + + +### Bug Fixes + +* Allow `cluster_settings` to be list of maps instead of single map ([#157](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/157)) ([c32a657](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/c32a65786dbfb28bac8fbc9f60ff3a4ac06830d9)) + +## [5.8.0](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.7.4...v5.8.0) (2024-01-29) + + +### Features + +* Add var service_tags ([#159](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/159)) ([1290240](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/1290240980647d391b49d417cb6d201d5620544c)) + +### [5.7.4](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.7.3...v5.7.4) (2023-12-21) + + +### Bug Fixes + +* Adding a `health_check` generates a new task definition revision on every `terraform apply` ([#149](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/149)) ([492e323](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/492e323c82447fcaecaa818dc9c258daa923f254)) + +### [5.7.3](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.7.2...v5.7.3) (2023-11-27) + + +### Bug Fixes + +* Bump MSV of AWS provider to support AppAutoscaling target tags ([#141](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/141)) ([eb7538a](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/eb7538ab30af1a3f10836f96e413385b8f8e2138)) + +### [5.7.2](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.7.1...v5.7.2) (2023-11-16) + + +### Bug Fixes + +* Allow setting `linux_parameters` without inconsistent left/right error ([#136](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/136)) ([45a37ac](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/45a37aca96651bf541aedc9778c063e12e4c317a)) + +### [5.7.1](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.7.0...v5.7.1) (2023-11-16) + + +### Bug Fixes + +* Add missing tag variable in autoscaling target resource ([#135](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/135)) ([5872445](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/58724455b3738f060de860f298ebc79f3b5d04e6)) + +## [5.7.0](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.6.0...v5.7.0) (2023-11-15) + + +### Features + +* Add create option for container definition ([#133](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/133)) ([66642c8](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/66642c898988bf04b414c681d1ae4e5ecac08ba1)) + +## [5.6.0](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.5.0...v5.6.0) (2023-11-03) + + +### Features + +* Add the external resource equivalents to the outputs as the fallback value ([#131](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/131)) ([e9d920b](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/e9d920b62779a4ea237cd560f66134b320df88c4)) + +## [5.5.0](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.4.0...v5.5.0) (2023-10-31) + + +### Features + +* Replace dynamic DNS suffix resolution for trusted service endpoints with static `*.amazonaws.com` ([#125](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/125)) ([f84dc7d](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/f84dc7d29247a6e2a1012f30bc5e47deeb1df925)) + +## [5.4.0](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.3.0...v5.4.0) (2023-10-30) + + +### Features + +* Add support for easily enabling ECS Exec support ([#127](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/127)) ([76acddb](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/76acddb0cc1649fa4b7a8338f0b6253b68bdeb8e)) + +## [5.3.0](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.2.2...v5.3.0) (2023-10-30) + + +### Features + +* Add support for using container definition CloudWatch log group name as prefix ([#126](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/126)) ([cf4101e](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/cf4101ea7c4ca315f8f9958156b6dfe48c0c4f51)) + +### [5.2.2](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.2.1...v5.2.2) (2023-08-22) + + +### Bug Fixes + +* Correct `wait_until_stable_timeout` variable type ([#110](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/110)) ([daa2c0e](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/daa2c0e8baf0c451c5dd0f614e53e3495ac54a64)) + +### [5.2.1](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.2.0...v5.2.1) (2023-07-27) + + +### Bug Fixes + +* Add default values for attributes that are showing up in plans and causing unnecessary diffs ([#101](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/101)) ([6be5cc1](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/6be5cc1e01c55ebe44c9330e1e075aa013953f3d)) + +## [5.2.0](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.1.0...v5.2.0) (2023-06-05) + + +### Features + +* Add support for setting placement constraints separately between service and task definition ([#93](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/93)) ([a111e5d](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/a111e5d38dca8b8743f8844e8aaa68568e1d737e)) + +## [5.1.0](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.0.2...v5.1.0) (2023-06-05) + + +### Features + +* Add option to ignore `load_balancer` changes to ECS service ([#81](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/81)) ([24bd1d8](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/24bd1d8d48596743a7cc5e74b1943b76e7916a5c)) + +### [5.0.2](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.0.1...v5.0.2) (2023-06-03) + + +### Bug Fixes + +* Missing field LogConfiguration.LogDriver error when enable_cloudwatch_logging is false ([#91](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/91)) ([8ca6fd4](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/8ca6fd4320441ed1f20d2c1c526ad034ab8dc0ac)) + +### [5.0.1](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v5.0.0...v5.0.1) (2023-04-26) + + +### Bug Fixes + +* Ensure that launch type is not specified when a capacity provider strategy is set on a service ([#80](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/80)) ([873cccf](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/873cccf1ea23b87fec8bffe0a4825178f0b1cacf)) + +## [5.0.0](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v4.1.3...v5.0.0) (2023-04-21) + + +### âš  BREAKING CHANGES + +* Add support for creating ECS service and container definition (#76) + +### Features + +* Add support for creating ECS service and container definition ([#76](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/76)) ([57244e6](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/57244e69abea685f7d45352abc994779b5f6d352)) + +### [4.1.3](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v4.1.2...v4.1.3) (2023-01-24) + + +### Bug Fixes + +* Use a version for to avoid GitHub API rate limiting on CI workflows ([#73](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/73)) ([fbff232](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/fbff232279bc5292a8469aa2a999ed08f4457011)) + +### [4.1.2](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v4.1.1...v4.1.2) (2022-11-07) + + +### Bug Fixes + +* Update CI configuration files to use latest version ([#71](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/71)) ([2f68113](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/2f68113130c3a815a2d0b83af0f645d561c1fbe0)) + +### [4.1.1](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v4.1.0...v4.1.1) (2022-07-25) + + +### Bug Fixes + +* Allow for both Fargate and EC2/Autoscaling capacity providers in same cluster ([#69](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/69)) ([9cb2b84](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/9cb2b84e5bb3983392c2d4c0e1f2879b42bbf9a7)) + +## [4.1.0](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v4.0.2...v4.1.0) (2022-06-29) + + +### Features + +* Export cluster name since cluster ID is exporting the ARN ([#67](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/67)) ([8c9273f](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/8c9273ff625ca8897d27af7bcb9e5d177a3a45c4)) + +### [4.0.2](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v4.0.1...v4.0.2) (2022-06-20) + + +### Bug Fixes + +* Complete example EC2 instance cluster joining issue ([#64](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/64)) ([81a7a56](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/81a7a560ab976c7c4b983355de4b97af3637734a)) + +### [4.0.1](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v4.0.0...v4.0.1) (2022-06-11) + + +### Bug Fixes + +* Add empty map to `execute_command_configuration` to avoid plugin crash from interface conversion ([#62](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/62)) ([1669236](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/16692361dfa3bd1506cea3e893f484790bab5c8a)) + +## [4.0.0](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v3.5.0...v4.0.0) (2022-06-08) + + +### âš  BREAKING CHANGES + +* Upgrade module to include capacity providers and bump minimum supported versions (#60) + +### Features + +* Upgrade module to include capacity providers and bump minimum supported versions ([#60](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/60)) ([7a41657](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/7a41657bd07d42b73757863266b6ea68710c36ce)) + +## [3.5.0](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v3.4.1...v3.5.0) (2022-03-18) + + +### Features + +* Add aws_ecs_cluster_capacity_providers resource ([#55](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/55)) ([bff70b3](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/bff70b384a337ed0a5f2254c08cd937c7a4ba90c)) + +## [3.4.1](https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v3.4.0...v3.4.1) (2021-11-22) + + +### Bug Fixes + +* update CI/CD process to enable auto-release workflow ([#51](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/51)) ([c9e282b](https://github.com/terraform-aws-modules/terraform-aws-ecs/commit/c9e282b6a6d65c57ff29e34a0bed4c06ac1dbabd)) + + +## [v3.4.0] - 2021-09-07 + +- Drop support for Terraform 0.12 +- feat: Add tags to aws_iam_instance_profile ([#49](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/49)) + + + +## [v3.3.0] - 2021-06-28 + +- fix: Complete ECS example (IAM role not configured in ASG) ([#45](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/45)) + + + +## [v3.2.0] - 2021-06-20 + +- feat: Add GovCloud support ([#44](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/44)) + + + +## [v3.1.0] - 2021-05-07 + +- chore: Fixed code in example ([#41](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/41)) +- chore: update CI/CD to use stable `terraform-docs` release artifact and discoverable Apache2.0 license ([#40](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/40)) + + + +## [v3.0.0] - 2021-04-26 + +- feat: Shorten outputs (removing this_) ([#39](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/39)) + + + +## [v2.9.0] - 2021-04-11 + +- feat: Add this_iam_instance_profile_arn output ([#38](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/38)) +- chore: update documentation and pin `terraform_docs` version to avoid future changes ([#36](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/36)) +- fix: correct documentation based on update by `terraform_docs` ([#35](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/35)) +- chore: add ci-cd workflow for pre-commit checks ([#34](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/34)) + + + +## [v2.8.0] - 2021-02-20 + +- chore: update documentation based on latest `terraform-docs` which includes module and resource sections ([#33](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/33)) + + + +## [v2.7.0] - 2021-01-30 + +- fix: Fixed no capacity providers with a weight value greater than 0 error message ([#30](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/30)) + + + +## [v2.6.0] - 2021-01-26 + +- fix: Converting type of `default_capacity_provider_strategy` from map to list ([#28](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/28)) + + + +## [v2.5.0] - 2020-11-09 + +- feat: Added capacity providers options to ECS cluster ([#25](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/25)) +- feat: add tags to ECS instance profile role ([#21](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/21)) + + + +## [v2.4.0] - 2020-10-06 + +- feat: Added IAM role id to outputs ([#13](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/13)) + + + +## [v2.3.0] - 2020-06-29 + +- feat: Add container insights ([#10](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/10)) + + + +## [v2.2.0] - 2020-06-23 + +- fix: make the example workable ([#23](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/23)) + + + +## [v2.1.0] - 2020-06-23 + +- fix: Remove the dependency of hard coded region and availability zones ([#22](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/22)) + + + +## [v2.0.0] - 2019-06-09 + +- Updated module to support Terraform 0.12 ([#8](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/8)) +- Fixed formatting +- Updated Terraform 0.12 + + + +## [v1.4.0] - 2019-06-09 + +- Added changelog + + + +## [v1.3.0] - 2019-03-08 + +- Add tag support for ECS module + + + +## [v1.2.0] - 2019-03-05 + +- Updated pre-commit +- Added cluster name to outputs + + + +## [v1.1.0] - 2019-01-22 + +- Run pre-commit -a +- Add ARN output to README +- Add arn output +- Fix typos + + + +## [v1.0.0] - 2018-05-20 + +- Updated README.md +- Added pre-commit hooks with docs +- Use this_ in the outputs +- Fix output when create cluster is false +- Move ec2-instances to main.tf in the example for easier reading +- Add link to examples from the readme +- Remove fixed versions from other dependencies +- Use _ instead of -in the resource name +- Call the resource 'this' +- Fix typo +- Remove version: need to be able to run examples using latest automatically +- Create only ECS resources nothing more +- Adding EC2 instances +- Update all to newest version +- Add infrastructure to the example +- Create ECS cluster + + + +## v0.0.1 - 2017-09-26 + +- Initial commit +- Initial commit + + +[Unreleased]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v3.4.0...HEAD +[v3.4.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v3.3.0...v3.4.0 +[v3.3.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v3.2.0...v3.3.0 +[v3.2.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v3.1.0...v3.2.0 +[v3.1.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v3.0.0...v3.1.0 +[v3.0.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v2.9.0...v3.0.0 +[v2.9.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v2.8.0...v2.9.0 +[v2.8.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v2.7.0...v2.8.0 +[v2.7.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v2.6.0...v2.7.0 +[v2.6.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v2.5.0...v2.6.0 +[v2.5.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v2.4.0...v2.5.0 +[v2.4.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v2.3.0...v2.4.0 +[v2.3.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v2.2.0...v2.3.0 +[v2.2.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v2.1.0...v2.2.0 +[v2.1.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v2.0.0...v2.1.0 +[v2.0.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v1.4.0...v2.0.0 +[v1.4.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v1.3.0...v1.4.0 +[v1.3.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v1.2.0...v1.3.0 +[v1.2.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v1.1.0...v1.2.0 +[v1.1.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v1.0.0...v1.1.0 +[v1.0.0]: https://github.com/terraform-aws-modules/terraform-aws-ecs/compare/v0.0.1...v1.0.0 diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/LICENSE b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/LICENSE new file mode 100644 index 000000000..d9a10c0d8 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/README.md b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/README.md new file mode 100644 index 000000000..8e20fbc04 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/README.md @@ -0,0 +1,236 @@ +# AWS ECS Terraform module + +Terraform module which creates ECS (Elastic Container Service) resources on AWS. + +[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) + +## Available Features + +- ECS cluster w/ Fargate or EC2 Auto Scaling capacity providers +- ECS Service w/ task definition, task set, and container definition support +- Separate sub-modules or integrated module for ECS cluster and service + +For more details see the [design doc](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/docs/README.md) + +## Usage + +This project supports creating resources through individual sub-modules, or through a single module that creates both the cluster and service resources. See the respective sub-module directory for more details and example usage. + +### Integrated Cluster w/ Services + +```hcl +module "ecs" { + source = "terraform-aws-modules/ecs/aws" + + cluster_name = "ecs-integrated" + + cluster_configuration = { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + cloud_watch_log_group_name = "/aws/ecs/aws-ec2" + } + } + } + + fargate_capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = 50 + } + } + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = 50 + } + } + } + + services = { + ecsdemo-frontend = { + cpu = 1024 + memory = 4096 + + # Container definition(s) + container_definitions = { + + fluent-bit = { + cpu = 512 + memory = 1024 + essential = true + image = "906394416424.dkr.ecr.us-west-2.amazonaws.com/aws-for-fluent-bit:stable" + firelens_configuration = { + type = "fluentbit" + } + memory_reservation = 50 + } + + ecs-sample = { + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = "ecs-sample" + containerPort = 80 + protocol = "tcp" + } + ] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + dependencies = [{ + containerName = "fluent-bit" + condition = "START" + }] + + enable_cloudwatch_logging = false + log_configuration = { + logDriver = "awsfirelens" + options = { + Name = "firehose" + region = "eu-west-1" + delivery_stream = "my-stream" + log-driver-buffer-limit = "2097152" + } + } + memory_reservation = 100 + } + } + + service_connect_configuration = { + namespace = "example" + service = { + client_alias = { + port = 80 + dns_name = "ecs-sample" + } + port_name = "ecs-sample" + discovery_name = "ecs-sample" + } + } + + load_balancer = { + service = { + target_group_arn = "arn:aws:elasticloadbalancing:eu-west-1:1234567890:targetgroup/bluegreentarget1/209a844cd01825a4" + container_name = "ecs-sample" + container_port = 80 + } + } + + subnet_ids = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] + security_group_rules = { + alb_ingress_3000 = { + type = "ingress" + from_port = 80 + to_port = 80 + protocol = "tcp" + description = "Service port" + source_security_group_id = "sg-12345678" + } + egress_all = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + } + } + } + + tags = { + Environment = "Development" + Project = "Example" + } +} +``` + +## Examples + +- [ECS Cluster Complete](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) +- [ECS Cluster w/ EC2 Autoscaling Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/ec2-autoscaling) +- [ECS Cluster w/ Fargate Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/fargate) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.66.1 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [cluster](#module\_cluster) | ./modules/cluster | n/a | +| [service](#module\_service) | ./modules/service | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [autoscaling\_capacity\_providers](#input\_autoscaling\_capacity\_providers) | Map of autoscaling capacity provider definitions to create for the cluster | `any` | `{}` | no | +| [cloudwatch\_log\_group\_kms\_key\_id](#input\_cloudwatch\_log\_group\_kms\_key\_id) | If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html) | `string` | `null` | no | +| [cloudwatch\_log\_group\_name](#input\_cloudwatch\_log\_group\_name) | Custom name of CloudWatch Log Group for ECS cluster | `string` | `null` | no | +| [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | Number of days to retain log events | `number` | `90` | no | +| [cloudwatch\_log\_group\_tags](#input\_cloudwatch\_log\_group\_tags) | A map of additional tags to add to the log group created | `map(string)` | `{}` | no | +| [cluster\_configuration](#input\_cluster\_configuration) | The execute command configuration for the cluster | `any` | `{}` | no | +| [cluster\_name](#input\_cluster\_name) | Name of the cluster (up to 255 letters, numbers, hyphens, and underscores) | `string` | `""` | no | +| [cluster\_service\_connect\_defaults](#input\_cluster\_service\_connect\_defaults) | Configures a default Service Connect namespace | `map(string)` | `{}` | no | +| [cluster\_settings](#input\_cluster\_settings) | List of configuration block(s) with cluster settings. For example, this can be used to enable CloudWatch Container Insights for a cluster | `any` |
[
{
"name": "containerInsights",
"value": "enabled"
}
]
| no | +| [cluster\_tags](#input\_cluster\_tags) | A map of additional tags to add to the cluster | `map(string)` | `{}` | no | +| [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no | +| [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Determines whether a log group is created by this module for the cluster logs. If not, AWS will automatically create one if logging is enabled | `bool` | `true` | no | +| [create\_task\_exec\_iam\_role](#input\_create\_task\_exec\_iam\_role) | Determines whether the ECS task definition IAM role should be created | `bool` | `false` | no | +| [create\_task\_exec\_policy](#input\_create\_task\_exec\_policy) | Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters | `bool` | `true` | no | +| [default\_capacity\_provider\_use\_fargate](#input\_default\_capacity\_provider\_use\_fargate) | Determines whether to use Fargate or autoscaling for default capacity provider strategy | `bool` | `true` | no | +| [fargate\_capacity\_providers](#input\_fargate\_capacity\_providers) | Map of Fargate capacity provider definitions to use for the cluster | `any` | `{}` | no | +| [services](#input\_services) | Map of service definitions to create | `any` | `{}` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_description](#input\_task\_exec\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [task\_exec\_iam\_role\_name](#input\_task\_exec\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [task\_exec\_iam\_role\_path](#input\_task\_exec\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [task\_exec\_iam\_role\_permissions\_boundary](#input\_task\_exec\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [task\_exec\_iam\_role\_policies](#input\_task\_exec\_iam\_role\_policies) | Map of IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_tags](#input\_task\_exec\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_use\_name\_prefix](#input\_task\_exec\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [task\_exec\_iam\_statements](#input\_task\_exec\_iam\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [task\_exec\_secret\_arns](#input\_task\_exec\_secret\_arns) | List of SecretsManager secret ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:secretsmanager:*:*:secret:*"
]
| no | +| [task\_exec\_ssm\_param\_arns](#input\_task\_exec\_ssm\_param\_arns) | List of SSM parameter ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/*"
]
| no | + +## Outputs + +| Name | Description | +|------|-------------| +| [autoscaling\_capacity\_providers](#output\_autoscaling\_capacity\_providers) | Map of autoscaling capacity providers created and their attributes | +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | ARN of CloudWatch log group created | +| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of CloudWatch log group created | +| [cluster\_arn](#output\_cluster\_arn) | ARN that identifies the cluster | +| [cluster\_capacity\_providers](#output\_cluster\_capacity\_providers) | Map of cluster capacity providers attributes | +| [cluster\_id](#output\_cluster\_id) | ID that identifies the cluster | +| [cluster\_name](#output\_cluster\_name) | Name that identifies the cluster | +| [services](#output\_services) | Map of services created and their attributes | +| [task\_exec\_iam\_role\_arn](#output\_task\_exec\_iam\_role\_arn) | Task execution IAM role ARN | +| [task\_exec\_iam\_role\_name](#output\_task\_exec\_iam\_role\_name) | Task execution IAM role name | +| [task\_exec\_iam\_role\_unique\_id](#output\_task\_exec\_iam\_role\_unique\_id) | Stable and unique string identifying the task execution IAM role | + + +## Authors + +Module is maintained by [Anton Babenko](https://github.com/antonbabenko) with help from [these awesome contributors](https://github.com/terraform-aws-modules/terraform-aws-ecs/graphs/contributors). + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/UPGRADE-4.0.md b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/UPGRADE-4.0.md new file mode 100644 index 000000000..a1bf05df6 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/UPGRADE-4.0.md @@ -0,0 +1,207 @@ +# Upgrade from v3.x to v4.x + +Please consult the `examples` directory for reference example configurations. If you find a bug, please open an issue with supporting configuration to reproduce. + +## List of backwards incompatible changes + +- Minimum supported version of Terraform AWS provider updated to v4.6 to support the latest resources utilized +- Minimum supported version of Terraform updated to v1.0 +- `ecs-instance-profile` sub-module has been removed; this functionality is available through the [`terraform-aws-modules/terraform-aws-autoscaling`](https://github.com/terraform-aws-modules/terraform-aws-autoscaling) module starting with version [v6.5.0](https://github.com/terraform-aws-modules/terraform-aws-autoscaling/pull/194). Please see the [`examples/complete`](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) example for a demonstration on how to use and integrate with the `terraform-aws-autoscaling` module. +- The `container_insights` and `capacity_providers` variables have been replaced by new variables - see below for more details + +## Additional changes + +### Added + +- Support for `aws_ecs_capacity_provider` has been added to the module + +### Modified + +- The `container_insights` variable has been replaced with the `cluster_settings` variable which allows users to enable/disable container insights and also allows for not specifying at all for regions where container insights is currently not supported. +- The `capacity_providers` variable has been replaced with `fargate_capacity_providers`and `autoscaling_capacity_providers`. This allows users to specify either Fargate based capacity providers, EC2 AutoScaling Group capacity providers, or both. +- Previously `capacity_providers` and `default_capacity_provider_strategy` usage looked like: +```hcl + capacity_providers = ["FARGATE", "FARGATE_SPOT"] + + default_capacity_provider_strategy = [{ + capacity_provider = "FARGATE" + weight = 50 + base = 20 + }, { + capacity_provider = "FARGATE_SPOT" + weight = 50 + }] +``` +Where the current equivalent now looks like: +```hcl + fargate_capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = 50 + base = 20 + } + } + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = 50 + } + } + } +``` +- Previously `capacity_providers` accepted the name of an AutoScaling Group created externally; this is now replaced by the usage of `autoscaling_capacity_providers` which incorporates the usage of the newly added support for `aws_ecs_capacity_provider` + +### Removed + +- `ecs-instance-profile` sub-module has been removed; this functionality is available through the [`terraform-aws-modules/terraform-aws-autoscaling`](https://github.com/terraform-aws-modules/terraform-aws-autoscaling) module starting with version [v6.5.0](https://github.com/terraform-aws-modules/terraform-aws-autoscaling/pull/194). Please see the [`examples/complete`](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) example for a demonstration on how to use and integrate with the `terraform-aws-autoscaling` module. + +### Variable and output changes + +1. Removed variables: + + - `default_capacity_provider_strategy` is now incorporated into the `fargate_capacity_providers` and `autoscaling_capacity_providers` variables. + +2. Renamed variables: + + - `create_ecs` -> `create` + - `name` -> `cluster_name` + +3. Added variables: + + - `cluster_configuration` has been added under a dynamic block with all current attributes supported + +4. Removed outputs: + + - `ecs_cluster_name` + +5. Renamed outputs: + + - `ecs_cluster_id` -> `cluster_id` + - `ecs_cluster_arn` -> `cluster_arn` + +6. Added outputs: + + - `cluster_capacity_providers` + - `autoscaling_capacity_providers` + +## Upgrade Migrations + +### Before v3.x Example + +```hcl +module "ecs" { + source = "terraform-aws-modules/ecs/aws" + version = "3.5.0" + + name = "example" + container_insights = true + + capacity_providers = ["FARGATE", "FARGATE_SPOT", aws_ecs_capacity_provider.prov1.name] + + default_capacity_provider_strategy = [{ + capacity_provider = aws_ecs_capacity_provider.prov1.name + weight = "1" + }] +} + +module "ec2_profile" { + source = "terraform-aws-modules/ecs/aws//modules/ecs-instance-profile" + version = "3.5.0" + + name = local.name +} + +resource "aws_ecs_capacity_provider" "prov1" { + name = "prov1" + + auto_scaling_group_provider { + auto_scaling_group_arn = module.autoscaling.autoscaling_group_arn + } +} +``` + +### After v4.x Example + +```hcl +module "ecs" { + source = "terraform-aws-modules/ecs/aws" + version = "4.0.0" + + cluster_name = "example" + + fargate_capacity_providers = { + FARGATE = {} + FARGATE_SPOT = {} + } + + autoscaling_capacity_providers = { + prov1 = { + auto_scaling_group_arn = module.autoscaling.autoscaling_group_arn + default_capacity_provider_strategy = { + weight = 1 + } + } + } +} + +module "ec2_profile" { + source = "terraform-aws-modules/ecs/aws//modules/ecs-instance-profile" + # Users can pin and stay on v3.5.0 until they able to use the IAM instance + # profile provided through the autoscaling group module + version = "3.5.0" + + name = "example +} +``` + +### Diff of Before vs After + +```diff +- resource "aws_ecs_capacity_provider" "prov1" { +- name = "prov1" +- +- auto_scaling_group_provider { +- auto_scaling_group_arn = module.autoscaling.autoscaling_group_arn +- } +- } + + module "ecs" { + source = "terraform-aws-modules/ecs/aws" +- version = "3.5.0" ++ version = "4.0.0" + +- name = "example" ++ cluster_name = "example" + +- container_insights = true ++ # On by default now + +- capacity_providers = ["FARGATE", "FARGATE_SPOT", aws_ecs_capacity_provider.prov1.name] +- default_capacity_provider_strategy = [{ +- capacity_provider = aws_ecs_capacity_provider.prov1.name +- weight = "1" +- }] + ++ fargate_capacity_providers = { ++ FARGATE = {} ++ FARGATE_SPOT = {} ++ } + ++ autoscaling_capacity_providers = { ++ prov1 = { ++ auto_scaling_group_arn = module.autoscaling.autoscaling_group_arn ++ default_capacity_provider_strategy = { ++ weight = 1 ++ } ++ } ++ } +} +``` + +### State Move Commands + +In conjunction with the changes above, users can elect to move their external capacity provider(s) under this module using the following move command. Command is shown using the values from the example shown above, please update to suit your configuration names: + +```sh +# Cluster +terraform state mv 'aws_ecs_capacity_provider.prov1' 'module.ecs.aws_ecs_capacity_provider.this["prov1"]' +``` diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/docs/README.md b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/docs/README.md new file mode 100644 index 000000000..80f0b9f9e --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/docs/README.md @@ -0,0 +1,347 @@ +# Design + +This document is intended to describe the decisions and assumptions that went into the current design of the ECS module(s). Various concepts of Amazon ECS are covered here to help align readers with the service and its capabilities and how they influence the design provided by this project. + +

+ ECS Design +

+ +## ECS Constructs + +At a high level, an Amazon ECS cluster is comprised of a cluster, services, and tasks. The relationship between these constructs is described below: + +- A cluster may contain one or more services +- A service may contain one or more task definitions/sets +- A task may contain one to ten (max) containers + +Multiple task definition/sets per service is not a common pattern used today. Therefore, this module makes the assumption that a service will contain one task definition/set. All other relationships described above are supported by this project. + +### Cluster + +An Amazon ECS cluster is a logical grouping of compute resources which are consumed by tasks. Compute resources are provided via capacity providers - users can elect to choose an EC2 capacity provider or Fargate capacity providers (spot or on-demand). A capacity provider strategy determines how the tasks will be distributed among capacity providers. A default capacity provider strategy can be defined at cluster level. Note you **can not** mix EC2 based capacity provider with Fargate. + +### Service + +An Amazon ECS service is responsible for managing its associated tasks (via task definition/task set) - the lifecycle of its tasks, the manner in which they are provisioned, their deployment model, network connectivity, etc. As stated previously, Amazon ECS services support one or more task definitions/sets per service. However, this module assumes a service will contain only one task definition/set. Some Amazon ECS service concepts to note: + +- When using an external deployment controller, most of the configurations that are normally specified in the service definition are instead specified in the task set. (For users of this project, this is abstracted away and handled by the module). +- The service IAM role is used to manage the lifecycle of load balancer targets (register/deregister) for the service. If the service does not utilize a load balancer, this role is not required. + +### Task + +Tasks in Amazon ECS can be configured via 3 different methods: + +1. From a task definition +2. From a task set (which also requires a task Definition) +3. From the ECS RunTask API (not supported by this module, but users can use the API to deploy tasks onto cluster resources created by this project) + +Task sets are required when using an external deployment controller (i.e. - something outside of Amazon ECS will manage the lifecycle of updating tasks when changes are made). Task definitions are always required, even when using task sets. + +A task wraps one or more container definitions (up to a max of 10 container definitions per task). You can think of a task as being synonymous to a pod in Kubernetes, and task definition/set being synonymous with a pod spec in Kubernetes. + +Within the construct of tasks, there are two different IAM roles that can be configured: + +1. The task execution IAM role provides permissions for Amazon ECS to access AWS resources specified within the task definition/set. When tasks are created, if a task requires access to SSM parameters and/or secrets in SecretsManager as defined in the task definition/set, the task execution role is used by Amazon ECS to retrieve those values and provide them to the tasks as environment variables. Task execution role permissions are not accessible by the containers within the task during runtime; they are only used during task creation. +2. The tasks IAM role provides permissions for the containers within the task to access AWS resources at runtime. If the application running within the container requires access to AWS resources such as retrieving files from S3 or connecting to RDS using IAM Auth, the tasks IAM role is used to provide those permissions. You can think of the tasks IAM role as being similar to the IAM instance profile used on EC2 instances, or IAM role for service accounts used by pods on Amazon EKS. + +## Module Constructs + +The following constructs are supported by this project. Please see their respective details below for more information on the design considerations, assumptions, and limitations. + +### Cluster + +The cluster sub-module creates an Amazon ECS cluster. With it, users are able to: + +- Create an Amazon ECS cluster +- Enable EC2, Fargate on-demand, and/or Fargate spot capacity providers for the cluster +- Create and manage a CloudWatch log group for the cluster +- Create a task execution IAM role with the ability to extend/add permissions as necessary + +When opting for EC2 capacity provider(s), users can utilize the [`terraform-aws-autoscaling` module](https://github.com/terraform-aws-modules/terraform-aws-autoscaling) to create the necessary autoscaling groups and associate them with the cluster. See the [`ec2-autoscaling` example](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/ec2-autoscaling) which demonstrates this configuration. + +This module supports creating a task execution IAM role in two different ways to support two common patterns used by Amazon ECS users: + +1. Users can create one task execution IAM role and re-use, or share, this role across all services within the cluster. This pattern is commonly used when a cluster is used by one team (each team gets their own cluster). When using this pattern, users can elect to create the task execution IAM role from within the cluster module. +2. Users can create one task execution IAM role per service. This pattern is commonly used when a cluster is used by multiple teams (each team gets their own service within the cluster), and access to SSM parameters and/or secrets need to be scoped per service and therefore, per task execution IAM role. When using this pattern, users can elect to create the task execution IAM role from within the service module. + +

+ ECS Cluster +

+ +### Service + +The service sub-module creates one service that can be deployed onto a cluster. The service sub-module allows users to: + +- Create an Amazon ECS service that ignores `desired_count`. This is intended for use when deploying task definition and container definition changes via Terraform +- Create an Amazon ECS service that ignores `desired_count` and `task_definition`, and `load_balancer`. This is intended to support a continuous deployment process that is responsible for updating the `image` and therefore the `task_definition` and `container_definition` while avoiding conflicts with Terraform. +- Amazon ECS task resources with the various configurations detailed below under [ECS Task](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/docs/README.md#ecs-task) + +Since Terraform does not support variables within `lifecycle {}` blocks, its not possible to allow users to dynamically select which arguments they wish to ignore within the resources defined in the modules. Therefore, any arguments that should be ignored are statically set within the module definition. To somewhat mimic the behavior of allowing users to opt in/out of ignoring certain arguments, the module supports two different service definitions; one that ignores the `desired_count`, and one that ignores `desired_count`, `task_definition` and `load_balancer`. The motivation and reasoning for these ignored argument configurations is detailed below: + +- `desired_count` is always ignored by the service module. It is very common to have autoscaling enabled for Amazon ECS services, allowing the number of tasks to scale based on the workload requirements. The scaling is managed via the `desired_count` that is managed by application auto scaling. This would directly conflict with Terraform if it was allowed to manage the `desired_count` as well. In addition, users have the ability to disable auto scaling if it does not suit their workload. In this case, the `desired_count` would be initially set by Terraform, and any further changes would need to be managed separately (outside of the service module). Users can make changes to the desired count of the service through the AWS console, AWS CLI, or AWS SDKs. One example workaround using Terraform is provided below, similar to the [EKS equivalent](https://github.com/bryantbiggs/eks-desired-size-hack): + + ```hcl + resource "null_resource" "update_desired_count" { + triggers = { + # Changes to this value will trigger the API call execution below + desired_count = 3 + } + + provisioner "local-exec" { + interpreter = ["/bin/bash", "-c"] + + # Note: this requires the awscli to be installed locally where Terraform is executed + command = <<-EOT + aws ecs update-service \ + --cluster ${module.ecs.cluster_name} \ + --service ${module.ecs_service.name} \ + --desired-count ${null_resource.update_desired_count.triggers.desired_count} + EOT + } + } + ``` + +- In addition to always ignoring `desired_count`, users can elect to ignore changes to task definitions by setting `ignore_task_definition_changes` to `true`. (Note: because of the aforementioned manner in which this psuedo-dynamic ignore change is being managed, changing this value after service creation will cause the entire service to be re-created. Change with caution!) This is primarily intended to support the external deployment controller as well as continuous delivery patterns where an [external process is changing the task definition](https://github.com/aws-actions/amazon-ecs-deploy-task-definition). The entire task definition argument is ignored due to the fact that any changes within the task definition, including changes to the container definition(s), results in a new task definition revision. As an alternative, this module does provide a work around that would support an external party making changes to something like the `image` of the container definition. In a scenario where there the service, task definition, and container definition are all managed by Terraform, the following configuration could be used to allow an external party to change the `image` of the container definition without conflicting with Terraform, provided that the external party is also updating the image tag in a shared location that can be retrieved by Terraform (here, we are using SSM Parameter Store): + + ```hcl + data "aws_ssm_parameter" "app_image_tag" { + name = "/my-app/image-tag" + } + + data "aws_ecr_repository" "app" { + name = "my-app-repository" + } + + data "aws_ecr_image" "app" { + repository_name = data.aws_ecr_repository.app.name + image_tag = data.aws_ssm_parameter.app_image_tag.value + } + + module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + + # ... omitted for brevity + + container_definitions = { + default = { + name = data.aws_ecr_repository.app.name + image = "${data.aws_ecr_repository.app.repository_url}@${data.aws_ecr_image.app.id}" + # ... + } + } + + # This is the default, but just to clarify on this example that we are NOT ignoring + # task definition changes, and still allowing an external party to modify the image/tag + # without conflicting with Terraform + ignore_task_definition_changes = false + } + ``` + +This could be expanded further to include the entire container definitions argument, provided that external party making changes to the container definitions provides a copy that Terraform can retrieve and use when making updates to task definitions. This is possible due to the use of the `aws_ecs_task_definition` data source in the module - the data source retrieves the task definition currently defined in AWS. Using the `max()` function in Terraform, we can get the latest version via `max(aws_ecs_task_definition.this[0].revision, data.aws_ecs_task_definition.this[0].revision)` and use that as the `task_definition` value through reconstructing the `family:revision` format such as `"${aws_ecs_task_definition.this[0].family}:${local.max_task_def_revision}"`. With this work around, any changes made by Terraform would result in a new task definition revision resulting in Terraform updating the revision and deploying that change into the cluster. Likewise, any external party making changes to the task definition will increment the revision and deploy that into the cluster. Provided that Terraform can refer to the same values of the arguments changed by the external party, Terraform will be able to have a complete view of the current status and only make changes when necessary, without conflicting with the external party. + +

+ ECS Service +

+ +- When using the above `ignore_task_definition_changes` setting, changes to the `load_balancer` argument are also ignored. This is intended to support the use of [Blue/Green deployment with CodeDeploy](https://docs.aws.amazon.com/AmazonECS/latest/userguide/deployment-type-bluegreen.html) which changes the the service's load balancer configuration. (Note: the ignored changes to the `load_balancer` were added after the fact which is why the variable name does not reflect this behavior. In a future major release, this variable will be updated to better reflect its behavior) + +```hcl + module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + + # ... omitted for brevity + + ignore_task_definition_changes = true + } + + resource "aws_lb_target_group" "this" { + for_each = { + blue = {}, + green = {} + } + + name = each.key + + # ... omitted for brevity + } + + resource "aws_codedeploy_app" "this" { + name = "my-app" + compute_platform = "ECS" + } + + resource "aws_codedeploy_deployment_group" "this" { + deployment_group_name = "my-deployment-group" + app_name = aws_codedeploy_app.this.name + + deployment_config_name = "CodeDeployDefault.ECSAllAtOnce" + + deployment_style { + deployment_option = "WITH_TRAFFIC_CONTROL" + deployment_type = "BLUE_GREEN" + } + + # ... omitted for brevity + + load_balancer_info { + target_group_pair_info { + prod_traffic_route { + listener_arns = ["my-listener-arn"] + } + + target_group { + name = aws_lb_target_group.this["blue"].name + } + + target_group { + name = aws_lb_target_group.this["green"].name + } + } + } + } +``` + +### Task + +ECS tasks are the byproduct of task definitions and task sets. In addition to what has been described above, the service module supports the following task level configurations: + +- Create task definition with support for n-number of container definitions (min 1, max 10 per AWS API) +- Create task execution IAM role and associated permissions. This is one of two routes previously described for creating a task execution role, where creating it at the service level is primarily intended to ensure parameters and secrets are only accessible by the tasks created in the respective service, and not shared across all services. +- Create tasks IAM role & permissions used by the containers during runtime +- Create task set that ignores `scale`; this is equivalent to the service and its ignored `desired_count` +- Create task set that ignores `scale` and `task_definition`; again, equivalent to the service and its ignored `desired_count` and `ignore_task_definition_changes` +- Create an application autoscaling target, policy, and schedule action to autoscale the number of tasks + +The same workaround described above where an external party can modify the task definition without conflicting with Terraform can be used here as well since task sets require a task definition and the module's implementation uses the same one throughout. + +Application auto scaling is enabled by default and can be disabled by setting `enable_autoscaling` to `false`. See the mentioned work around above for adjusting the `desired_count` of the service to adjust the number of tasks. The equivalent for task set using an external deployment controller is provided below: + + ```hcl + resource "null_resource" "update_scale" { + triggers = { + # Changes to this value will trigger the API call execution below + scale = "value=50,unit=PERCENT" + } + + provisioner "local-exec" { + interpreter = ["/bin/bash", "-c"] + + # Note: this requires the awscli to be installed locally where Terraform is executed + command = <<-EOT + aws ecs update-task-set \ + --cluster ${module.ecs.cluster_name} \ + --service ${module.ecs_service.name} \ + --task-set ${module.ecs_service.task_set_arn} \ + --scale ${null_resource.update_scale.triggers.scale} + EOT + } + } + ``` + +

+ ECS Task +

+ +#### Container Definition + +The container definition sub-module provided by the project is intended to be used as a building block for the container definitions argument of the task definition and to fill the gap [until a native Terraform resource is provided](https://github.com/hashicorp/terraform-provider-aws/issues/17988) for container definitions. + +Within a container definition, there are primarily 2 ways in which logs are created/managed: + +1. Via Cloudwatch logs: When users provide a name to `awslogs-group`, if the CloudWatch log group does not exists, ECS will create it. This means it is created outside of IaC and therefore will not be deleted when the task/container definition is deleted, nor tagged, etc. +2. Via Firelens: Firelens allows users to provide a configuration that will forward logs to various locations, including 3rd party locations. When using Firelens though, users will need to add a FluentBit sidecar to forward logs to the Firelens service which will send logs to the final destination based on the `firelensConfiguration` + +In this module we aim to provide support for both, but with the addition of affording users the ability to manage the Cloudwatch log group through Terraform. This is the similar scenario we face with other modules as well - RDS, Lambda, EKS - that will automatically create log groups when logging is enabled. This sub-module provides support for creating the Cloudwatch log groups so that users can tag the log groups, set the retention period, , encrypt the log groups with KMS, ensure logs are destroyed when the resources are, etc. This is the reason why the Cloudwatch log resource is provided in this sub-module; to allow users to control the log group through Terraform/IaC + +The default behavior of the container definition module is to create the CloudWatch log group on behalf of users to ensure it can be fully managed through Terraform. This leads to the following scenarios for logging: + +1. Disable logging entirely + + ```hcl + module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + + # ... omitted for brevity + + container_definitions = { + default = { + enable_cloudwatch_logging = false + # ... + } + } + } + ``` + +2. Use CloudWatch logging - opt out of Terraform managing the Cloudwatch log group and instead ECS will create the log group: + + ```hcl + module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + + # ... omitted for brevity + + container_definitions = { + default = { + create_cloudwatch_log_group = false + # ... + } + } + } + ``` + +3. [Default] Use CloudWatch logging - Let Terraform manage the Cloudwatch log group: + + ```hcl + module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + + # ... omitted for brevity + + container_definitions = { + default = { + # ... + } + } + } + ``` + +4. Use Firelens for log forwarding: + + ```hcl + data "aws_ssm_parameter" "fluentbit" { + name = "/aws/service/aws-for-fluent-bit/stable" + } + + module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + + # ... omitted for brevity + + container_definitions = { + # FluentBit sidecar is required for Firelens + fluent-bit = { + image = data.aws_ssm_parameter.fluentbit.value + firelens_configuration = { + type = "fluentbit" + } + # ... + } + + default = { + dependencies = [{ + containerName = "fluent-bit" + condition = "START" + }] + + enable_cloudwatch_logging = false + log_configuration = { + logDriver = "awsfirelens" + options = { + # ... + } + } + # ... + } + } + } + ``` diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/docs/images/cluster.png b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/docs/images/cluster.png new file mode 100644 index 000000000..baa5862e9 Binary files /dev/null and b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/docs/images/cluster.png differ diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/docs/images/complete.png b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/docs/images/complete.png new file mode 100644 index 000000000..131a14b78 Binary files /dev/null and b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/docs/images/complete.png differ diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/docs/images/service.png b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/docs/images/service.png new file mode 100644 index 000000000..a22c90c1a Binary files /dev/null and b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/docs/images/service.png differ diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/docs/images/task.png b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/docs/images/task.png new file mode 100644 index 000000000..c35a97eb6 Binary files /dev/null and b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/docs/images/task.png differ diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/README.md b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/README.md new file mode 100644 index 000000000..f417c0adc --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/README.md @@ -0,0 +1,8 @@ +# Examples + +Please note - the examples provided serve two primary means: + +1. Show users working examples of the various ways in which the module can be configured and features supported +2. A means of testing/validating module changes + +Please do not mistake the examples provided as "best practices". It is up to users to consult the AWS service documentation for best practices, usage recommendations, etc. diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/complete/README.md b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/complete/README.md new file mode 100644 index 000000000..3f105953f --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/complete/README.md @@ -0,0 +1,75 @@ +# ECS Cluster Complete + +Configuration in this directory creates: + +- ECS cluster using Fargate (on-demand and spot) capacity providers +- Example ECS service that utilizes + - AWS Firelens using FluentBit sidecar container definition + - Service connect configuration + - Load balancer target group attachment + - Security group for access to the example service + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which will incur monetary charges on your AWS bill. Run `terraform destroy` when you no longer need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.66.1 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.66.1 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [alb](#module\_alb) | terraform-aws-modules/alb/aws | ~> 9.0 | +| [ecs](#module\_ecs) | ../../ | n/a | +| [ecs\_cluster\_disabled](#module\_ecs\_cluster\_disabled) | ../../modules/cluster | n/a | +| [ecs\_disabled](#module\_ecs\_disabled) | ../../ | n/a | +| [service\_disabled](#module\_service\_disabled) | ../../modules/service | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_service_discovery_http_namespace.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/service_discovery_http_namespace) | resource | +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | +| [aws_ssm_parameter.fluentbit](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [cluster\_arn](#output\_cluster\_arn) | ARN that identifies the cluster | +| [cluster\_autoscaling\_capacity\_providers](#output\_cluster\_autoscaling\_capacity\_providers) | Map of capacity providers created and their attributes | +| [cluster\_capacity\_providers](#output\_cluster\_capacity\_providers) | Map of cluster capacity providers attributes | +| [cluster\_id](#output\_cluster\_id) | ID that identifies the cluster | +| [cluster\_name](#output\_cluster\_name) | Name that identifies the cluster | +| [services](#output\_services) | Map of services created and their attributes | + + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/complete/main.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/complete/main.tf new file mode 100644 index 000000000..b7353bbd8 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/complete/main.tf @@ -0,0 +1,281 @@ +provider "aws" { + region = local.region +} + +data "aws_availability_zones" "available" {} + +locals { + region = "eu-west-1" + name = "ex-${basename(path.cwd)}" + + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 3) + + container_name = "ecsdemo-frontend" + container_port = 3000 + + tags = { + Name = local.name + Example = local.name + Repository = "https://github.com/terraform-aws-modules/terraform-aws-ecs" + } +} + +################################################################################ +# Cluster +################################################################################ + +module "ecs" { + source = "../../" + + cluster_name = local.name + + # Capacity provider + fargate_capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = 50 + base = 20 + } + } + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = 50 + } + } + } + + services = { + ecsdemo-frontend = { + cpu = 1024 + memory = 4096 + + # Container definition(s) + container_definitions = { + + fluent-bit = { + cpu = 512 + memory = 1024 + essential = true + image = nonsensitive(data.aws_ssm_parameter.fluentbit.value) + firelens_configuration = { + type = "fluentbit" + } + memory_reservation = 50 + } + + (local.container_name) = { + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + + health_check = { + command = ["CMD-SHELL", "curl -f http://localhost:${local.container_port}/health || exit 1"] + } + + port_mappings = [ + { + name = local.container_name + containerPort = local.container_port + hostPort = local.container_port + protocol = "tcp" + } + ] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + dependencies = [{ + containerName = "fluent-bit" + condition = "START" + }] + + enable_cloudwatch_logging = false + log_configuration = { + logDriver = "awsfirelens" + options = { + Name = "firehose" + region = local.region + delivery_stream = "my-stream" + log-driver-buffer-limit = "2097152" + } + } + memory_reservation = 100 + } + } + + service_connect_configuration = { + namespace = aws_service_discovery_http_namespace.this.arn + service = { + client_alias = { + port = local.container_port + dns_name = local.container_name + } + port_name = local.container_name + discovery_name = local.container_name + } + } + + load_balancer = { + service = { + target_group_arn = module.alb.target_groups["ex_ecs"].arn + container_name = local.container_name + container_port = local.container_port + } + } + + tasks_iam_role_name = "${local.name}-tasks" + tasks_iam_role_description = "Example tasks IAM role for ${local.name}" + tasks_iam_role_policies = { + ReadOnlyAccess = "arn:aws:iam::aws:policy/ReadOnlyAccess" + } + tasks_iam_role_statements = [ + { + actions = ["s3:List*"] + resources = ["arn:aws:s3:::*"] + } + ] + + subnet_ids = module.vpc.private_subnets + security_group_rules = { + alb_ingress_3000 = { + type = "ingress" + from_port = local.container_port + to_port = local.container_port + protocol = "tcp" + description = "Service port" + source_security_group_id = module.alb.security_group_id + } + egress_all = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + } + } + } + + tags = local.tags +} + +module "ecs_disabled" { + source = "../../" + + create = false +} + +module "ecs_cluster_disabled" { + source = "../../modules/cluster" + + create = false +} + +module "service_disabled" { + source = "../../modules/service" + + create = false +} + +################################################################################ +# Supporting Resources +################################################################################ + +data "aws_ssm_parameter" "fluentbit" { + name = "/aws/service/aws-for-fluent-bit/stable" +} + +resource "aws_service_discovery_http_namespace" "this" { + name = local.name + description = "CloudMap namespace for ${local.name}" + tags = local.tags +} + +module "alb" { + source = "terraform-aws-modules/alb/aws" + version = "~> 9.0" + + name = local.name + + load_balancer_type = "application" + + vpc_id = module.vpc.vpc_id + subnets = module.vpc.public_subnets + + # For example only + enable_deletion_protection = false + + # Security Group + security_group_ingress_rules = { + all_http = { + from_port = 80 + to_port = 80 + ip_protocol = "tcp" + cidr_ipv4 = "0.0.0.0/0" + } + } + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = module.vpc.vpc_cidr_block + } + } + + listeners = { + ex_http = { + port = 80 + protocol = "HTTP" + + forward = { + target_group_key = "ex_ecs" + } + } + } + + target_groups = { + ex_ecs = { + backend_protocol = "HTTP" + backend_port = local.container_port + target_type = "ip" + deregistration_delay = 5 + load_balancing_cross_zone_enabled = true + + health_check = { + enabled = true + healthy_threshold = 5 + interval = 30 + matcher = "200" + path = "/" + port = "traffic-port" + protocol = "HTTP" + timeout = 5 + unhealthy_threshold = 2 + } + + # Theres nothing to attach here in this definition. Instead, + # ECS will attach the IPs of the tasks to this target group + create_attachment = false + } + } + + tags = local.tags +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" + + name = local.name + cidr = local.vpc_cidr + + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] + + enable_nat_gateway = true + single_nat_gateway = true + + tags = local.tags +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/complete/outputs.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/complete/outputs.tf new file mode 100644 index 000000000..350316941 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/complete/outputs.tf @@ -0,0 +1,37 @@ +################################################################################ +# Cluster +################################################################################ + +output "cluster_arn" { + description = "ARN that identifies the cluster" + value = module.ecs.cluster_arn +} + +output "cluster_id" { + description = "ID that identifies the cluster" + value = module.ecs.cluster_id +} + +output "cluster_name" { + description = "Name that identifies the cluster" + value = module.ecs.cluster_name +} + +output "cluster_capacity_providers" { + description = "Map of cluster capacity providers attributes" + value = module.ecs.cluster_capacity_providers +} + +output "cluster_autoscaling_capacity_providers" { + description = "Map of capacity providers created and their attributes" + value = module.ecs.autoscaling_capacity_providers +} + +################################################################################ +# Service(s) +################################################################################ + +output "services" { + description = "Map of services created and their attributes" + value = module.ecs.services +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/complete/variables.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/complete/variables.tf new file mode 100644 index 000000000..e69de29bb diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/complete/versions.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/complete/versions.tf new file mode 100644 index 000000000..682191e70 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/complete/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.66.1" + } + } +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/ec2-autoscaling/README.md b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/ec2-autoscaling/README.md new file mode 100644 index 000000000..c7a04afc6 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/ec2-autoscaling/README.md @@ -0,0 +1,93 @@ +# ECS Cluster w/ EC2 Autoscaling + +Configuration in this directory creates: + +- ECS cluster using EC2 autoscaling groups +- Autoscaling groups with IAM instance profile to be used by ECS cluster +- Example ECS service that utilizes + - Mounts a host volume into the container definition + - Load balancer target group attachment + - Security group for access to the example service + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which will incur monetary charges on your AWS bill. Run `terraform destroy` when you no longer need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.66.1 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.66.1 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [alb](#module\_alb) | terraform-aws-modules/alb/aws | ~> 9.0 | +| [autoscaling](#module\_autoscaling) | terraform-aws-modules/autoscaling/aws | ~> 6.5 | +| [autoscaling\_sg](#module\_autoscaling\_sg) | terraform-aws-modules/security-group/aws | ~> 5.0 | +| [ecs\_cluster](#module\_ecs\_cluster) | ../../modules/cluster | n/a | +| [ecs\_service](#module\_ecs\_service) | ../../modules/service | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | +| [aws_ssm_parameter.ecs_optimized_ami](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [cluster\_arn](#output\_cluster\_arn) | ARN that identifies the cluster | +| [cluster\_autoscaling\_capacity\_providers](#output\_cluster\_autoscaling\_capacity\_providers) | Map of capacity providers created and their attributes | +| [cluster\_capacity\_providers](#output\_cluster\_capacity\_providers) | Map of cluster capacity providers attributes | +| [cluster\_id](#output\_cluster\_id) | ID that identifies the cluster | +| [cluster\_name](#output\_cluster\_name) | Name that identifies the cluster | +| [service\_autoscaling\_policies](#output\_service\_autoscaling\_policies) | Map of autoscaling policies and their attributes | +| [service\_autoscaling\_scheduled\_actions](#output\_service\_autoscaling\_scheduled\_actions) | Map of autoscaling scheduled actions and their attributes | +| [service\_container\_definitions](#output\_service\_container\_definitions) | Container definitions | +| [service\_iam\_role\_arn](#output\_service\_iam\_role\_arn) | Service IAM role ARN | +| [service\_iam\_role\_name](#output\_service\_iam\_role\_name) | Service IAM role name | +| [service\_iam\_role\_unique\_id](#output\_service\_iam\_role\_unique\_id) | Stable and unique string identifying the service IAM role | +| [service\_id](#output\_service\_id) | ARN that identifies the service | +| [service\_name](#output\_service\_name) | Name of the service | +| [service\_task\_definition\_arn](#output\_service\_task\_definition\_arn) | Full ARN of the Task Definition (including both `family` and `revision`) | +| [service\_task\_definition\_revision](#output\_service\_task\_definition\_revision) | Revision of the task in a particular family | +| [service\_task\_exec\_iam\_role\_arn](#output\_service\_task\_exec\_iam\_role\_arn) | Task execution IAM role ARN | +| [service\_task\_exec\_iam\_role\_name](#output\_service\_task\_exec\_iam\_role\_name) | Task execution IAM role name | +| [service\_task\_exec\_iam\_role\_unique\_id](#output\_service\_task\_exec\_iam\_role\_unique\_id) | Stable and unique string identifying the task execution IAM role | +| [service\_task\_set\_arn](#output\_service\_task\_set\_arn) | The Amazon Resource Name (ARN) that identifies the task set | +| [service\_task\_set\_id](#output\_service\_task\_set\_id) | The ID of the task set | +| [service\_task\_set\_stability\_status](#output\_service\_task\_set\_stability\_status) | The stability status. This indicates whether the task set has reached a steady state | +| [service\_task\_set\_status](#output\_service\_task\_set\_status) | The status of the task set | +| [service\_tasks\_iam\_role\_arn](#output\_service\_tasks\_iam\_role\_arn) | Tasks IAM role ARN | +| [service\_tasks\_iam\_role\_name](#output\_service\_tasks\_iam\_role\_name) | Tasks IAM role name | +| [service\_tasks\_iam\_role\_unique\_id](#output\_service\_tasks\_iam\_role\_unique\_id) | Stable and unique string identifying the tasks IAM role | + + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/ec2-autoscaling/main.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/ec2-autoscaling/main.tf new file mode 100644 index 000000000..cef24c975 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/ec2-autoscaling/main.tf @@ -0,0 +1,368 @@ +provider "aws" { + region = local.region +} + +data "aws_availability_zones" "available" {} + +locals { + region = "eu-west-1" + name = "ex-${basename(path.cwd)}" + + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 3) + + container_name = "ecs-sample" + container_port = 80 + + tags = { + Name = local.name + Example = local.name + Repository = "https://github.com/terraform-aws-modules/terraform-aws-ecs" + } +} + +################################################################################ +# Cluster +################################################################################ + +module "ecs_cluster" { + source = "../../modules/cluster" + + cluster_name = local.name + + # Capacity provider - autoscaling groups + default_capacity_provider_use_fargate = false + autoscaling_capacity_providers = { + # On-demand instances + ex_1 = { + auto_scaling_group_arn = module.autoscaling["ex_1"].autoscaling_group_arn + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 5 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } + + default_capacity_provider_strategy = { + weight = 60 + base = 20 + } + } + # Spot instances + ex_2 = { + auto_scaling_group_arn = module.autoscaling["ex_2"].autoscaling_group_arn + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 15 + minimum_scaling_step_size = 5 + status = "ENABLED" + target_capacity = 90 + } + + default_capacity_provider_strategy = { + weight = 40 + } + } + } + + tags = local.tags +} + +################################################################################ +# Service +################################################################################ + +module "ecs_service" { + source = "../../modules/service" + + # Service + name = local.name + cluster_arn = module.ecs_cluster.arn + + # Task Definition + requires_compatibilities = ["EC2"] + capacity_provider_strategy = { + # On-demand instances + ex_1 = { + capacity_provider = module.ecs_cluster.autoscaling_capacity_providers["ex_1"].name + weight = 1 + base = 1 + } + } + + volume = { + my-vol = {} + } + + # Container definition(s) + container_definitions = { + (local.container_name) = { + image = "public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest" + port_mappings = [ + { + name = local.container_name + containerPort = local.container_port + protocol = "tcp" + } + ] + + mount_points = [ + { + sourceVolume = "my-vol", + containerPath = "/var/www/my-vol" + } + ] + + entry_point = ["/usr/sbin/apache2", "-D", "FOREGROUND"] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + enable_cloudwatch_logging = true + create_cloudwatch_log_group = true + cloudwatch_log_group_name = "/aws/ecs/${local.name}/${local.container_name}" + cloudwatch_log_group_retention_in_days = 7 + + log_configuration = { + logDriver = "awslogs" + } + } + } + + load_balancer = { + service = { + target_group_arn = module.alb.target_groups["ex_ecs"].arn + container_name = local.container_name + container_port = local.container_port + } + } + + subnet_ids = module.vpc.private_subnets + security_group_rules = { + alb_http_ingress = { + type = "ingress" + from_port = local.container_port + to_port = local.container_port + protocol = "tcp" + description = "Service port" + source_security_group_id = module.alb.security_group_id + } + } + + tags = local.tags +} + +################################################################################ +# Supporting Resources +################################################################################ + +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html#ecs-optimized-ami-linux +data "aws_ssm_parameter" "ecs_optimized_ami" { + name = "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended" +} + +module "alb" { + source = "terraform-aws-modules/alb/aws" + version = "~> 9.0" + + name = local.name + + load_balancer_type = "application" + + vpc_id = module.vpc.vpc_id + subnets = module.vpc.public_subnets + + # For example only + enable_deletion_protection = false + + # Security Group + security_group_ingress_rules = { + all_http = { + from_port = 80 + to_port = 80 + ip_protocol = "tcp" + cidr_ipv4 = "0.0.0.0/0" + } + } + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = module.vpc.vpc_cidr_block + } + } + + listeners = { + ex_http = { + port = 80 + protocol = "HTTP" + + forward = { + target_group_key = "ex_ecs" + } + } + } + + target_groups = { + ex_ecs = { + backend_protocol = "HTTP" + backend_port = local.container_port + target_type = "ip" + deregistration_delay = 5 + load_balancing_cross_zone_enabled = true + + health_check = { + enabled = true + healthy_threshold = 5 + interval = 30 + matcher = "200" + path = "/" + port = "traffic-port" + protocol = "HTTP" + timeout = 5 + unhealthy_threshold = 2 + } + + # Theres nothing to attach here in this definition. Instead, + # ECS will attach the IPs of the tasks to this target group + create_attachment = false + } + } + + tags = local.tags +} + +module "autoscaling" { + source = "terraform-aws-modules/autoscaling/aws" + version = "~> 6.5" + + for_each = { + # On-demand instances + ex_1 = { + instance_type = "t3.large" + use_mixed_instances_policy = false + mixed_instances_policy = {} + user_data = <<-EOT + #!/bin/bash + + cat <<'EOF' >> /etc/ecs/ecs.config + ECS_CLUSTER=${local.name} + ECS_LOGLEVEL=debug + ECS_CONTAINER_INSTANCE_TAGS=${jsonencode(local.tags)} + ECS_ENABLE_TASK_IAM_ROLE=true + EOF + EOT + } + # Spot instances + ex_2 = { + instance_type = "t3.medium" + use_mixed_instances_policy = true + mixed_instances_policy = { + instances_distribution = { + on_demand_base_capacity = 0 + on_demand_percentage_above_base_capacity = 0 + spot_allocation_strategy = "price-capacity-optimized" + } + + override = [ + { + instance_type = "m4.large" + weighted_capacity = "2" + }, + { + instance_type = "t3.large" + weighted_capacity = "1" + }, + ] + } + user_data = <<-EOT + #!/bin/bash + + cat <<'EOF' >> /etc/ecs/ecs.config + ECS_CLUSTER=${local.name} + ECS_LOGLEVEL=debug + ECS_CONTAINER_INSTANCE_TAGS=${jsonencode(local.tags)} + ECS_ENABLE_TASK_IAM_ROLE=true + ECS_ENABLE_SPOT_INSTANCE_DRAINING=true + EOF + EOT + } + } + + name = "${local.name}-${each.key}" + + image_id = jsondecode(data.aws_ssm_parameter.ecs_optimized_ami.value)["image_id"] + instance_type = each.value.instance_type + + security_groups = [module.autoscaling_sg.security_group_id] + user_data = base64encode(each.value.user_data) + ignore_desired_capacity_changes = true + + create_iam_instance_profile = true + iam_role_name = local.name + iam_role_description = "ECS role for ${local.name}" + iam_role_policies = { + AmazonEC2ContainerServiceforEC2Role = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" + AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" + } + + vpc_zone_identifier = module.vpc.private_subnets + health_check_type = "EC2" + min_size = 1 + max_size = 5 + desired_capacity = 2 + + # https://github.com/hashicorp/terraform-provider-aws/issues/12582 + autoscaling_group_tags = { + AmazonECSManaged = true + } + + # Required for managed_termination_protection = "ENABLED" + protect_from_scale_in = true + + # Spot instances + use_mixed_instances_policy = each.value.use_mixed_instances_policy + mixed_instances_policy = each.value.mixed_instances_policy + + tags = local.tags +} + +module "autoscaling_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 5.0" + + name = local.name + description = "Autoscaling group security group" + vpc_id = module.vpc.vpc_id + + computed_ingress_with_source_security_group_id = [ + { + rule = "http-80-tcp" + source_security_group_id = module.alb.security_group_id + } + ] + number_of_computed_ingress_with_source_security_group_id = 1 + + egress_rules = ["all-all"] + + tags = local.tags +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" + + name = local.name + cidr = local.vpc_cidr + + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] + + enable_nat_gateway = true + single_nat_gateway = true + + tags = local.tags +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/ec2-autoscaling/outputs.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/ec2-autoscaling/outputs.tf new file mode 100644 index 000000000..2f6f85a78 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/ec2-autoscaling/outputs.tf @@ -0,0 +1,132 @@ +################################################################################ +# Cluster +################################################################################ + +output "cluster_arn" { + description = "ARN that identifies the cluster" + value = module.ecs_cluster.arn +} + +output "cluster_id" { + description = "ID that identifies the cluster" + value = module.ecs_cluster.id +} + +output "cluster_name" { + description = "Name that identifies the cluster" + value = module.ecs_cluster.name +} + +output "cluster_capacity_providers" { + description = "Map of cluster capacity providers attributes" + value = module.ecs_cluster.cluster_capacity_providers +} + +output "cluster_autoscaling_capacity_providers" { + description = "Map of capacity providers created and their attributes" + value = module.ecs_cluster.autoscaling_capacity_providers +} + +################################################################################ +# Service +################################################################################ + +output "service_id" { + description = "ARN that identifies the service" + value = module.ecs_service.id +} + +output "service_name" { + description = "Name of the service" + value = module.ecs_service.name +} + +output "service_iam_role_name" { + description = "Service IAM role name" + value = module.ecs_service.iam_role_name +} + +output "service_iam_role_arn" { + description = "Service IAM role ARN" + value = module.ecs_service.iam_role_arn +} + +output "service_iam_role_unique_id" { + description = "Stable and unique string identifying the service IAM role" + value = module.ecs_service.iam_role_unique_id +} + +output "service_container_definitions" { + description = "Container definitions" + value = module.ecs_service.container_definitions +} + +output "service_task_definition_arn" { + description = "Full ARN of the Task Definition (including both `family` and `revision`)" + value = module.ecs_service.task_definition_arn +} + +output "service_task_definition_revision" { + description = "Revision of the task in a particular family" + value = module.ecs_service.task_definition_revision +} + +output "service_task_exec_iam_role_name" { + description = "Task execution IAM role name" + value = module.ecs_service.task_exec_iam_role_name +} + +output "service_task_exec_iam_role_arn" { + description = "Task execution IAM role ARN" + value = module.ecs_service.task_exec_iam_role_arn +} + +output "service_task_exec_iam_role_unique_id" { + description = "Stable and unique string identifying the task execution IAM role" + value = module.ecs_service.task_exec_iam_role_unique_id +} + +output "service_tasks_iam_role_name" { + description = "Tasks IAM role name" + value = module.ecs_service.tasks_iam_role_name +} + +output "service_tasks_iam_role_arn" { + description = "Tasks IAM role ARN" + value = module.ecs_service.tasks_iam_role_arn +} + +output "service_tasks_iam_role_unique_id" { + description = "Stable and unique string identifying the tasks IAM role" + value = module.ecs_service.tasks_iam_role_unique_id +} + +output "service_task_set_id" { + description = "The ID of the task set" + value = module.ecs_service.task_set_id +} + +output "service_task_set_arn" { + description = "The Amazon Resource Name (ARN) that identifies the task set" + value = module.ecs_service.task_set_arn +} + +output "service_task_set_stability_status" { + description = "The stability status. This indicates whether the task set has reached a steady state" + value = module.ecs_service.task_set_stability_status +} + +output "service_task_set_status" { + description = "The status of the task set" + value = module.ecs_service.task_set_status +} + +output "service_autoscaling_policies" { + description = "Map of autoscaling policies and their attributes" + value = module.ecs_service.autoscaling_policies +} + +output "service_autoscaling_scheduled_actions" { + description = "Map of autoscaling scheduled actions and their attributes" + value = module.ecs_service.autoscaling_scheduled_actions +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/ec2-autoscaling/variables.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/ec2-autoscaling/variables.tf new file mode 100644 index 000000000..e69de29bb diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/ec2-autoscaling/versions.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/ec2-autoscaling/versions.tf new file mode 100644 index 000000000..682191e70 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/ec2-autoscaling/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.66.1" + } + } +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/fargate/README.md b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/fargate/README.md new file mode 100644 index 000000000..49d4697de --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/fargate/README.md @@ -0,0 +1,98 @@ +# ECS Clusters w/ Fargate + +Configuration in this directory creates: + +- ECS cluster using Fargate (on-demand and spot) capacity providers +- Example ECS service that utilizes + - AWS Firelens using FluentBit sidecar container definition + - Service connect configuration + - Load balancer target group attachment + - Security group for access to the example service + +## Usage + +To run this example you need to execute: + +```bash +$ terraform init +$ terraform plan +$ terraform apply +``` + +Note that this example may create resources which will incur monetary charges on your AWS bill. Run `terraform destroy` when you no longer need these resources. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.66.1 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.66.1 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [alb](#module\_alb) | terraform-aws-modules/alb/aws | ~> 9.0 | +| [ecs\_cluster](#module\_ecs\_cluster) | ../../modules/cluster | n/a | +| [ecs\_service](#module\_ecs\_service) | ../../modules/service | n/a | +| [ecs\_task\_definition](#module\_ecs\_task\_definition) | ../../modules/service | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_service_discovery_http_namespace.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/service_discovery_http_namespace) | resource | +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | +| [aws_ssm_parameter.fluentbit](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [cluster\_arn](#output\_cluster\_arn) | ARN that identifies the cluster | +| [cluster\_autoscaling\_capacity\_providers](#output\_cluster\_autoscaling\_capacity\_providers) | Map of capacity providers created and their attributes | +| [cluster\_capacity\_providers](#output\_cluster\_capacity\_providers) | Map of cluster capacity providers attributes | +| [cluster\_id](#output\_cluster\_id) | ID that identifies the cluster | +| [cluster\_name](#output\_cluster\_name) | Name that identifies the cluster | +| [service\_autoscaling\_policies](#output\_service\_autoscaling\_policies) | Map of autoscaling policies and their attributes | +| [service\_autoscaling\_scheduled\_actions](#output\_service\_autoscaling\_scheduled\_actions) | Map of autoscaling scheduled actions and their attributes | +| [service\_container\_definitions](#output\_service\_container\_definitions) | Container definitions | +| [service\_iam\_role\_arn](#output\_service\_iam\_role\_arn) | Service IAM role ARN | +| [service\_iam\_role\_name](#output\_service\_iam\_role\_name) | Service IAM role name | +| [service\_iam\_role\_unique\_id](#output\_service\_iam\_role\_unique\_id) | Stable and unique string identifying the service IAM role | +| [service\_id](#output\_service\_id) | ARN that identifies the service | +| [service\_name](#output\_service\_name) | Name of the service | +| [service\_security\_group\_arn](#output\_service\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | +| [service\_security\_group\_id](#output\_service\_security\_group\_id) | ID of the security group | +| [service\_task\_definition\_arn](#output\_service\_task\_definition\_arn) | Full ARN of the Task Definition (including both `family` and `revision`) | +| [service\_task\_definition\_family](#output\_service\_task\_definition\_family) | The unique name of the task definition | +| [service\_task\_definition\_family\_revision](#output\_service\_task\_definition\_family\_revision) | The family and revision (family:revision) of the task definition | +| [service\_task\_definition\_revision](#output\_service\_task\_definition\_revision) | Revision of the task in a particular family | +| [service\_task\_exec\_iam\_role\_arn](#output\_service\_task\_exec\_iam\_role\_arn) | Task execution IAM role ARN | +| [service\_task\_exec\_iam\_role\_name](#output\_service\_task\_exec\_iam\_role\_name) | Task execution IAM role name | +| [service\_task\_exec\_iam\_role\_unique\_id](#output\_service\_task\_exec\_iam\_role\_unique\_id) | Stable and unique string identifying the task execution IAM role | +| [service\_task\_set\_arn](#output\_service\_task\_set\_arn) | The Amazon Resource Name (ARN) that identifies the task set | +| [service\_task\_set\_id](#output\_service\_task\_set\_id) | The ID of the task set | +| [service\_task\_set\_stability\_status](#output\_service\_task\_set\_stability\_status) | The stability status. This indicates whether the task set has reached a steady state | +| [service\_task\_set\_status](#output\_service\_task\_set\_status) | The status of the task set | +| [service\_tasks\_iam\_role\_arn](#output\_service\_tasks\_iam\_role\_arn) | Tasks IAM role ARN | +| [service\_tasks\_iam\_role\_name](#output\_service\_tasks\_iam\_role\_name) | Tasks IAM role name | +| [service\_tasks\_iam\_role\_unique\_id](#output\_service\_tasks\_iam\_role\_unique\_id) | Stable and unique string identifying the tasks IAM role | +| [task\_definition\_run\_task\_command](#output\_task\_definition\_run\_task\_command) | awscli command to run the standalone task | + + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/fargate/main.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/fargate/main.tf new file mode 100644 index 000000000..c263e0b8f --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/fargate/main.tf @@ -0,0 +1,332 @@ +provider "aws" { + region = local.region +} + +data "aws_availability_zones" "available" {} + +locals { + region = "eu-west-1" + name = "ex-${basename(path.cwd)}" + + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 3) + + container_name = "ecsdemo-frontend" + container_port = 3000 + + tags = { + Name = local.name + Example = local.name + Repository = "https://github.com/terraform-aws-modules/terraform-aws-ecs" + } +} + +################################################################################ +# Cluster +################################################################################ + +module "ecs_cluster" { + source = "../../modules/cluster" + + cluster_name = local.name + + # Capacity provider + fargate_capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = 50 + base = 20 + } + } + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = 50 + } + } + } + + tags = local.tags +} + +################################################################################ +# Service +################################################################################ + +module "ecs_service" { + source = "../../modules/service" + + name = local.name + cluster_arn = module.ecs_cluster.arn + + cpu = 1024 + memory = 4096 + + # Enables ECS Exec + enable_execute_command = true + + # Container definition(s) + container_definitions = { + + fluent-bit = { + cpu = 512 + memory = 1024 + essential = true + image = nonsensitive(data.aws_ssm_parameter.fluentbit.value) + firelens_configuration = { + type = "fluentbit" + } + memory_reservation = 50 + user = "0" + } + + (local.container_name) = { + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = local.container_name + containerPort = local.container_port + hostPort = local.container_port + protocol = "tcp" + } + ] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + dependencies = [{ + containerName = "fluent-bit" + condition = "START" + }] + + enable_cloudwatch_logging = false + log_configuration = { + logDriver = "awsfirelens" + options = { + Name = "firehose" + region = local.region + delivery_stream = "my-stream" + log-driver-buffer-limit = "2097152" + } + } + + linux_parameters = { + capabilities = { + add = [] + drop = [ + "NET_RAW" + ] + } + } + + # Not required for fluent-bit, just an example + volumes_from = [{ + sourceContainer = "fluent-bit" + readOnly = false + }] + + memory_reservation = 100 + } + } + + service_connect_configuration = { + namespace = aws_service_discovery_http_namespace.this.arn + service = { + client_alias = { + port = local.container_port + dns_name = local.container_name + } + port_name = local.container_name + discovery_name = local.container_name + } + } + + load_balancer = { + service = { + target_group_arn = module.alb.target_groups["ex_ecs"].arn + container_name = local.container_name + container_port = local.container_port + } + } + + subnet_ids = module.vpc.private_subnets + security_group_rules = { + alb_ingress_3000 = { + type = "ingress" + from_port = local.container_port + to_port = local.container_port + protocol = "tcp" + description = "Service port" + source_security_group_id = module.alb.security_group_id + } + egress_all = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + } + + service_tags = { + "ServiceTag" = "Tag on service level" + } + + tags = local.tags +} + +################################################################################ +# Standalone Task Definition (w/o Service) +################################################################################ + +module "ecs_task_definition" { + source = "../../modules/service" + + # Service + name = "${local.name}-standalone" + cluster_arn = module.ecs_cluster.arn + + # Task Definition + volume = { + ex-vol = {} + } + + runtime_platform = { + cpu_architecture = "ARM64" + operating_system_family = "LINUX" + } + + # Container definition(s) + container_definitions = { + al2023 = { + image = "public.ecr.aws/amazonlinux/amazonlinux:2023-minimal" + + mount_points = [ + { + sourceVolume = "ex-vol", + containerPath = "/var/www/ex-vol" + } + ] + + command = ["echo hello world"] + entrypoint = ["/usr/bin/sh", "-c"] + } + } + + subnet_ids = module.vpc.private_subnets + + security_group_rules = { + egress_all = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + } + + tags = local.tags +} + +################################################################################ +# Supporting Resources +################################################################################ + +data "aws_ssm_parameter" "fluentbit" { + name = "/aws/service/aws-for-fluent-bit/stable" +} + +resource "aws_service_discovery_http_namespace" "this" { + name = local.name + description = "CloudMap namespace for ${local.name}" + tags = local.tags +} + +module "alb" { + source = "terraform-aws-modules/alb/aws" + version = "~> 9.0" + + name = local.name + + load_balancer_type = "application" + + vpc_id = module.vpc.vpc_id + subnets = module.vpc.public_subnets + + # For example only + enable_deletion_protection = false + + # Security Group + security_group_ingress_rules = { + all_http = { + from_port = 80 + to_port = 80 + ip_protocol = "tcp" + cidr_ipv4 = "0.0.0.0/0" + } + } + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = module.vpc.vpc_cidr_block + } + } + + listeners = { + ex_http = { + port = 80 + protocol = "HTTP" + + forward = { + target_group_key = "ex_ecs" + } + } + } + + target_groups = { + ex_ecs = { + backend_protocol = "HTTP" + backend_port = local.container_port + target_type = "ip" + deregistration_delay = 5 + load_balancing_cross_zone_enabled = true + + health_check = { + enabled = true + healthy_threshold = 5 + interval = 30 + matcher = "200" + path = "/" + port = "traffic-port" + protocol = "HTTP" + timeout = 5 + unhealthy_threshold = 2 + } + + # There's nothing to attach here in this definition. Instead, + # ECS will attach the IPs of the tasks to this target group + create_attachment = false + } + } + + tags = local.tags +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" + + name = local.name + cidr = local.vpc_cidr + + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] + + enable_nat_gateway = true + single_nat_gateway = true + + tags = local.tags +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/fargate/outputs.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/fargate/outputs.tf new file mode 100644 index 000000000..6c77f7ba9 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/examples/fargate/outputs.tf @@ -0,0 +1,166 @@ +################################################################################ +# Cluster +################################################################################ + +output "cluster_arn" { + description = "ARN that identifies the cluster" + value = module.ecs_cluster.arn +} + +output "cluster_id" { + description = "ID that identifies the cluster" + value = module.ecs_cluster.id +} + +output "cluster_name" { + description = "Name that identifies the cluster" + value = module.ecs_cluster.name +} + +output "cluster_capacity_providers" { + description = "Map of cluster capacity providers attributes" + value = module.ecs_cluster.cluster_capacity_providers +} + +output "cluster_autoscaling_capacity_providers" { + description = "Map of capacity providers created and their attributes" + value = module.ecs_cluster.autoscaling_capacity_providers +} + +################################################################################ +# Service +################################################################################ + +output "service_id" { + description = "ARN that identifies the service" + value = module.ecs_service.id +} + +output "service_name" { + description = "Name of the service" + value = module.ecs_service.name +} + +output "service_iam_role_name" { + description = "Service IAM role name" + value = module.ecs_service.iam_role_name +} + +output "service_iam_role_arn" { + description = "Service IAM role ARN" + value = module.ecs_service.iam_role_arn +} + +output "service_iam_role_unique_id" { + description = "Stable and unique string identifying the service IAM role" + value = module.ecs_service.iam_role_unique_id +} + +output "service_container_definitions" { + description = "Container definitions" + value = module.ecs_service.container_definitions +} + +output "service_task_definition_arn" { + description = "Full ARN of the Task Definition (including both `family` and `revision`)" + value = module.ecs_service.task_definition_arn +} + +output "service_task_definition_revision" { + description = "Revision of the task in a particular family" + value = module.ecs_service.task_definition_revision +} + +output "service_task_definition_family" { + description = "The unique name of the task definition" + value = module.ecs_service.task_definition_family +} + +output "service_task_definition_family_revision" { + description = "The family and revision (family:revision) of the task definition" + value = module.ecs_service.task_definition_family_revision +} + +output "service_task_exec_iam_role_name" { + description = "Task execution IAM role name" + value = module.ecs_service.task_exec_iam_role_name +} + +output "service_task_exec_iam_role_arn" { + description = "Task execution IAM role ARN" + value = module.ecs_service.task_exec_iam_role_arn +} + +output "service_task_exec_iam_role_unique_id" { + description = "Stable and unique string identifying the task execution IAM role" + value = module.ecs_service.task_exec_iam_role_unique_id +} + +output "service_tasks_iam_role_name" { + description = "Tasks IAM role name" + value = module.ecs_service.tasks_iam_role_name +} + +output "service_tasks_iam_role_arn" { + description = "Tasks IAM role ARN" + value = module.ecs_service.tasks_iam_role_arn +} + +output "service_tasks_iam_role_unique_id" { + description = "Stable and unique string identifying the tasks IAM role" + value = module.ecs_service.tasks_iam_role_unique_id +} + +output "service_task_set_id" { + description = "The ID of the task set" + value = module.ecs_service.task_set_id +} + +output "service_task_set_arn" { + description = "The Amazon Resource Name (ARN) that identifies the task set" + value = module.ecs_service.task_set_arn +} + +output "service_task_set_stability_status" { + description = "The stability status. This indicates whether the task set has reached a steady state" + value = module.ecs_service.task_set_stability_status +} + +output "service_task_set_status" { + description = "The status of the task set" + value = module.ecs_service.task_set_status +} + +output "service_autoscaling_policies" { + description = "Map of autoscaling policies and their attributes" + value = module.ecs_service.autoscaling_policies +} + +output "service_autoscaling_scheduled_actions" { + description = "Map of autoscaling scheduled actions and their attributes" + value = module.ecs_service.autoscaling_scheduled_actions +} + +output "service_security_group_arn" { + description = "Amazon Resource Name (ARN) of the security group" + value = module.ecs_service.security_group_arn +} + +output "service_security_group_id" { + description = "ID of the security group" + value = module.ecs_service.security_group_id +} + +################################################################################ +# Standalone Task Definition (w/o Service) +################################################################################ + +output "task_definition_run_task_command" { + description = "awscli command to run the standalone task" + value = < v if var.create } + + create = try(each.value.create, true) + create_service = try(each.value.create_service, true) + + # Service + ignore_task_definition_changes = try(each.value.ignore_task_definition_changes, false) + alarms = try(each.value.alarms, {}) + capacity_provider_strategy = try(each.value.capacity_provider_strategy, {}) + cluster_arn = module.cluster.arn + deployment_circuit_breaker = try(each.value.deployment_circuit_breaker, {}) + deployment_controller = try(each.value.deployment_controller, {}) + deployment_maximum_percent = try(each.value.deployment_maximum_percent, 200) + deployment_minimum_healthy_percent = try(each.value.deployment_minimum_healthy_percent, 66) + desired_count = try(each.value.desired_count, 1) + enable_ecs_managed_tags = try(each.value.enable_ecs_managed_tags, true) + enable_execute_command = try(each.value.enable_execute_command, false) + force_new_deployment = try(each.value.force_new_deployment, true) + health_check_grace_period_seconds = try(each.value.health_check_grace_period_seconds, null) + launch_type = try(each.value.launch_type, "FARGATE") + load_balancer = lookup(each.value, "load_balancer", {}) + name = try(each.value.name, each.key) + assign_public_ip = try(each.value.assign_public_ip, false) + security_group_ids = lookup(each.value, "security_group_ids", []) + subnet_ids = lookup(each.value, "subnet_ids", []) + ordered_placement_strategy = try(each.value.ordered_placement_strategy, {}) + placement_constraints = try(each.value.placement_constraints, {}) + platform_version = try(each.value.platform_version, null) + propagate_tags = try(each.value.propagate_tags, null) + scheduling_strategy = try(each.value.scheduling_strategy, null) + service_connect_configuration = lookup(each.value, "service_connect_configuration", {}) + service_registries = lookup(each.value, "service_registries", {}) + timeouts = try(each.value.timeouts, {}) + triggers = try(each.value.triggers, {}) + wait_for_steady_state = try(each.value.wait_for_steady_state, null) + + # Service IAM role + create_iam_role = try(each.value.create_iam_role, true) + iam_role_arn = lookup(each.value, "iam_role_arn", null) + iam_role_name = try(each.value.iam_role_name, null) + iam_role_use_name_prefix = try(each.value.iam_role_use_name_prefix, true) + iam_role_path = try(each.value.iam_role_path, null) + iam_role_description = try(each.value.iam_role_description, null) + iam_role_permissions_boundary = try(each.value.iam_role_permissions_boundary, null) + iam_role_tags = try(each.value.iam_role_tags, {}) + iam_role_statements = lookup(each.value, "iam_role_statements", {}) + + # Task definition + create_task_definition = try(each.value.create_task_definition, true) + task_definition_arn = lookup(each.value, "task_definition_arn", null) + container_definitions = try(each.value.container_definitions, {}) + container_definition_defaults = try(each.value.container_definition_defaults, {}) + cpu = try(each.value.cpu, 1024) + ephemeral_storage = try(each.value.ephemeral_storage, {}) + family = try(each.value.family, null) + inference_accelerator = try(each.value.inference_accelerator, {}) + ipc_mode = try(each.value.ipc_mode, null) + memory = try(each.value.memory, 2048) + network_mode = try(each.value.network_mode, "awsvpc") + pid_mode = try(each.value.pid_mode, null) + proxy_configuration = try(each.value.proxy_configuration, {}) + requires_compatibilities = try(each.value.requires_compatibilities, ["FARGATE"]) + runtime_platform = try(each.value.runtime_platform, { + operating_system_family = "LINUX" + cpu_architecture = "X86_64" + }) + skip_destroy = try(each.value.skip_destroy, null) + volume = try(each.value.volume, {}) + task_tags = try(each.value.task_tags, {}) + + # Task execution IAM role + create_task_exec_iam_role = try(each.value.create_task_exec_iam_role, true) + task_exec_iam_role_arn = lookup(each.value, "task_exec_iam_role_arn", null) + task_exec_iam_role_name = try(each.value.task_exec_iam_role_name, null) + task_exec_iam_role_use_name_prefix = try(each.value.task_exec_iam_role_use_name_prefix, true) + task_exec_iam_role_path = try(each.value.task_exec_iam_role_path, null) + task_exec_iam_role_description = try(each.value.task_exec_iam_role_description, null) + task_exec_iam_role_permissions_boundary = try(each.value.task_exec_iam_role_permissions_boundary, null) + task_exec_iam_role_tags = try(each.value.task_exec_iam_role_tags, {}) + task_exec_iam_role_policies = try(each.value.task_exec_iam_role_policies, {}) + task_exec_iam_role_max_session_duration = try(each.value.task_exec_iam_role_max_session_duration, null) + + # Task execution IAM role policy + create_task_exec_policy = try(each.value.create_task_exec_policy, true) + task_exec_ssm_param_arns = lookup(each.value, "task_exec_ssm_param_arns", ["arn:aws:ssm:*:*:parameter/*"]) + task_exec_secret_arns = lookup(each.value, "task_exec_secret_arns", ["arn:aws:secretsmanager:*:*:secret:*"]) + task_exec_iam_statements = lookup(each.value, "task_exec_iam_statements", {}) + + # Tasks - IAM role + create_tasks_iam_role = try(each.value.create_tasks_iam_role, true) + tasks_iam_role_arn = lookup(each.value, "tasks_iam_role_arn", null) + tasks_iam_role_name = try(each.value.tasks_iam_role_name, null) + tasks_iam_role_use_name_prefix = try(each.value.tasks_iam_role_use_name_prefix, true) + tasks_iam_role_path = try(each.value.tasks_iam_role_path, null) + tasks_iam_role_description = try(each.value.tasks_iam_role_description, null) + tasks_iam_role_permissions_boundary = try(each.value.tasks_iam_role_permissions_boundary, null) + tasks_iam_role_tags = try(each.value.tasks_iam_role_tags, {}) + tasks_iam_role_policies = lookup(each.value, "tasks_iam_role_policies", {}) + tasks_iam_role_statements = lookup(each.value, "tasks_iam_role_statements", {}) + + # Task set + external_id = try(each.value.external_id, null) + scale = try(each.value.scale, {}) + force_delete = try(each.value.force_delete, null) + wait_until_stable = try(each.value.wait_until_stable, null) + wait_until_stable_timeout = try(each.value.wait_until_stable_timeout, null) + + # Autoscaling + enable_autoscaling = try(each.value.enable_autoscaling, true) + autoscaling_min_capacity = try(each.value.autoscaling_min_capacity, 1) + autoscaling_max_capacity = try(each.value.autoscaling_max_capacity, 10) + autoscaling_policies = try(each.value.autoscaling_policies, { + cpu = { + policy_type = "TargetTrackingScaling" + + target_tracking_scaling_policy_configuration = { + predefined_metric_specification = { + predefined_metric_type = "ECSServiceAverageCPUUtilization" + } + } + } + memory = { + policy_type = "TargetTrackingScaling" + + target_tracking_scaling_policy_configuration = { + predefined_metric_specification = { + predefined_metric_type = "ECSServiceAverageMemoryUtilization" + } + } + } + }) + autoscaling_scheduled_actions = try(each.value.autoscaling_scheduled_actions, {}) + + # Security Group + create_security_group = try(each.value.create_security_group, true) + security_group_name = try(each.value.security_group_name, null) + security_group_use_name_prefix = try(each.value.security_group_use_name_prefix, true) + security_group_description = try(each.value.security_group_description, null) + security_group_rules = lookup(each.value, "security_group_rules", {}) + security_group_tags = try(each.value.security_group_tags, {}) + + tags = merge(var.tags, try(each.value.tags, {})) +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/cluster/README.md b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/cluster/README.md new file mode 100644 index 000000000..282943b50 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/cluster/README.md @@ -0,0 +1,214 @@ +# Amazon ECS Cluster Terraform Module + +Terraform module which creates Amazon ECS (Elastic Container Service) cluster resources on AWS. + +## Available Features + +- ECS cluster +- Fargate capacity providers +- EC2 AutoScaling Group capacity providers +- ECS Service w/ task definition, task set, and container definition support + +For more details see the [design doc](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/docs/README.md) + +## Usage + +### Fargate Capacity Providers + +```hcl +module "ecs_cluster" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + + cluster_name = "ecs-fargate" + + cluster_configuration = { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + cloud_watch_log_group_name = "/aws/ecs/aws-ec2" + } + } + } + + fargate_capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = 50 + } + } + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = 50 + } + } + } + + tags = { + Environment = "Development" + Project = "EcsEc2" + } +} +``` + +### EC2 Autoscaling Capacity Providers + +```hcl +module "ecs_cluster" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + + cluster_name = "ecs-ec2" + + cluster_configuration = { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + cloud_watch_log_group_name = "/aws/ecs/aws-ec2" + } + } + } + + autoscaling_capacity_providers = { + one = { + auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-one-20220603194933774300000011" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 5 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } + + default_capacity_provider_strategy = { + weight = 60 + base = 20 + } + } + two = { + auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-two-20220603194933774300000022" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 15 + minimum_scaling_step_size = 5 + status = "ENABLED" + target_capacity = 90 + } + + default_capacity_provider_strategy = { + weight = 40 + } + } + } + + tags = { + Environment = "Development" + Project = "EcsEc2" + } +} +``` + +## Conditional Creation + +The following values are provided to toggle on/off creation of the associated resources as desired: + +```hcl +module "ecs_cluster" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + + # Disable creation of cluster and all resources + create = false + + # ... omitted +} +``` + +## Examples + +- [ECS Cluster Complete](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) +- [ECS Cluster w/ EC2 Autoscaling Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/ec2-autoscaling) +- [ECS Cluster w/ Fargate Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/fargate) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.66.1 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.66.1 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_ecs_capacity_provider.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_capacity_provider) | resource | +| [aws_ecs_cluster.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster) | resource | +| [aws_ecs_cluster_capacity_providers.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster_capacity_providers) | resource | +| [aws_iam_policy.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.task_exec_additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_policy_document.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.task_exec_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [autoscaling\_capacity\_providers](#input\_autoscaling\_capacity\_providers) | Map of autoscaling capacity provider definitions to create for the cluster | `any` | `{}` | no | +| [cloudwatch\_log\_group\_kms\_key\_id](#input\_cloudwatch\_log\_group\_kms\_key\_id) | If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html) | `string` | `null` | no | +| [cloudwatch\_log\_group\_name](#input\_cloudwatch\_log\_group\_name) | Custom name of CloudWatch Log Group for ECS cluster | `string` | `null` | no | +| [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | Number of days to retain log events | `number` | `90` | no | +| [cloudwatch\_log\_group\_tags](#input\_cloudwatch\_log\_group\_tags) | A map of additional tags to add to the log group created | `map(string)` | `{}` | no | +| [cluster\_configuration](#input\_cluster\_configuration) | The execute command configuration for the cluster | `any` | `{}` | no | +| [cluster\_name](#input\_cluster\_name) | Name of the cluster (up to 255 letters, numbers, hyphens, and underscores) | `string` | `""` | no | +| [cluster\_service\_connect\_defaults](#input\_cluster\_service\_connect\_defaults) | Configures a default Service Connect namespace | `map(string)` | `{}` | no | +| [cluster\_settings](#input\_cluster\_settings) | List of configuration block(s) with cluster settings. For example, this can be used to enable CloudWatch Container Insights for a cluster | `any` |
[
{
"name": "containerInsights",
"value": "enabled"
}
]
| no | +| [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no | +| [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Determines whether a log group is created by this module for the cluster logs. If not, AWS will automatically create one if logging is enabled | `bool` | `true` | no | +| [create\_task\_exec\_iam\_role](#input\_create\_task\_exec\_iam\_role) | Determines whether the ECS task definition IAM role should be created | `bool` | `false` | no | +| [create\_task\_exec\_policy](#input\_create\_task\_exec\_policy) | Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters | `bool` | `true` | no | +| [default\_capacity\_provider\_use\_fargate](#input\_default\_capacity\_provider\_use\_fargate) | Determines whether to use Fargate or autoscaling for default capacity provider strategy | `bool` | `true` | no | +| [fargate\_capacity\_providers](#input\_fargate\_capacity\_providers) | Map of Fargate capacity provider definitions to use for the cluster | `any` | `{}` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_description](#input\_task\_exec\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [task\_exec\_iam\_role\_name](#input\_task\_exec\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [task\_exec\_iam\_role\_path](#input\_task\_exec\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [task\_exec\_iam\_role\_permissions\_boundary](#input\_task\_exec\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [task\_exec\_iam\_role\_policies](#input\_task\_exec\_iam\_role\_policies) | Map of IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_tags](#input\_task\_exec\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_use\_name\_prefix](#input\_task\_exec\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [task\_exec\_iam\_statements](#input\_task\_exec\_iam\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [task\_exec\_secret\_arns](#input\_task\_exec\_secret\_arns) | List of SecretsManager secret ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:secretsmanager:*:*:secret:*"
]
| no | +| [task\_exec\_ssm\_param\_arns](#input\_task\_exec\_ssm\_param\_arns) | List of SSM parameter ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/*"
]
| no | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | ARN that identifies the cluster | +| [autoscaling\_capacity\_providers](#output\_autoscaling\_capacity\_providers) | Map of autoscaling capacity providers created and their attributes | +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | ARN of CloudWatch log group created | +| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of CloudWatch log group created | +| [cluster\_capacity\_providers](#output\_cluster\_capacity\_providers) | Map of cluster capacity providers attributes | +| [id](#output\_id) | ID that identifies the cluster | +| [name](#output\_name) | Name that identifies the cluster | +| [task\_exec\_iam\_role\_arn](#output\_task\_exec\_iam\_role\_arn) | Task execution IAM role ARN | +| [task\_exec\_iam\_role\_name](#output\_task\_exec\_iam\_role\_name) | Task execution IAM role name | +| [task\_exec\_iam\_role\_unique\_id](#output\_task\_exec\_iam\_role\_unique\_id) | Stable and unique string identifying the task execution IAM role | + + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/cluster/main.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/cluster/main.tf new file mode 100644 index 000000000..0b72057d6 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/cluster/main.tf @@ -0,0 +1,340 @@ +################################################################################ +# Cluster +################################################################################ + +locals { + execute_command_configuration = jsondecode(try(aws_cloudwatch_log_group.this[0].name, null) != null ? jsonencode({ + logging = "OVERRIDE" + log_configuration = { + cloud_watch_log_group_name = try(aws_cloudwatch_log_group.this[0].name, null) + } + }) : jsonencode({ + logging = "DEFAULT" + })) +} + +resource "aws_ecs_cluster" "this" { + count = var.create ? 1 : 0 + + name = var.cluster_name + + dynamic "configuration" { + for_each = var.create_cloudwatch_log_group ? [var.cluster_configuration] : [{execute_command_configuration = local.execute_command_configuration}] + + content { + dynamic "execute_command_configuration" { + for_each = try([merge(local.execute_command_configuration, configuration.value.execute_command_configuration)], [{}]) + + content { + kms_key_id = try(execute_command_configuration.value.kms_key_id, null) + logging = try(execute_command_configuration.value.logging, "DEFAULT") + + dynamic "log_configuration" { + for_each = try([execute_command_configuration.value.log_configuration], []) + + content { + cloud_watch_encryption_enabled = try(log_configuration.value.cloud_watch_encryption_enabled, null) + cloud_watch_log_group_name = try(log_configuration.value.cloud_watch_log_group_name, null) + s3_bucket_name = try(log_configuration.value.s3_bucket_name, null) + s3_bucket_encryption_enabled = try(log_configuration.value.s3_bucket_encryption_enabled, null) + s3_key_prefix = try(log_configuration.value.s3_key_prefix, null) + } + } + } + } + } + } + + dynamic "configuration" { + for_each = !var.create_cloudwatch_log_group && length(var.cluster_configuration) > 0 ? [var.cluster_configuration] : [] + + content { + dynamic "execute_command_configuration" { + for_each = try([configuration.value.execute_command_configuration], [{}]) + + content { + kms_key_id = try(execute_command_configuration.value.kms_key_id, null) + logging = try(execute_command_configuration.value.logging, "DEFAULT") + + dynamic "log_configuration" { + for_each = try([execute_command_configuration.value.log_configuration], []) + + content { + cloud_watch_encryption_enabled = try(log_configuration.value.cloud_watch_encryption_enabled, null) + cloud_watch_log_group_name = try(log_configuration.value.cloud_watch_log_group_name, null) + s3_bucket_name = try(log_configuration.value.s3_bucket_name, null) + s3_bucket_encryption_enabled = try(log_configuration.value.s3_bucket_encryption_enabled, null) + s3_key_prefix = try(log_configuration.value.s3_key_prefix, null) + } + } + } + } + } + } + + dynamic "service_connect_defaults" { + for_each = length(var.cluster_service_connect_defaults) > 0 ? [var.cluster_service_connect_defaults] : [] + + content { + namespace = service_connect_defaults.value.namespace + } + } + + dynamic "setting" { + for_each = flatten([var.cluster_settings]) + + content { + name = setting.value.name + value = setting.value.value + } + } + + tags = var.tags + provider = "aws5" + + lifecycle { + prevent_destroy = true + } +} + +################################################################################ +# CloudWatch Log Group +################################################################################ +resource "aws_cloudwatch_log_group" "this" { + count = var.create && var.create_cloudwatch_log_group ? 1 : 0 + + name = try(coalesce(var.cloudwatch_log_group_name, "/aws/ecs/${var.cluster_name}"), "") + retention_in_days = var.cloudwatch_log_group_retention_in_days + kms_key_id = var.cloudwatch_log_group_kms_key_id + + tags = merge(var.tags, var.cloudwatch_log_group_tags) + provider = "aws5" +} + +################################################################################ +# Cluster Capacity Providers +################################################################################ + +locals { + default_capacity_providers = merge( + { for k, v in var.fargate_capacity_providers : k => v if var.default_capacity_provider_use_fargate }, + { for k, v in var.autoscaling_capacity_providers : k => v if !var.default_capacity_provider_use_fargate } + ) +} + +resource "aws_ecs_cluster_capacity_providers" "this" { + count = var.create && length(merge(var.fargate_capacity_providers, var.autoscaling_capacity_providers)) > 0 ? 1 : 0 + + cluster_name = aws_ecs_cluster.this[0].name + capacity_providers = distinct(concat( + [for k, v in var.fargate_capacity_providers : try(v.name, k)], + [for k, v in var.autoscaling_capacity_providers : try(v.name, k)] + )) + + # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-capacity-providers.html#capacity-providers-considerations + dynamic "default_capacity_provider_strategy" { + for_each = local.default_capacity_providers + iterator = strategy + + content { + capacity_provider = try(strategy.value.name, strategy.key) + base = try(strategy.value.default_capacity_provider_strategy.base, null) + weight = try(strategy.value.default_capacity_provider_strategy.weight, null) + } + } + + depends_on = [ + aws_ecs_capacity_provider.this + ] + provider = "aws5" +} + +################################################################################ +# Capacity Provider - Autoscaling Group(s) +################################################################################ + +resource "aws_ecs_capacity_provider" "this" { + for_each = { for k, v in var.autoscaling_capacity_providers : k => v if var.create } + + name = try(each.value.name, each.key) + + auto_scaling_group_provider { + auto_scaling_group_arn = each.value.auto_scaling_group_arn + # When you use managed termination protection, you must also use managed scaling otherwise managed termination protection won't work + managed_termination_protection = length(try([each.value.managed_scaling], [])) == 0 ? "DISABLED" : try(each.value.managed_termination_protection, null) + + dynamic "managed_scaling" { + for_each = try([each.value.managed_scaling], []) + + content { + instance_warmup_period = try(managed_scaling.value.instance_warmup_period, null) + maximum_scaling_step_size = try(managed_scaling.value.maximum_scaling_step_size, null) + minimum_scaling_step_size = try(managed_scaling.value.minimum_scaling_step_size, null) + status = try(managed_scaling.value.status, null) + target_capacity = try(managed_scaling.value.target_capacity, null) + } + } + } + + tags = merge(var.tags, try(each.value.tags, {})) + provider = "aws5" +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +locals { + task_exec_iam_role_name = try(coalesce(var.task_exec_iam_role_name, var.cluster_name), "") + + create_task_exec_iam_role = var.create && var.create_task_exec_iam_role + create_task_exec_policy = local.create_task_exec_iam_role && var.create_task_exec_policy +} + +data "aws_iam_policy_document" "task_exec_assume" { + count = local.create_task_exec_iam_role ? 1 : 0 + + statement { + sid = "ECSTaskExecutionAssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ecs-tasks.amazonaws.com"] + } + } + provider = "aws5" +} + +resource "aws_iam_role" "task_exec" { + count = local.create_task_exec_iam_role ? 1 : 0 + + name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name + name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null + path = var.task_exec_iam_role_path + description = coalesce(var.task_exec_iam_role_description, "Task execution role for ${var.cluster_name}") + + assume_role_policy = data.aws_iam_policy_document.task_exec_assume[0].json + permissions_boundary = var.task_exec_iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.task_exec_iam_role_tags) + provider = "aws5" +} + +resource "aws_iam_role_policy_attachment" "task_exec_additional" { + for_each = { for k, v in var.task_exec_iam_role_policies : k => v if local.create_task_exec_iam_role } + + role = aws_iam_role.task_exec[0].name + policy_arn = each.value + provider = "aws5" +} + +data "aws_iam_policy_document" "task_exec" { + count = local.create_task_exec_policy ? 1 : 0 + + # Pulled from AmazonECSTaskExecutionRolePolicy + statement { + sid = "Logs" + actions = [ + "logs:CreateLogStream", + "logs:PutLogEvents", + ] + resources = ["*"] + } + + # Pulled from AmazonECSTaskExecutionRolePolicy + statement { + sid = "ECR" + actions = [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + ] + resources = ["*"] + } + + dynamic "statement" { + for_each = length(var.task_exec_ssm_param_arns) > 0 ? [1] : [] + + content { + sid = "GetSSMParams" + actions = ["ssm:GetParameters"] + resources = var.task_exec_ssm_param_arns + } + } + + dynamic "statement" { + for_each = length(var.task_exec_secret_arns) > 0 ? [1] : [] + + content { + sid = "GetSecrets" + actions = ["secretsmanager:GetSecretValue"] + resources = var.task_exec_secret_arns + } + } + + dynamic "statement" { + for_each = var.task_exec_iam_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, null) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } + provider = "aws5" +} + +resource "aws_iam_policy" "task_exec" { + count = local.create_task_exec_policy ? 1 : 0 + + name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name + name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null + description = coalesce(var.task_exec_iam_role_description, "Task execution role IAM policy") + policy = data.aws_iam_policy_document.task_exec[0].json + + tags = merge(var.tags, var.task_exec_iam_role_tags) + provider = "aws5" +} + +resource "aws_iam_role_policy_attachment" "task_exec" { + count = local.create_task_exec_policy ? 1 : 0 + + role = aws_iam_role.task_exec[0].name + policy_arn = aws_iam_policy.task_exec[0].arn + provider = "aws5" +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/cluster/outputs.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/cluster/outputs.tf new file mode 100644 index 000000000..4e05381f8 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/cluster/outputs.tf @@ -0,0 +1,70 @@ +################################################################################ +# Cluster +################################################################################ + +output "arn" { + description = "ARN that identifies the cluster" + value = try(aws_ecs_cluster.this[0].arn, null) +} + +output "id" { + description = "ID that identifies the cluster" + value = try(aws_ecs_cluster.this[0].id, null) +} + +output "name" { + description = "Name that identifies the cluster" + value = try(aws_ecs_cluster.this[0].name, null) +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +output "cloudwatch_log_group_name" { + description = "Name of CloudWatch log group created" + value = try(aws_cloudwatch_log_group.this[0].name, null) +} + +output "cloudwatch_log_group_arn" { + description = "ARN of CloudWatch log group created" + value = try(aws_cloudwatch_log_group.this[0].arn, null) +} + +################################################################################ +# Cluster Capacity Providers +################################################################################ + +output "cluster_capacity_providers" { + description = "Map of cluster capacity providers attributes" + value = { for k, v in aws_ecs_cluster_capacity_providers.this : v.id => v } +} + +################################################################################ +# Capacity Provider - Autoscaling Group(s) +################################################################################ + +output "autoscaling_capacity_providers" { + description = "Map of autoscaling capacity providers created and their attributes" + value = aws_ecs_capacity_provider.this +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +output "task_exec_iam_role_name" { + description = "Task execution IAM role name" + value = try(aws_iam_role.task_exec[0].name, null) +} + +output "task_exec_iam_role_arn" { + description = "Task execution IAM role ARN" + value = try(aws_iam_role.task_exec[0].arn, null) +} + +output "task_exec_iam_role_unique_id" { + description = "Stable and unique string identifying the task execution IAM role" + value = try(aws_iam_role.task_exec[0].unique_id, null) +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/cluster/variables.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/cluster/variables.tf new file mode 100644 index 000000000..6629743cb --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/cluster/variables.tf @@ -0,0 +1,177 @@ +variable "create" { + description = "Determines whether resources will be created (affects all resources)" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +################################################################################ +# Cluster +################################################################################ + +variable "cluster_name" { + description = "Name of the cluster (up to 255 letters, numbers, hyphens, and underscores)" + type = string + default = "" +} + +variable "cluster_configuration" { + description = "The execute command configuration for the cluster" + type = any + default = {} +} + +variable "cluster_settings" { + description = "List of configuration block(s) with cluster settings. For example, this can be used to enable CloudWatch Container Insights for a cluster" + type = any + default = [ + { + name = "containerInsights" + value = "enabled" + } + ] +} + +variable "cluster_service_connect_defaults" { + description = "Configures a default Service Connect namespace" + type = map(string) + default = {} +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +variable "create_cloudwatch_log_group" { + description = "Determines whether a log group is created by this module for the cluster logs. If not, AWS will automatically create one if logging is enabled" + type = bool + default = true +} + +variable "cloudwatch_log_group_name" { + description = "Custom name of CloudWatch Log Group for ECS cluster" + type = string + default = null +} + +variable "cloudwatch_log_group_retention_in_days" { + description = "Number of days to retain log events" + type = number + default = 90 +} + +variable "cloudwatch_log_group_kms_key_id" { + description = "If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html)" + type = string + default = null +} + +variable "cloudwatch_log_group_tags" { + description = "A map of additional tags to add to the log group created" + type = map(string) + default = {} +} + +################################################################################ +# Capacity Providers +################################################################################ + +variable "default_capacity_provider_use_fargate" { + description = "Determines whether to use Fargate or autoscaling for default capacity provider strategy" + type = bool + default = true +} + +variable "fargate_capacity_providers" { + description = "Map of Fargate capacity provider definitions to use for the cluster" + type = any + default = {} +} + +variable "autoscaling_capacity_providers" { + description = "Map of autoscaling capacity provider definitions to create for the cluster" + type = any + default = {} +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +variable "create_task_exec_iam_role" { + description = "Determines whether the ECS task definition IAM role should be created" + type = bool + default = false +} + +variable "task_exec_iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "task_exec_iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "task_exec_iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "task_exec_iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "task_exec_iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "task_exec_iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} + +variable "task_exec_iam_role_policies" { + description = "Map of IAM role policy ARNs to attach to the IAM role" + type = map(string) + default = {} +} + +variable "create_task_exec_policy" { + description = "Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters" + type = bool + default = true +} + +variable "task_exec_ssm_param_arns" { + description = "List of SSM parameter ARNs the task execution role will be permitted to get/read" + type = list(string) + default = ["arn:aws:ssm:*:*:parameter/*"] +} + +variable "task_exec_secret_arns" { + description = "List of SecretsManager secret ARNs the task execution role will be permitted to get/read" + type = list(string) + default = ["arn:aws:secretsmanager:*:*:secret:*"] +} + +variable "task_exec_iam_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/cluster/versions.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/cluster/versions.tf new file mode 100644 index 000000000..17c7f7b40 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/cluster/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws5" + version = ">= 4.66.1" + } + } +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/container-definition/README.md b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/container-definition/README.md new file mode 100644 index 000000000..99bc71aa1 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/container-definition/README.md @@ -0,0 +1,203 @@ +# Amazon ECS Container Definition Module + +Configuration in this directory creates an Amazon ECS container definition. + +The module defaults to creating and utilizing a CloudWatch log group. You can disable this behavior by setting `enable_cloudwatch_logging` = `false` - useful for scenarios where Firelens is used for log forwarding. + +For more details see the [design doc](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/docs/design.md) + +## Usage + +### Standard + +```hcl +module "ecs_container_definition" { + source = "terraform-aws-modules/ecs/aws//modules/container-definition" + + name = "example" + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = "ecs-sample" + containerPort = 80 + protocol = "tcp" + } + ] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + memory_reservation = 100 + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + +### W/ Firelens + +```hcl +module "fluentbit_ecs_container_definition" { + source = "terraform-aws-modules/ecs/aws//modules/container-definition" + name = "fluent-bit" + + cpu = 512 + memory = 1024 + essential = true + image = "906394416424.dkr.ecr.us-west-2.amazonaws.com/aws-for-fluent-bit:stable" + firelens_configuration = { + type = "fluentbit" + } + memory_reservation = 50 + + tags = { + Environment = "dev" + Terraform = "true" + } +} + +module "example_ecs_container_definition" { + source = "terraform-aws-modules/ecs/aws//modules/container-definition" + + name = "example" + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = "ecs-sample" + containerPort = 80 + protocol = "tcp" + } + ] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + dependencies = [{ + containerName = "fluent-bit" + condition = "START" + }] + + enable_cloudwatch_logging = false + log_configuration = { + logDriver = "awsfirelens" + options = { + Name = "firehose" + region = "eu-west-1" + delivery_stream = "my-stream" + log-driver-buffer-limit = "2097152" + } + } + memory_reservation = 100 + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + +## Examples + +- [ECS Cluster Complete](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) +- [ECS Cluster w/ EC2 Autoscaling Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/ec2-autoscaling) +- [ECS Cluster w/ Fargate Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/fargate) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.66.1 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.66.1 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [cloudwatch\_log\_group\_kms\_key\_id](#input\_cloudwatch\_log\_group\_kms\_key\_id) | If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html) | `string` | `null` | no | +| [cloudwatch\_log\_group\_name](#input\_cloudwatch\_log\_group\_name) | Custom name of CloudWatch log group for a service associated with the container definition | `string` | `null` | no | +| [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | Number of days to retain log events. Default is 30 days | `number` | `30` | no | +| [cloudwatch\_log\_group\_use\_name\_prefix](#input\_cloudwatch\_log\_group\_use\_name\_prefix) | Determines whether the log group name should be used as a prefix | `bool` | `false` | no | +| [command](#input\_command) | The command that's passed to the container | `list(string)` | `[]` | no | +| [cpu](#input\_cpu) | The number of cpu units to reserve for the container. This is optional for tasks using Fargate launch type and the total amount of `cpu` of all containers in a task will need to be lower than the task-level cpu value | `number` | `null` | no | +| [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Determines whether a log group is created by this module. If not, AWS will automatically create one if logging is enabled | `bool` | `true` | no | +| [dependencies](#input\_dependencies) | The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed. The condition can be one of START, COMPLETE, SUCCESS or HEALTHY |
list(object({
condition = string
containerName = string
}))
| `[]` | no | +| [disable\_networking](#input\_disable\_networking) | When this parameter is true, networking is disabled within the container | `bool` | `null` | no | +| [dns\_search\_domains](#input\_dns\_search\_domains) | Container DNS search domains. A list of DNS search domains that are presented to the container | `list(string)` | `[]` | no | +| [dns\_servers](#input\_dns\_servers) | Container DNS servers. This is a list of strings specifying the IP addresses of the DNS servers | `list(string)` | `[]` | no | +| [docker\_labels](#input\_docker\_labels) | A key/value map of labels to add to the container | `map(string)` | `{}` | no | +| [docker\_security\_options](#input\_docker\_security\_options) | A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems. This field isn't valid for containers in tasks using the Fargate launch type | `list(string)` | `[]` | no | +| [enable\_cloudwatch\_logging](#input\_enable\_cloudwatch\_logging) | Determines whether CloudWatch logging is configured for this container definition. Set to `false` to use other logging drivers | `bool` | `true` | no | +| [enable\_execute\_command](#input\_enable\_execute\_command) | Specifies whether to enable Amazon ECS Exec for the tasks within the service | `bool` | `false` | no | +| [entrypoint](#input\_entrypoint) | The entry point that is passed to the container | `list(string)` | `[]` | no | +| [environment](#input\_environment) | The environment variables to pass to the container |
list(object({
name = string
value = string
}))
| `[]` | no | +| [environment\_files](#input\_environment\_files) | A list of files containing the environment variables to pass to a container |
list(object({
value = string
type = string
}))
| `[]` | no | +| [essential](#input\_essential) | If the `essential` parameter of a container is marked as `true`, and that container fails or stops for any reason, all other containers that are part of the task are stopped | `bool` | `null` | no | +| [extra\_hosts](#input\_extra\_hosts) | A list of hostnames and IP address mappings to append to the `/etc/hosts` file on the container |
list(object({
hostname = string
ipAddress = string
}))
| `[]` | no | +| [firelens\_configuration](#input\_firelens\_configuration) | The FireLens configuration for the container. This is used to specify and configure a log router for container logs. For more information, see [Custom Log Routing](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html) in the Amazon Elastic Container Service Developer Guide | `any` | `{}` | no | +| [health\_check](#input\_health\_check) | The container health check command and associated configuration parameters for the container. See [HealthCheck](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_HealthCheck.html) | `any` | `{}` | no | +| [hostname](#input\_hostname) | The hostname to use for your container | `string` | `null` | no | +| [image](#input\_image) | The image used to start a container. This string is passed directly to the Docker daemon. By default, images in the Docker Hub registry are available. Other repositories are specified with either `repository-url/image:tag` or `repository-url/image@digest` | `string` | `null` | no | +| [interactive](#input\_interactive) | When this parameter is `true`, you can deploy containerized applications that require `stdin` or a `tty` to be allocated | `bool` | `false` | no | +| [links](#input\_links) | The links parameter allows containers to communicate with each other without the need for port mappings. This parameter is only supported if the network mode of a task definition is `bridge` | `list(string)` | `[]` | no | +| [linux\_parameters](#input\_linux\_parameters) | Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html) | `any` | `{}` | no | +| [log\_configuration](#input\_log\_configuration) | The log configuration for the container. For more information see [LogConfiguration](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_LogConfiguration.html) | `any` | `{}` | no | +| [memory](#input\_memory) | The amount (in MiB) of memory to present to the container. If your container attempts to exceed the memory specified here, the container is killed. The total amount of memory reserved for all containers within a task must be lower than the task `memory` value, if one is specified | `number` | `null` | no | +| [memory\_reservation](#input\_memory\_reservation) | The soft limit (in MiB) of memory to reserve for the container. When system memory is under heavy contention, Docker attempts to keep the container memory to this soft limit. However, your container can consume more memory when it needs to, up to either the hard limit specified with the `memory` parameter (if applicable), or all of the available memory on the container instance | `number` | `null` | no | +| [mount\_points](#input\_mount\_points) | The mount points for data volumes in your container | `list(any)` | `[]` | no | +| [name](#input\_name) | The name of a container. If you're linking multiple containers together in a task definition, the name of one container can be entered in the links of another container to connect the containers. Up to 255 letters (uppercase and lowercase), numbers, underscores, and hyphens are allowed | `string` | `null` | no | +| [operating\_system\_family](#input\_operating\_system\_family) | The OS family for task | `string` | `"LINUX"` | no | +| [port\_mappings](#input\_port\_mappings) | The list of port mappings for the container. Port mappings allow containers to access ports on the host container instance to send or receive traffic. For task definitions that use the awsvpc network mode, only specify the containerPort. The hostPort can be left blank or it must be the same value as the containerPort | `list(any)` | `[]` | no | +| [privileged](#input\_privileged) | When this parameter is true, the container is given elevated privileges on the host container instance (similar to the root user) | `bool` | `false` | no | +| [pseudo\_terminal](#input\_pseudo\_terminal) | When this parameter is true, a `TTY` is allocated | `bool` | `false` | no | +| [readonly\_root\_filesystem](#input\_readonly\_root\_filesystem) | When this parameter is true, the container is given read-only access to its root file system | `bool` | `true` | no | +| [repository\_credentials](#input\_repository\_credentials) | Container repository credentials; required when using a private repo. This map currently supports a single key; "credentialsParameter", which should be the ARN of a Secrets Manager's secret holding the credentials | `map(string)` | `{}` | no | +| [resource\_requirements](#input\_resource\_requirements) | The type and amount of a resource to assign to a container. The only supported resource is a GPU |
list(object({
type = string
value = string
}))
| `[]` | no | +| [secrets](#input\_secrets) | The secrets to pass to the container. For more information, see [Specifying Sensitive Data](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) in the Amazon Elastic Container Service Developer Guide |
list(object({
name = string
valueFrom = string
}))
| `[]` | no | +| [service](#input\_service) | The name of the service that the container definition is associated with | `string` | `""` | no | +| [start\_timeout](#input\_start\_timeout) | Time duration (in seconds) to wait before giving up on resolving dependencies for a container | `number` | `30` | no | +| [stop\_timeout](#input\_stop\_timeout) | Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own | `number` | `120` | no | +| [system\_controls](#input\_system\_controls) | A list of namespaced kernel parameters to set in the container | `list(map(string))` | `[]` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [ulimits](#input\_ulimits) | A list of ulimits to set in the container. If a ulimit value is specified in a task definition, it overrides the default values set by Docker |
list(object({
hardLimit = number
name = string
softLimit = number
}))
| `[]` | no | +| [user](#input\_user) | The user to run as inside the container. Can be any of these formats: user, user:group, uid, uid:gid, user:gid, uid:group. The default (null) will use the container's configured `USER` directive or root if not set | `string` | `null` | no | +| [volumes\_from](#input\_volumes\_from) | Data volumes to mount from another container | `list(any)` | `[]` | no | +| [working\_directory](#input\_working\_directory) | The working directory to run commands inside the container | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | ARN of CloudWatch log group created | +| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of CloudWatch log group created | +| [container\_definition](#output\_container\_definition) | Container definition | + + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/container-definition/main.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/container-definition/main.tf new file mode 100644 index 000000000..682fc94ce --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/container-definition/main.tf @@ -0,0 +1,83 @@ +data "aws_region" "current" {} + +locals { + is_not_windows = contains(["LINUX"], var.operating_system_family) + + log_group_name = try(coalesce(var.cloudwatch_log_group_name, "/aws/ecs/${var.service}/${var.name}"), "") + + log_configuration = merge( + { for k, v in { + logDriver = "awslogs", + options = { + awslogs-region = data.aws_region.current.name, + awslogs-group = try(aws_cloudwatch_log_group.this[0].name, ""), + awslogs-stream-prefix = "ecs" + }, + } : k => v if var.enable_cloudwatch_logging }, + var.log_configuration + ) + + linux_parameters = var.enable_execute_command ? merge({ "initProcessEnabled" : true }, var.linux_parameters) : merge({ "initProcessEnabled" : false }, var.linux_parameters) + + health_check = length(var.health_check) > 0 ? merge({ + interval = 30, + retries = 3, + timeout = 5 + }, var.health_check) : null + + definition = { + command = length(var.command) > 0 ? var.command : null + cpu = var.cpu + dependsOn = length(var.dependencies) > 0 ? var.dependencies : null # depends_on is a reserved word + disableNetworking = local.is_not_windows ? var.disable_networking : null + dnsSearchDomains = local.is_not_windows && length(var.dns_search_domains) > 0 ? var.dns_search_domains : null + dnsServers = local.is_not_windows && length(var.dns_servers) > 0 ? var.dns_servers : null + dockerLabels = length(var.docker_labels) > 0 ? var.docker_labels : null + dockerSecurityOptions = length(var.docker_security_options) > 0 ? var.docker_security_options : null + entrypoint = length(var.entrypoint) > 0 ? var.entrypoint : null + environment = var.environment + environmentFiles = length(var.environment_files) > 0 ? var.environment_files : null + essential = var.essential + extraHosts = local.is_not_windows && length(var.extra_hosts) > 0 ? var.extra_hosts : null + firelensConfiguration = length(var.firelens_configuration) > 0 ? var.firelens_configuration : null + healthCheck = local.health_check + hostname = var.hostname + image = var.image + interactive = var.interactive + links = local.is_not_windows && length(var.links) > 0 ? var.links : null + linuxParameters = local.is_not_windows && length(local.linux_parameters) > 0 ? local.linux_parameters : null + logConfiguration = length(local.log_configuration) > 0 ? local.log_configuration : null + memory = var.memory + memoryReservation = var.memory_reservation + mountPoints = var.mount_points + name = var.name + portMappings = var.port_mappings + privileged = local.is_not_windows ? var.privileged : null + pseudoTerminal = var.pseudo_terminal + readonlyRootFilesystem = local.is_not_windows ? var.readonly_root_filesystem : null + repositoryCredentials = length(var.repository_credentials) > 0 ? var.repository_credentials : null + resourceRequirements = length(var.resource_requirements) > 0 ? var.resource_requirements : null + secrets = length(var.secrets) > 0 ? var.secrets : null + startTimeout = var.start_timeout + stopTimeout = var.stop_timeout + systemControls = length(var.system_controls) > 0 ? var.system_controls : [] + ulimits = local.is_not_windows && length(var.ulimits) > 0 ? var.ulimits : null + user = local.is_not_windows ? var.user : null + volumesFrom = var.volumes_from + workingDirectory = var.working_directory + } + + # Strip out all null values, ECS API will provide defaults in place of null/empty values + container_definition = { for k, v in local.definition : k => v if v != null } +} + +resource "aws_cloudwatch_log_group" "this" { + count = var.create_cloudwatch_log_group && var.enable_cloudwatch_logging ? 1 : 0 + + name = var.cloudwatch_log_group_use_name_prefix ? null : local.log_group_name + name_prefix = var.cloudwatch_log_group_use_name_prefix ? "${local.log_group_name}-" : null + retention_in_days = var.cloudwatch_log_group_retention_in_days + kms_key_id = var.cloudwatch_log_group_kms_key_id + + tags = var.tags +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/container-definition/outputs.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/container-definition/outputs.tf new file mode 100644 index 000000000..2f26967a3 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/container-definition/outputs.tf @@ -0,0 +1,22 @@ +################################################################################ +# Container Definition +################################################################################ + +output "container_definition" { + description = "Container definition" + value = local.container_definition +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +output "cloudwatch_log_group_name" { + description = "Name of CloudWatch log group created" + value = try(aws_cloudwatch_log_group.this[0].name, null) +} + +output "cloudwatch_log_group_arn" { + description = "ARN of CloudWatch log group created" + value = try(aws_cloudwatch_log_group.this[0].arn, null) +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/container-definition/variables.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/container-definition/variables.tf new file mode 100644 index 000000000..0f88b9de1 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/container-definition/variables.tf @@ -0,0 +1,323 @@ +variable "operating_system_family" { + description = "The OS family for task" + type = string + default = "LINUX" +} + +################################################################################ +# Container Definition +################################################################################ + +variable "command" { + description = "The command that's passed to the container" + type = list(string) + default = [] +} + +variable "cpu" { + description = "The number of cpu units to reserve for the container. This is optional for tasks using Fargate launch type and the total amount of `cpu` of all containers in a task will need to be lower than the task-level cpu value" + type = number + default = null +} + +variable "dependencies" { + description = "The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed. The condition can be one of START, COMPLETE, SUCCESS or HEALTHY" + type = list(object({ + condition = string + containerName = string + })) + default = [] +} + +variable "disable_networking" { + description = "When this parameter is true, networking is disabled within the container" + type = bool + default = null +} + +variable "dns_search_domains" { + description = "Container DNS search domains. A list of DNS search domains that are presented to the container" + type = list(string) + default = [] +} + +variable "dns_servers" { + description = "Container DNS servers. This is a list of strings specifying the IP addresses of the DNS servers" + type = list(string) + default = [] +} + +variable "docker_labels" { + description = "A key/value map of labels to add to the container" + type = map(string) + default = {} +} + +variable "docker_security_options" { + description = "A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems. This field isn't valid for containers in tasks using the Fargate launch type" + type = list(string) + default = [] +} + +variable "enable_execute_command" { + description = "Specifies whether to enable Amazon ECS Exec for the tasks within the service" + type = bool + default = false +} + +variable "entrypoint" { + description = "The entry point that is passed to the container" + type = list(string) + default = [] +} + +variable "environment" { + description = "The environment variables to pass to the container" + type = list(object({ + name = string + value = string + })) + default = [] +} + +variable "environment_files" { + description = "A list of files containing the environment variables to pass to a container" + type = list(object({ + value = string + type = string + })) + default = [] +} + +variable "essential" { + description = "If the `essential` parameter of a container is marked as `true`, and that container fails or stops for any reason, all other containers that are part of the task are stopped" + type = bool + default = null +} + +variable "extra_hosts" { + description = "A list of hostnames and IP address mappings to append to the `/etc/hosts` file on the container" + type = list(object({ + hostname = string + ipAddress = string + })) + default = [] +} + +variable "firelens_configuration" { + description = "The FireLens configuration for the container. This is used to specify and configure a log router for container logs. For more information, see [Custom Log Routing](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html) in the Amazon Elastic Container Service Developer Guide" + type = any + default = {} +} + +variable "health_check" { + description = "The container health check command and associated configuration parameters for the container. See [HealthCheck](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_HealthCheck.html)" + type = any + default = {} +} + +variable "hostname" { + description = "The hostname to use for your container" + type = string + default = null +} + +variable "image" { + description = "The image used to start a container. This string is passed directly to the Docker daemon. By default, images in the Docker Hub registry are available. Other repositories are specified with either `repository-url/image:tag` or `repository-url/image@digest`" + type = string + default = null +} + +variable "interactive" { + description = "When this parameter is `true`, you can deploy containerized applications that require `stdin` or a `tty` to be allocated" + type = bool + default = false +} + +variable "links" { + description = "The links parameter allows containers to communicate with each other without the need for port mappings. This parameter is only supported if the network mode of a task definition is `bridge`" + type = list(string) + default = [] +} + +variable "linux_parameters" { + description = "Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html)" + type = any + default = {} +} + +variable "log_configuration" { + description = "The log configuration for the container. For more information see [LogConfiguration](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_LogConfiguration.html)" + type = any + default = {} +} + +variable "memory" { + description = "The amount (in MiB) of memory to present to the container. If your container attempts to exceed the memory specified here, the container is killed. The total amount of memory reserved for all containers within a task must be lower than the task `memory` value, if one is specified" + type = number + default = null +} + +variable "memory_reservation" { + description = "The soft limit (in MiB) of memory to reserve for the container. When system memory is under heavy contention, Docker attempts to keep the container memory to this soft limit. However, your container can consume more memory when it needs to, up to either the hard limit specified with the `memory` parameter (if applicable), or all of the available memory on the container instance" + type = number + default = null +} + +variable "mount_points" { + description = "The mount points for data volumes in your container" + type = list(any) + default = [] +} + +variable "name" { + description = "The name of a container. If you're linking multiple containers together in a task definition, the name of one container can be entered in the links of another container to connect the containers. Up to 255 letters (uppercase and lowercase), numbers, underscores, and hyphens are allowed" + type = string + default = null +} + +variable "port_mappings" { + description = "The list of port mappings for the container. Port mappings allow containers to access ports on the host container instance to send or receive traffic. For task definitions that use the awsvpc network mode, only specify the containerPort. The hostPort can be left blank or it must be the same value as the containerPort" + type = list(any) + default = [] +} + +variable "privileged" { + description = "When this parameter is true, the container is given elevated privileges on the host container instance (similar to the root user)" + type = bool + default = false +} + +variable "pseudo_terminal" { + description = "When this parameter is true, a `TTY` is allocated" + type = bool + default = false +} + +variable "readonly_root_filesystem" { + description = "When this parameter is true, the container is given read-only access to its root file system" + type = bool + default = true +} + +variable "repository_credentials" { + description = "Container repository credentials; required when using a private repo. This map currently supports a single key; \"credentialsParameter\", which should be the ARN of a Secrets Manager's secret holding the credentials" + type = map(string) + default = {} +} + +variable "resource_requirements" { + description = "The type and amount of a resource to assign to a container. The only supported resource is a GPU" + type = list(object({ + type = string + value = string + })) + default = [] +} + +variable "secrets" { + description = "The secrets to pass to the container. For more information, see [Specifying Sensitive Data](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) in the Amazon Elastic Container Service Developer Guide" + type = list(object({ + name = string + valueFrom = string + })) + default = [] +} + +variable "start_timeout" { + description = "Time duration (in seconds) to wait before giving up on resolving dependencies for a container" + type = number + default = 30 +} + +variable "stop_timeout" { + description = "Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own" + type = number + default = 120 +} + +variable "system_controls" { + description = "A list of namespaced kernel parameters to set in the container" + type = list(map(string)) + default = [] +} + +variable "ulimits" { + description = "A list of ulimits to set in the container. If a ulimit value is specified in a task definition, it overrides the default values set by Docker" + type = list(object({ + hardLimit = number + name = string + softLimit = number + })) + default = [] +} + +variable "user" { + description = "The user to run as inside the container. Can be any of these formats: user, user:group, uid, uid:gid, user:gid, uid:group. The default (null) will use the container's configured `USER` directive or root if not set" + type = string + default = null +} + +variable "volumes_from" { + description = "Data volumes to mount from another container" + type = list(any) + default = [] +} + +variable "working_directory" { + description = "The working directory to run commands inside the container" + type = string + default = null +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +variable "service" { + description = "The name of the service that the container definition is associated with" + type = string + default = "" +} + +variable "enable_cloudwatch_logging" { + description = "Determines whether CloudWatch logging is configured for this container definition. Set to `false` to use other logging drivers" + type = bool + default = true +} + +variable "create_cloudwatch_log_group" { + description = "Determines whether a log group is created by this module. If not, AWS will automatically create one if logging is enabled" + type = bool + default = true +} + +variable "cloudwatch_log_group_name" { + description = "Custom name of CloudWatch log group for a service associated with the container definition" + type = string + default = null +} + +variable "cloudwatch_log_group_use_name_prefix" { + description = "Determines whether the log group name should be used as a prefix" + type = bool + default = false +} + +variable "cloudwatch_log_group_retention_in_days" { + description = "Number of days to retain log events. Default is 30 days" + type = number + default = 30 +} + +variable "cloudwatch_log_group_kms_key_id" { + description = "If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html)" + type = string + default = null +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/container-definition/versions.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/container-definition/versions.tf new file mode 100644 index 000000000..17c7f7b40 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/container-definition/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws5" + version = ">= 4.66.1" + } + } +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/service/README.md b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/service/README.md new file mode 100644 index 000000000..799e99933 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/service/README.md @@ -0,0 +1,358 @@ +# Amazon ECS Service Module + +Configuration in this directory creates an Amazon ECS Service and associated resources. + +Some notable configurations to be aware of when using this module: +1. `desired_count`/`scale` is always ignored; the module is designed to utilize autoscaling by default (though it can be disabled) +2. The default configuration is intended for `FARGATE` use + +For more details see the [design doc](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/docs/README.md) + +### Logging + +Please refer to [FireLens examples repository](https://github.com/aws-samples/amazon-ecs-firelens-examples) for logging configuration examples for FireLens on Amazon ECS and AWS Fargate. + +## Usage + +```hcl +module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + + name = "example" + cluster_arn = "arn:aws:ecs:us-west-2:123456789012:cluster/default" + + cpu = 1024 + memory = 4096 + + # Container definition(s) + container_definitions = { + + fluent-bit = { + cpu = 512 + memory = 1024 + essential = true + image = "906394416424.dkr.ecr.us-west-2.amazonaws.com/aws-for-fluent-bit:stable" + firelens_configuration = { + type = "fluentbit" + } + memory_reservation = 50 + } + + ecs-sample = { + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = "ecs-sample" + containerPort = 80 + protocol = "tcp" + } + ] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + dependencies = [{ + containerName = "fluent-bit" + condition = "START" + }] + + enable_cloudwatch_logging = false + log_configuration = { + logDriver = "awsfirelens" + options = { + Name = "firehose" + region = "eu-west-1" + delivery_stream = "my-stream" + log-driver-buffer-limit = "2097152" + } + } + memory_reservation = 100 + } + } + + service_connect_configuration = { + namespace = "example" + service = { + client_alias = { + port = 80 + dns_name = "ecs-sample" + } + port_name = "ecs-sample" + discovery_name = "ecs-sample" + } + } + + load_balancer = { + service = { + target_group_arn = "arn:aws:elasticloadbalancing:eu-west-1:1234567890:targetgroup/bluegreentarget1/209a844cd01825a4" + container_name = "ecs-sample" + container_port = 80 + } + } + + subnet_ids = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] + security_group_rules = { + alb_ingress_3000 = { + type = "ingress" + from_port = 80 + to_port = 80 + protocol = "tcp" + description = "Service port" + source_security_group_id = "sg-12345678" + } + egress_all = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + } + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + +## Conditional Creation + +The following values are provided to toggle on/off creation of the associated resources as desired: + +```hcl +module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + + # Disable creation of service and all resources + create = false + + # Enable ECS Exec + enable_execute_command = true + + # Disable creation of the service IAM role; `iam_role_arn` should be provided + create_iam_role = false + + # Disable creation of the task definition; `task_definition_arn` should be provided + create_task_definition = false + + # Disable creation of the task execution IAM role; `task_exec_iam_role_arn` should be provided + create_task_exec_iam_role = false + + # Disable creation of the task execution IAM role policy + create_task_exec_policy = false + + # Disable creation of the tasks IAM role; `tasks_iam_role_arn` should be provided + create_tasks_iam_role = false + + # Disable creation of the service security group + create_security_group = false + + # ... omitted +} +``` + +## Examples + +- [ECS Cluster Complete](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) +- [ECS Cluster w/ EC2 Autoscaling Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/ec2-autoscaling) +- [ECS Cluster w/ Fargate Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/fargate) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.66.1 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.66.1 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [container\_definition](#module\_container\_definition) | ../container-definition | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_appautoscaling_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource | +| [aws_appautoscaling_scheduled_action.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_scheduled_action) | resource | +| [aws_appautoscaling_target.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_target) | resource | +| [aws_ecs_service.ignore_task_definition](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | +| [aws_ecs_service.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | +| [aws_ecs_task_definition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource | +| [aws_ecs_task_set.ignore_task_definition](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_set) | resource | +| [aws_ecs_task_set.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_set) | resource | +| [aws_iam_policy.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy_attachment.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.task_exec_additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_ecs_task_definition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ecs_task_definition) | data source | +| [aws_iam_policy_document.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.service_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.task_exec_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.tasks_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | +| [aws_subnet.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [alarms](#input\_alarms) | Information about the CloudWatch alarms | `any` | `{}` | no | +| [assign\_public\_ip](#input\_assign\_public\_ip) | Assign a public IP address to the ENI (Fargate launch type only) | `bool` | `false` | no | +| [autoscaling\_max\_capacity](#input\_autoscaling\_max\_capacity) | Maximum number of tasks to run in your service | `number` | `10` | no | +| [autoscaling\_min\_capacity](#input\_autoscaling\_min\_capacity) | Minimum number of tasks to run in your service | `number` | `1` | no | +| [autoscaling\_policies](#input\_autoscaling\_policies) | Map of autoscaling policies to create for the service | `any` |
{
"cpu": {
"policy_type": "TargetTrackingScaling",
"target_tracking_scaling_policy_configuration": {
"predefined_metric_specification": {
"predefined_metric_type": "ECSServiceAverageCPUUtilization"
}
}
},
"memory": {
"policy_type": "TargetTrackingScaling",
"target_tracking_scaling_policy_configuration": {
"predefined_metric_specification": {
"predefined_metric_type": "ECSServiceAverageMemoryUtilization"
}
}
}
}
| no | +| [autoscaling\_scheduled\_actions](#input\_autoscaling\_scheduled\_actions) | Map of autoscaling scheduled actions to create for the service | `any` | `{}` | no | +| [capacity\_provider\_strategy](#input\_capacity\_provider\_strategy) | Capacity provider strategies to use for the service. Can be one or more | `any` | `{}` | no | +| [cluster\_arn](#input\_cluster\_arn) | ARN of the ECS cluster where the resources will be provisioned | `string` | `""` | no | +| [container\_definition\_defaults](#input\_container\_definition\_defaults) | A map of default values for [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html) created by `container_definitions` | `any` | `{}` | no | +| [container\_definitions](#input\_container\_definitions) | A map of valid [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html). Please note that you should only provide values that are part of the container definition document | `any` | `{}` | no | +| [cpu](#input\_cpu) | Number of cpu units used by the task. If the `requires_compatibilities` is `FARGATE` this field is required | `number` | `1024` | no | +| [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no | +| [create\_iam\_role](#input\_create\_iam\_role) | Determines whether the ECS service IAM role should be created | `bool` | `true` | no | +| [create\_security\_group](#input\_create\_security\_group) | Determines if a security group is created | `bool` | `true` | no | +| [create\_service](#input\_create\_service) | Determines whether service resource will be created (set to `false` in case you want to create task definition only) | `bool` | `true` | no | +| [create\_task\_definition](#input\_create\_task\_definition) | Determines whether to create a task definition or use existing/provided | `bool` | `true` | no | +| [create\_task\_exec\_iam\_role](#input\_create\_task\_exec\_iam\_role) | Determines whether the ECS task definition IAM role should be created | `bool` | `true` | no | +| [create\_task\_exec\_policy](#input\_create\_task\_exec\_policy) | Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters | `bool` | `true` | no | +| [create\_tasks\_iam\_role](#input\_create\_tasks\_iam\_role) | Determines whether the ECS tasks IAM role should be created | `bool` | `true` | no | +| [deployment\_circuit\_breaker](#input\_deployment\_circuit\_breaker) | Configuration block for deployment circuit breaker | `any` | `{}` | no | +| [deployment\_controller](#input\_deployment\_controller) | Configuration block for deployment controller configuration | `any` | `{}` | no | +| [deployment\_maximum\_percent](#input\_deployment\_maximum\_percent) | Upper limit (as a percentage of the service's `desired_count`) of the number of running tasks that can be running in a service during a deployment | `number` | `200` | no | +| [deployment\_minimum\_healthy\_percent](#input\_deployment\_minimum\_healthy\_percent) | Lower limit (as a percentage of the service's `desired_count`) of the number of running tasks that must remain running and healthy in a service during a deployment | `number` | `66` | no | +| [desired\_count](#input\_desired\_count) | Number of instances of the task definition to place and keep running | `number` | `1` | no | +| [enable\_autoscaling](#input\_enable\_autoscaling) | Determines whether to enable autoscaling for the service | `bool` | `true` | no | +| [enable\_ecs\_managed\_tags](#input\_enable\_ecs\_managed\_tags) | Specifies whether to enable Amazon ECS managed tags for the tasks within the service | `bool` | `true` | no | +| [enable\_execute\_command](#input\_enable\_execute\_command) | Specifies whether to enable Amazon ECS Exec for the tasks within the service | `bool` | `false` | no | +| [ephemeral\_storage](#input\_ephemeral\_storage) | The amount of ephemeral storage to allocate for the task. This parameter is used to expand the total amount of ephemeral storage available, beyond the default amount, for tasks hosted on AWS Fargate | `any` | `{}` | no | +| [external\_id](#input\_external\_id) | The external ID associated with the task set | `string` | `null` | no | +| [family](#input\_family) | A unique name for your task definition | `string` | `null` | no | +| [force\_delete](#input\_force\_delete) | Whether to allow deleting the task set without waiting for scaling down to 0 | `bool` | `null` | no | +| [force\_new\_deployment](#input\_force\_new\_deployment) | Enable to force a new task deployment of the service. This can be used to update tasks to use a newer Docker image with same image/tag combination, roll Fargate tasks onto a newer platform version, or immediately deploy `ordered_placement_strategy` and `placement_constraints` updates | `bool` | `true` | no | +| [health\_check\_grace\_period\_seconds](#input\_health\_check\_grace\_period\_seconds) | Seconds to ignore failing load balancer health checks on newly instantiated tasks to prevent premature shutdown, up to 2147483647. Only valid for services configured to use load balancers | `number` | `null` | no | +| [iam\_role\_arn](#input\_iam\_role\_arn) | Existing IAM role ARN | `string` | `null` | no | +| [iam\_role\_description](#input\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [iam\_role\_name](#input\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [iam\_role\_path](#input\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [iam\_role\_permissions\_boundary](#input\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [iam\_role\_statements](#input\_iam\_role\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [iam\_role\_tags](#input\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [iam\_role\_use\_name\_prefix](#input\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [ignore\_task\_definition\_changes](#input\_ignore\_task\_definition\_changes) | Whether changes to service `task_definition` changes should be ignored | `bool` | `false` | no | +| [inference\_accelerator](#input\_inference\_accelerator) | Configuration block(s) with Inference Accelerators settings | `any` | `{}` | no | +| [ipc\_mode](#input\_ipc\_mode) | IPC resource namespace to be used for the containers in the task The valid values are `host`, `task`, and `none` | `string` | `null` | no | +| [launch\_type](#input\_launch\_type) | Launch type on which to run your service. The valid values are `EC2`, `FARGATE`, and `EXTERNAL`. Defaults to `FARGATE` | `string` | `"FARGATE"` | no | +| [load\_balancer](#input\_load\_balancer) | Configuration block for load balancers | `any` | `{}` | no | +| [memory](#input\_memory) | Amount (in MiB) of memory used by the task. If the `requires_compatibilities` is `FARGATE` this field is required | `number` | `2048` | no | +| [name](#input\_name) | Name of the service (up to 255 letters, numbers, hyphens, and underscores) | `string` | `null` | no | +| [network\_mode](#input\_network\_mode) | Docker networking mode to use for the containers in the task. Valid values are `none`, `bridge`, `awsvpc`, and `host` | `string` | `"awsvpc"` | no | +| [ordered\_placement\_strategy](#input\_ordered\_placement\_strategy) | Service level strategy rules that are taken into consideration during task placement. List from top to bottom in order of precedence | `any` | `{}` | no | +| [pid\_mode](#input\_pid\_mode) | Process namespace to use for the containers in the task. The valid values are `host` and `task` | `string` | `null` | no | +| [placement\_constraints](#input\_placement\_constraints) | Configuration block for rules that are taken into consideration during task placement (up to max of 10). This is set at the service, see `task_definition_placement_constraints` for setting at the task definition | `any` | `{}` | no | +| [platform\_version](#input\_platform\_version) | Platform version on which to run your service. Only applicable for `launch_type` set to `FARGATE`. Defaults to `LATEST` | `string` | `null` | no | +| [propagate\_tags](#input\_propagate\_tags) | Specifies whether to propagate the tags from the task definition or the service to the tasks. The valid values are `SERVICE` and `TASK_DEFINITION` | `string` | `null` | no | +| [proxy\_configuration](#input\_proxy\_configuration) | Configuration block for the App Mesh proxy | `any` | `{}` | no | +| [requires\_compatibilities](#input\_requires\_compatibilities) | Set of launch types required by the task. The valid values are `EC2` and `FARGATE` | `list(string)` |
[
"FARGATE"
]
| no | +| [runtime\_platform](#input\_runtime\_platform) | Configuration block for `runtime_platform` that containers in your task may use | `any` |
{
"cpu_architecture": "X86_64",
"operating_system_family": "LINUX"
}
| no | +| [scale](#input\_scale) | A floating-point percentage of the desired number of tasks to place and keep running in the task set | `any` | `{}` | no | +| [scheduling\_strategy](#input\_scheduling\_strategy) | Scheduling strategy to use for the service. The valid values are `REPLICA` and `DAEMON`. Defaults to `REPLICA` | `string` | `null` | no | +| [security\_group\_description](#input\_security\_group\_description) | Description of the security group created | `string` | `null` | no | +| [security\_group\_ids](#input\_security\_group\_ids) | List of security groups to associate with the task or service | `list(string)` | `[]` | no | +| [security\_group\_name](#input\_security\_group\_name) | Name to use on security group created | `string` | `null` | no | +| [security\_group\_rules](#input\_security\_group\_rules) | Security group rules to add to the security group created | `any` | `{}` | no | +| [security\_group\_tags](#input\_security\_group\_tags) | A map of additional tags to add to the security group created | `map(string)` | `{}` | no | +| [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Determines whether the security group name (`security_group_name`) is used as a prefix | `bool` | `true` | no | +| [service\_connect\_configuration](#input\_service\_connect\_configuration) | The ECS Service Connect configuration for this service to discover and connect to services, and be discovered by, and connected from, other services within a namespace | `any` | `{}` | no | +| [service\_registries](#input\_service\_registries) | Service discovery registries for the service | `any` | `{}` | no | +| [service\_tags](#input\_service\_tags) | A map of additional tags to add to the service | `map(string)` | `{}` | no | +| [skip\_destroy](#input\_skip\_destroy) | If true, the task is not deleted when the service is deleted | `bool` | `null` | no | +| [subnet\_ids](#input\_subnet\_ids) | List of subnets to associate with the task or service | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [task\_definition\_arn](#input\_task\_definition\_arn) | Existing task definition ARN. Required when `create_task_definition` is `false` | `string` | `null` | no | +| [task\_definition\_placement\_constraints](#input\_task\_definition\_placement\_constraints) | Configuration block for rules that are taken into consideration during task placement (up to max of 10). This is set at the task definition, see `placement_constraints` for setting at the service | `any` | `{}` | no | +| [task\_exec\_iam\_role\_arn](#input\_task\_exec\_iam\_role\_arn) | Existing IAM role ARN | `string` | `null` | no | +| [task\_exec\_iam\_role\_description](#input\_task\_exec\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [task\_exec\_iam\_role\_max\_session\_duration](#input\_task\_exec\_iam\_role\_max\_session\_duration) | Maximum session duration (in seconds) for ECS task execution role. Default is 3600. | `number` | `null` | no | +| [task\_exec\_iam\_role\_name](#input\_task\_exec\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [task\_exec\_iam\_role\_path](#input\_task\_exec\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [task\_exec\_iam\_role\_permissions\_boundary](#input\_task\_exec\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [task\_exec\_iam\_role\_policies](#input\_task\_exec\_iam\_role\_policies) | Map of IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_tags](#input\_task\_exec\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_use\_name\_prefix](#input\_task\_exec\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [task\_exec\_iam\_statements](#input\_task\_exec\_iam\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [task\_exec\_secret\_arns](#input\_task\_exec\_secret\_arns) | List of SecretsManager secret ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:secretsmanager:*:*:secret:*"
]
| no | +| [task\_exec\_ssm\_param\_arns](#input\_task\_exec\_ssm\_param\_arns) | List of SSM parameter ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/*"
]
| no | +| [task\_tags](#input\_task\_tags) | A map of additional tags to add to the task definition/set created | `map(string)` | `{}` | no | +| [tasks\_iam\_role\_arn](#input\_tasks\_iam\_role\_arn) | Existing IAM role ARN | `string` | `null` | no | +| [tasks\_iam\_role\_description](#input\_tasks\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [tasks\_iam\_role\_name](#input\_tasks\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [tasks\_iam\_role\_path](#input\_tasks\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [tasks\_iam\_role\_permissions\_boundary](#input\_tasks\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [tasks\_iam\_role\_policies](#input\_tasks\_iam\_role\_policies) | Map of IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | +| [tasks\_iam\_role\_statements](#input\_tasks\_iam\_role\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [tasks\_iam\_role\_tags](#input\_tasks\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [tasks\_iam\_role\_use\_name\_prefix](#input\_tasks\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`tasks_iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [timeouts](#input\_timeouts) | Create, update, and delete timeout configurations for the service | `map(string)` | `{}` | no | +| [triggers](#input\_triggers) | Map of arbitrary keys and values that, when changed, will trigger an in-place update (redeployment). Useful with `timestamp()` | `any` | `{}` | no | +| [volume](#input\_volume) | Configuration block for volumes that containers in your task may use | `any` | `{}` | no | +| [wait\_for\_steady\_state](#input\_wait\_for\_steady\_state) | If true, Terraform will wait for the service to reach a steady state before continuing. Default is `false` | `bool` | `null` | no | +| [wait\_until\_stable](#input\_wait\_until\_stable) | Whether terraform should wait until the task set has reached `STEADY_STATE` | `bool` | `null` | no | +| [wait\_until\_stable\_timeout](#input\_wait\_until\_stable\_timeout) | Wait timeout for task set to reach `STEADY_STATE`. Valid time units include `ns`, `us` (or µs), `ms`, `s`, `m`, and `h`. Default `10m` | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [autoscaling\_policies](#output\_autoscaling\_policies) | Map of autoscaling policies and their attributes | +| [autoscaling\_scheduled\_actions](#output\_autoscaling\_scheduled\_actions) | Map of autoscaling scheduled actions and their attributes | +| [container\_definitions](#output\_container\_definitions) | Container definitions | +| [iam\_role\_arn](#output\_iam\_role\_arn) | Service IAM role ARN | +| [iam\_role\_name](#output\_iam\_role\_name) | Service IAM role name | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Stable and unique string identifying the service IAM role | +| [id](#output\_id) | ARN that identifies the service | +| [name](#output\_name) | Name of the service | +| [security\_group\_arn](#output\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | +| [security\_group\_id](#output\_security\_group\_id) | ID of the security group | +| [task\_definition\_arn](#output\_task\_definition\_arn) | Full ARN of the Task Definition (including both `family` and `revision`) | +| [task\_definition\_family](#output\_task\_definition\_family) | The unique name of the task definition | +| [task\_definition\_family\_revision](#output\_task\_definition\_family\_revision) | The family and revision (family:revision) of the task definition | +| [task\_definition\_revision](#output\_task\_definition\_revision) | Revision of the task in a particular family | +| [task\_exec\_iam\_role\_arn](#output\_task\_exec\_iam\_role\_arn) | Task execution IAM role ARN | +| [task\_exec\_iam\_role\_name](#output\_task\_exec\_iam\_role\_name) | Task execution IAM role name | +| [task\_exec\_iam\_role\_unique\_id](#output\_task\_exec\_iam\_role\_unique\_id) | Stable and unique string identifying the task execution IAM role | +| [task\_set\_arn](#output\_task\_set\_arn) | The Amazon Resource Name (ARN) that identifies the task set | +| [task\_set\_id](#output\_task\_set\_id) | The ID of the task set | +| [task\_set\_stability\_status](#output\_task\_set\_stability\_status) | The stability status. This indicates whether the task set has reached a steady state | +| [task\_set\_status](#output\_task\_set\_status) | The status of the task set | +| [tasks\_iam\_role\_arn](#output\_tasks\_iam\_role\_arn) | Tasks IAM role ARN | +| [tasks\_iam\_role\_name](#output\_tasks\_iam\_role\_name) | Tasks IAM role name | +| [tasks\_iam\_role\_unique\_id](#output\_tasks\_iam\_role\_unique\_id) | Stable and unique string identifying the tasks IAM role | + + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/service/main.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/service/main.tf new file mode 100644 index 000000000..75cc0ef62 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/service/main.tf @@ -0,0 +1,1358 @@ +data "aws_region" "current" {} +data "aws_partition" "current" {} +data "aws_caller_identity" "current" {} + +locals { + account_id = data.aws_caller_identity.current.account_id + partition = data.aws_partition.current.partition + region = data.aws_region.current.name +} + +################################################################################ +# Service +################################################################################ + +locals { + # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/deployment-type-external.html + is_external_deployment = try(var.deployment_controller.type, null) == "EXTERNAL" + is_daemon = var.scheduling_strategy == "DAEMON" + is_fargate = var.launch_type == "FARGATE" + + # Flattened `network_configuration` + network_configuration = { + assign_public_ip = var.assign_public_ip + security_groups = flatten(concat([try(aws_security_group.this[0].id, [])], var.security_group_ids)) + subnets = var.subnet_ids + } + + create_service = var.create && var.create_service +} + +resource "aws_ecs_service" "this" { + count = local.create_service && !var.ignore_task_definition_changes ? 1 : 0 + + dynamic "alarms" { + for_each = length(var.alarms) > 0 ? [var.alarms] : [] + + content { + alarm_names = alarms.value.alarm_names + enable = try(alarms.value.enable, true) + rollback = try(alarms.value.rollback, true) + } + } + + dynamic "capacity_provider_strategy" { + # Set by task set if deployment controller is external + for_each = { for k, v in var.capacity_provider_strategy : k => v if !local.is_external_deployment } + + content { + base = try(capacity_provider_strategy.value.base, null) + capacity_provider = capacity_provider_strategy.value.capacity_provider + weight = try(capacity_provider_strategy.value.weight, null) + } + } + + cluster = var.cluster_arn + + dynamic "deployment_circuit_breaker" { + for_each = length(var.deployment_circuit_breaker) > 0 ? [var.deployment_circuit_breaker] : [] + + content { + enable = deployment_circuit_breaker.value.enable + rollback = deployment_circuit_breaker.value.rollback + } + } + + dynamic "deployment_controller" { + for_each = length(var.deployment_controller) > 0 ? [var.deployment_controller] : [] + + content { + type = try(deployment_controller.value.type, null) + } + } + + deployment_maximum_percent = local.is_daemon || local.is_external_deployment ? null : var.deployment_maximum_percent + deployment_minimum_healthy_percent = local.is_daemon || local.is_external_deployment ? null : var.deployment_minimum_healthy_percent + desired_count = local.is_daemon || local.is_external_deployment ? null : var.desired_count + enable_ecs_managed_tags = var.enable_ecs_managed_tags + enable_execute_command = var.enable_execute_command + force_new_deployment = local.is_external_deployment ? null : var.force_new_deployment + health_check_grace_period_seconds = var.health_check_grace_period_seconds + iam_role = local.iam_role_arn + launch_type = local.is_external_deployment || length(var.capacity_provider_strategy) > 0 ? null : var.launch_type + + dynamic "load_balancer" { + # Set by task set if deployment controller is external + for_each = { for k, v in var.load_balancer : k => v if !local.is_external_deployment } + + content { + container_name = load_balancer.value.container_name + container_port = load_balancer.value.container_port + elb_name = try(load_balancer.value.elb_name, null) + target_group_arn = try(load_balancer.value.target_group_arn, null) + } + } + + name = var.name + + dynamic "network_configuration" { + # Set by task set if deployment controller is external + for_each = var.network_mode == "awsvpc" && !local.is_external_deployment ? [local.network_configuration] : [] + + content { + assign_public_ip = network_configuration.value.assign_public_ip + security_groups = network_configuration.value.security_groups + subnets = network_configuration.value.subnets + } + } + + dynamic "ordered_placement_strategy" { + for_each = var.ordered_placement_strategy + + content { + field = try(ordered_placement_strategy.value.field, null) + type = ordered_placement_strategy.value.type + } + } + + dynamic "placement_constraints" { + for_each = var.placement_constraints + + content { + expression = try(placement_constraints.value.expression, null) + type = placement_constraints.value.type + } + } + + # Set by task set if deployment controller is external + platform_version = local.is_fargate && !local.is_external_deployment ? var.platform_version : null + scheduling_strategy = local.is_fargate ? "REPLICA" : var.scheduling_strategy + + dynamic "service_connect_configuration" { + for_each = length(var.service_connect_configuration) > 0 ? [var.service_connect_configuration] : [] + + content { + enabled = try(service_connect_configuration.value.enabled, true) + + dynamic "log_configuration" { + for_each = try([service_connect_configuration.value.log_configuration], []) + + content { + log_driver = try(log_configuration.value.log_driver, null) + options = try(log_configuration.value.options, null) + + dynamic "secret_option" { + for_each = try(log_configuration.value.secret_option, []) + + content { + name = secret_option.value.name + value_from = secret_option.value.value_from + } + } + } + } + + namespace = lookup(service_connect_configuration.value, "namespace", null) + + dynamic "service" { + for_each = try([service_connect_configuration.value.service], []) + + content { + + dynamic "client_alias" { + for_each = try([service.value.client_alias], []) + + content { + dns_name = try(client_alias.value.dns_name, null) + port = client_alias.value.port + } + } + + discovery_name = try(service.value.discovery_name, null) + ingress_port_override = try(service.value.ingress_port_override, null) + port_name = service.value.port_name + } + } + } + } + + dynamic "service_registries" { + # Set by task set if deployment controller is external + for_each = length(var.service_registries) > 0 ? [{ for k, v in var.service_registries : k => v if !local.is_external_deployment }] : [] + + content { + container_name = try(service_registries.value.container_name, null) + container_port = try(service_registries.value.container_port, null) + port = try(service_registries.value.port, null) + registry_arn = service_registries.value.registry_arn + } + } + + task_definition = local.task_definition + triggers = var.triggers + wait_for_steady_state = var.wait_for_steady_state + + propagate_tags = var.propagate_tags + tags = merge(var.tags, var.service_tags) + + timeouts { + create = try(var.timeouts.create, null) + update = try(var.timeouts.update, null) + delete = try(var.timeouts.delete, null) + } + + depends_on = [ + aws_iam_role_policy_attachment.service + ] + + lifecycle { + ignore_changes = [ + desired_count, # Always ignored + ] + } +} + +################################################################################ +# Service - Ignore `task_definition` +################################################################################ + +resource "aws_ecs_service" "ignore_task_definition" { + count = local.create_service && var.ignore_task_definition_changes ? 1 : 0 + + dynamic "alarms" { + for_each = length(var.alarms) > 0 ? [var.alarms] : [] + + content { + alarm_names = alarms.value.alarm_names + enable = try(alarms.value.enable, true) + rollback = try(alarms.value.rollback, true) + } + } + + dynamic "capacity_provider_strategy" { + # Set by task set if deployment controller is external + for_each = { for k, v in var.capacity_provider_strategy : k => v if !local.is_external_deployment } + + content { + base = try(capacity_provider_strategy.value.base, null) + capacity_provider = capacity_provider_strategy.value.capacity_provider + weight = try(capacity_provider_strategy.value.weight, null) + } + } + + cluster = var.cluster_arn + + dynamic "deployment_circuit_breaker" { + for_each = length(var.deployment_circuit_breaker) > 0 ? [var.deployment_circuit_breaker] : [] + + content { + enable = deployment_circuit_breaker.value.enable + rollback = deployment_circuit_breaker.value.rollback + } + } + + dynamic "deployment_controller" { + for_each = length(var.deployment_controller) > 0 ? [var.deployment_controller] : [] + + content { + type = try(deployment_controller.value.type, null) + } + } + + deployment_maximum_percent = local.is_daemon || local.is_external_deployment ? null : var.deployment_maximum_percent + deployment_minimum_healthy_percent = local.is_daemon || local.is_external_deployment ? null : var.deployment_minimum_healthy_percent + desired_count = local.is_daemon || local.is_external_deployment ? null : var.desired_count + enable_ecs_managed_tags = var.enable_ecs_managed_tags + enable_execute_command = var.enable_execute_command + force_new_deployment = local.is_external_deployment ? null : var.force_new_deployment + health_check_grace_period_seconds = var.health_check_grace_period_seconds + iam_role = local.iam_role_arn + launch_type = local.is_external_deployment || length(var.capacity_provider_strategy) > 0 ? null : var.launch_type + + dynamic "load_balancer" { + # Set by task set if deployment controller is external + for_each = { for k, v in var.load_balancer : k => v if !local.is_external_deployment } + + content { + container_name = load_balancer.value.container_name + container_port = load_balancer.value.container_port + elb_name = try(load_balancer.value.elb_name, null) + target_group_arn = try(load_balancer.value.target_group_arn, null) + } + } + + name = var.name + + dynamic "network_configuration" { + # Set by task set if deployment controller is external + for_each = var.network_mode == "awsvpc" ? [{ for k, v in local.network_configuration : k => v if !local.is_external_deployment }] : [] + + content { + assign_public_ip = network_configuration.value.assign_public_ip + security_groups = network_configuration.value.security_groups + subnets = network_configuration.value.subnets + } + } + + dynamic "ordered_placement_strategy" { + for_each = var.ordered_placement_strategy + + content { + field = try(ordered_placement_strategy.value.field, null) + type = ordered_placement_strategy.value.type + } + } + + dynamic "placement_constraints" { + for_each = var.placement_constraints + + content { + expression = try(placement_constraints.value.expression, null) + type = placement_constraints.value.type + } + } + + # Set by task set if deployment controller is external + platform_version = local.is_fargate && !local.is_external_deployment ? var.platform_version : null + scheduling_strategy = local.is_fargate ? "REPLICA" : var.scheduling_strategy + + dynamic "service_connect_configuration" { + for_each = length(var.service_connect_configuration) > 0 ? [var.service_connect_configuration] : [] + + content { + enabled = try(service_connect_configuration.value.enabled, true) + + dynamic "log_configuration" { + for_each = try([service_connect_configuration.value.log_configuration], []) + + content { + log_driver = try(log_configuration.value.log_driver, null) + options = try(log_configuration.value.options, null) + + dynamic "secret_option" { + for_each = try(log_configuration.value.secret_option, []) + + content { + name = secret_option.value.name + value_from = secret_option.value.value_from + } + } + } + } + + namespace = lookup(service_connect_configuration.value, "namespace", null) + + dynamic "service" { + for_each = try([service_connect_configuration.value.service], []) + + content { + + dynamic "client_alias" { + for_each = try([service.value.client_alias], []) + + content { + dns_name = try(client_alias.value.dns_name, null) + port = client_alias.value.port + } + } + + discovery_name = try(service.value.discovery_name, null) + ingress_port_override = try(service.value.ingress_port_override, null) + port_name = service.value.port_name + } + } + } + } + + dynamic "service_registries" { + # Set by task set if deployment controller is external + for_each = length(var.service_registries) > 0 ? [{ for k, v in var.service_registries : k => v if !local.is_external_deployment }] : [] + + content { + container_name = try(service_registries.value.container_name, null) + container_port = try(service_registries.value.container_port, null) + port = try(service_registries.value.port, null) + registry_arn = service_registries.value.registry_arn + } + } + + task_definition = local.task_definition + triggers = var.triggers + wait_for_steady_state = var.wait_for_steady_state + + propagate_tags = var.propagate_tags + tags = var.tags + + timeouts { + create = try(var.timeouts.create, null) + update = try(var.timeouts.update, null) + delete = try(var.timeouts.delete, null) + } + + depends_on = [ + aws_iam_role_policy_attachment.service + ] + + lifecycle { + ignore_changes = [ + desired_count, # Always ignored + task_definition, + load_balancer, + ] + } +} + +################################################################################ +# Service - IAM Role +################################################################################ + +locals { + # Role is not required if task definition uses `awsvpc` network mode or if a load balancer is not used + needs_iam_role = var.network_mode != "awsvpc" && length(var.load_balancer) > 0 + create_iam_role = var.create && var.create_iam_role && local.needs_iam_role + iam_role_arn = local.needs_iam_role ? try(aws_iam_role.service[0].arn, var.iam_role_arn) : null + + iam_role_name = try(coalesce(var.iam_role_name, var.name), "") +} + +data "aws_iam_policy_document" "service_assume" { + count = local.create_iam_role ? 1 : 0 + + statement { + sid = "ECSServiceAssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ecs.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "service" { + count = local.create_iam_role ? 1 : 0 + + name = var.iam_role_use_name_prefix ? null : local.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null + path = var.iam_role_path + description = var.iam_role_description + + assume_role_policy = data.aws_iam_policy_document.service_assume[0].json + permissions_boundary = var.iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.iam_role_tags) +} + +data "aws_iam_policy_document" "service" { + count = local.create_iam_role ? 1 : 0 + + statement { + sid = "ECSService" + resources = ["*"] + + actions = [ + "ec2:Describe*", + "elasticloadbalancing:DeregisterInstancesFromLoadBalancer", + "elasticloadbalancing:DeregisterTargets", + "elasticloadbalancing:Describe*", + "elasticloadbalancing:RegisterInstancesWithLoadBalancer", + "elasticloadbalancing:RegisterTargets" + ] + } + + dynamic "statement" { + for_each = var.iam_role_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, null) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_iam_policy" "service" { + count = local.create_iam_role ? 1 : 0 + + name = var.iam_role_use_name_prefix ? null : local.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null + description = coalesce(var.iam_role_description, "ECS service policy that allows Amazon ECS to make calls to your load balancer on your behalf") + policy = data.aws_iam_policy_document.service[0].json + + tags = merge(var.tags, var.iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "service" { + count = local.create_iam_role ? 1 : 0 + + role = aws_iam_role.service[0].name + policy_arn = aws_iam_policy.service[0].arn +} + +################################################################################ +# Container Definition +################################################################################ + +module "container_definition" { + source = "../container-definition" + + for_each = { for k, v in var.container_definitions : k => v if local.create_task_definition && try(v.create, true) } + + operating_system_family = try(var.runtime_platform.operating_system_family, "LINUX") + + # Container Definition + command = try(each.value.command, var.container_definition_defaults.command, []) + cpu = try(each.value.cpu, var.container_definition_defaults.cpu, null) + dependencies = try(each.value.dependencies, var.container_definition_defaults.dependencies, []) # depends_on is a reserved word + disable_networking = try(each.value.disable_networking, var.container_definition_defaults.disable_networking, null) + dns_search_domains = try(each.value.dns_search_domains, var.container_definition_defaults.dns_search_domains, []) + dns_servers = try(each.value.dns_servers, var.container_definition_defaults.dns_servers, []) + docker_labels = try(each.value.docker_labels, var.container_definition_defaults.docker_labels, {}) + docker_security_options = try(each.value.docker_security_options, var.container_definition_defaults.docker_security_options, []) + enable_execute_command = try(each.value.enable_execute_command, var.container_definition_defaults.enable_execute_command, var.enable_execute_command) + entrypoint = try(each.value.entrypoint, var.container_definition_defaults.entrypoint, []) + environment = try(each.value.environment, var.container_definition_defaults.environment, []) + environment_files = try(each.value.environment_files, var.container_definition_defaults.environment_files, []) + essential = try(each.value.essential, var.container_definition_defaults.essential, null) + extra_hosts = try(each.value.extra_hosts, var.container_definition_defaults.extra_hosts, []) + firelens_configuration = try(each.value.firelens_configuration, var.container_definition_defaults.firelens_configuration, {}) + health_check = try(each.value.health_check, var.container_definition_defaults.health_check, {}) + hostname = try(each.value.hostname, var.container_definition_defaults.hostname, null) + image = try(each.value.image, var.container_definition_defaults.image, null) + interactive = try(each.value.interactive, var.container_definition_defaults.interactive, false) + links = try(each.value.links, var.container_definition_defaults.links, []) + linux_parameters = try(each.value.linux_parameters, var.container_definition_defaults.linux_parameters, {}) + log_configuration = try(each.value.log_configuration, var.container_definition_defaults.log_configuration, {}) + memory = try(each.value.memory, var.container_definition_defaults.memory, null) + memory_reservation = try(each.value.memory_reservation, var.container_definition_defaults.memory_reservation, null) + mount_points = try(each.value.mount_points, var.container_definition_defaults.mount_points, []) + name = try(each.value.name, each.key) + port_mappings = try(each.value.port_mappings, var.container_definition_defaults.port_mappings, []) + privileged = try(each.value.privileged, var.container_definition_defaults.privileged, false) + pseudo_terminal = try(each.value.pseudo_terminal, var.container_definition_defaults.pseudo_terminal, false) + readonly_root_filesystem = try(each.value.readonly_root_filesystem, var.container_definition_defaults.readonly_root_filesystem, true) + repository_credentials = try(each.value.repository_credentials, var.container_definition_defaults.repository_credentials, {}) + resource_requirements = try(each.value.resource_requirements, var.container_definition_defaults.resource_requirements, []) + secrets = try(each.value.secrets, var.container_definition_defaults.secrets, []) + start_timeout = try(each.value.start_timeout, var.container_definition_defaults.start_timeout, 30) + stop_timeout = try(each.value.stop_timeout, var.container_definition_defaults.stop_timeout, 120) + system_controls = try(each.value.system_controls, var.container_definition_defaults.system_controls, []) + ulimits = try(each.value.ulimits, var.container_definition_defaults.ulimits, []) + user = try(each.value.user, var.container_definition_defaults.user, 0) + volumes_from = try(each.value.volumes_from, var.container_definition_defaults.volumes_from, []) + working_directory = try(each.value.working_directory, var.container_definition_defaults.working_directory, null) + + # CloudWatch Log Group + service = var.name + enable_cloudwatch_logging = try(each.value.enable_cloudwatch_logging, var.container_definition_defaults.enable_cloudwatch_logging, true) + create_cloudwatch_log_group = try(each.value.create_cloudwatch_log_group, var.container_definition_defaults.create_cloudwatch_log_group, true) + cloudwatch_log_group_name = try(each.value.cloudwatch_log_group_name, var.container_definition_defaults.cloudwatch_log_group_name, null) + cloudwatch_log_group_use_name_prefix = try(each.value.cloudwatch_log_group_use_name_prefix, var.container_definition_defaults.cloudwatch_log_group_use_name_prefix, false) + cloudwatch_log_group_retention_in_days = try(each.value.cloudwatch_log_group_retention_in_days, var.container_definition_defaults.cloudwatch_log_group_retention_in_days, 14) + cloudwatch_log_group_kms_key_id = try(each.value.cloudwatch_log_group_kms_key_id, var.container_definition_defaults.cloudwatch_log_group_kms_key_id, null) + + tags = var.tags +} + +################################################################################ +# Task Definition +################################################################################ + +locals { + create_task_definition = var.create && var.create_task_definition + + # This allows us to query both the existing as well as Terraform's state and get + # and get the max version of either source, useful for when external resources + # update the container definition + max_task_def_revision = local.create_task_definition ? max(aws_ecs_task_definition.this[0].revision, data.aws_ecs_task_definition.this[0].revision) : 0 + task_definition = local.create_task_definition ? "${aws_ecs_task_definition.this[0].family}:${local.max_task_def_revision}" : var.task_definition_arn +} + +# This allows us to query both the existing as well as Terraform's state and get +# and get the max version of either source, useful for when external resources +# update the container definition +data "aws_ecs_task_definition" "this" { + count = local.create_task_definition ? 1 : 0 + + task_definition = aws_ecs_task_definition.this[0].family + + depends_on = [ + # Needs to exist first on first deployment + aws_ecs_task_definition.this + ] +} + +resource "aws_ecs_task_definition" "this" { + count = local.create_task_definition ? 1 : 0 + + # Convert map of maps to array of maps before JSON encoding + container_definitions = jsonencode([for k, v in module.container_definition : v.container_definition]) + cpu = var.cpu + + dynamic "ephemeral_storage" { + for_each = length(var.ephemeral_storage) > 0 ? [var.ephemeral_storage] : [] + + content { + size_in_gib = ephemeral_storage.value.size_in_gib + } + } + + execution_role_arn = try(aws_iam_role.task_exec[0].arn, var.task_exec_iam_role_arn) + family = coalesce(var.family, var.name) + + dynamic "inference_accelerator" { + for_each = var.inference_accelerator + + content { + device_name = inference_accelerator.value.device_name + device_type = inference_accelerator.value.device_type + } + } + + ipc_mode = var.ipc_mode + memory = var.memory + network_mode = var.network_mode + pid_mode = var.pid_mode + + dynamic "placement_constraints" { + for_each = var.task_definition_placement_constraints + + content { + expression = try(placement_constraints.value.expression, null) + type = placement_constraints.value.type + } + } + + dynamic "proxy_configuration" { + for_each = length(var.proxy_configuration) > 0 ? [var.proxy_configuration] : [] + + content { + container_name = proxy_configuration.value.container_name + properties = try(proxy_configuration.value.properties, null) + type = try(proxy_configuration.value.type, null) + } + } + + requires_compatibilities = var.requires_compatibilities + + dynamic "runtime_platform" { + for_each = length(var.runtime_platform) > 0 ? [var.runtime_platform] : [] + + content { + cpu_architecture = try(runtime_platform.value.cpu_architecture, null) + operating_system_family = try(runtime_platform.value.operating_system_family, null) + } + } + + skip_destroy = var.skip_destroy + task_role_arn = try(aws_iam_role.tasks[0].arn, var.tasks_iam_role_arn) + + dynamic "volume" { + for_each = var.volume + + content { + dynamic "docker_volume_configuration" { + for_each = try([volume.value.docker_volume_configuration], []) + + content { + autoprovision = try(docker_volume_configuration.value.autoprovision, null) + driver = try(docker_volume_configuration.value.driver, null) + driver_opts = try(docker_volume_configuration.value.driver_opts, null) + labels = try(docker_volume_configuration.value.labels, null) + scope = try(docker_volume_configuration.value.scope, null) + } + } + + dynamic "efs_volume_configuration" { + for_each = try([volume.value.efs_volume_configuration], []) + + content { + dynamic "authorization_config" { + for_each = try([efs_volume_configuration.value.authorization_config], []) + + content { + access_point_id = try(authorization_config.value.access_point_id, null) + iam = try(authorization_config.value.iam, null) + } + } + + file_system_id = efs_volume_configuration.value.file_system_id + root_directory = try(efs_volume_configuration.value.root_directory, null) + transit_encryption = try(efs_volume_configuration.value.transit_encryption, null) + transit_encryption_port = try(efs_volume_configuration.value.transit_encryption_port, null) + } + } + + dynamic "fsx_windows_file_server_volume_configuration" { + for_each = try([volume.value.fsx_windows_file_server_volume_configuration], []) + + content { + dynamic "authorization_config" { + for_each = try([fsx_windows_file_server_volume_configuration.value.authorization_config], []) + + content { + credentials_parameter = authorization_config.value.credentials_parameter + domain = authorization_config.value.domain + } + } + + file_system_id = fsx_windows_file_server_volume_configuration.value.file_system_id + root_directory = fsx_windows_file_server_volume_configuration.value.root_directory + } + } + + host_path = try(volume.value.host_path, null) + name = try(volume.value.name, volume.key) + } + } + + tags = merge(var.tags, var.task_tags) + + depends_on = [ + aws_iam_role_policy_attachment.tasks, + aws_iam_role_policy_attachment.task_exec, + aws_iam_role_policy_attachment.task_exec_additional, + ] + + lifecycle { + create_before_destroy = true + } +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +locals { + task_exec_iam_role_name = try(coalesce(var.task_exec_iam_role_name, var.name), "") + + create_task_exec_iam_role = local.create_task_definition && var.create_task_exec_iam_role + create_task_exec_policy = local.create_task_exec_iam_role && var.create_task_exec_policy +} + +data "aws_iam_policy_document" "task_exec_assume" { + count = local.create_task_exec_iam_role ? 1 : 0 + + statement { + sid = "ECSTaskExecutionAssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ecs-tasks.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "task_exec" { + count = local.create_task_exec_iam_role ? 1 : 0 + + name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name + name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null + path = var.task_exec_iam_role_path + description = coalesce(var.task_exec_iam_role_description, "Task execution role for ${local.task_exec_iam_role_name}") + + assume_role_policy = data.aws_iam_policy_document.task_exec_assume[0].json + max_session_duration = var.task_exec_iam_role_max_session_duration + permissions_boundary = var.task_exec_iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.task_exec_iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "task_exec_additional" { + for_each = { for k, v in var.task_exec_iam_role_policies : k => v if local.create_task_exec_iam_role } + + role = aws_iam_role.task_exec[0].name + policy_arn = each.value +} + +data "aws_iam_policy_document" "task_exec" { + count = local.create_task_exec_policy ? 1 : 0 + + # Pulled from AmazonECSTaskExecutionRolePolicy + statement { + sid = "Logs" + actions = [ + "logs:CreateLogStream", + "logs:PutLogEvents", + ] + resources = ["*"] + } + + # Pulled from AmazonECSTaskExecutionRolePolicy + statement { + sid = "ECR" + actions = [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + ] + resources = ["*"] + } + + dynamic "statement" { + for_each = length(var.task_exec_ssm_param_arns) > 0 ? [1] : [] + + content { + sid = "GetSSMParams" + actions = ["ssm:GetParameters"] + resources = var.task_exec_ssm_param_arns + } + } + + dynamic "statement" { + for_each = length(var.task_exec_secret_arns) > 0 ? [1] : [] + + content { + sid = "GetSecrets" + actions = ["secretsmanager:GetSecretValue"] + resources = var.task_exec_secret_arns + } + } + + dynamic "statement" { + for_each = var.task_exec_iam_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, null) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_iam_policy" "task_exec" { + count = local.create_task_exec_policy ? 1 : 0 + + name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name + name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null + description = coalesce(var.task_exec_iam_role_description, "Task execution role IAM policy") + policy = data.aws_iam_policy_document.task_exec[0].json + + tags = merge(var.tags, var.task_exec_iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "task_exec" { + count = local.create_task_exec_policy ? 1 : 0 + + role = aws_iam_role.task_exec[0].name + policy_arn = aws_iam_policy.task_exec[0].arn +} + +################################################################################ +# Tasks - IAM role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html +################################################################################ + +locals { + tasks_iam_role_name = try(coalesce(var.tasks_iam_role_name, var.name), "") + create_tasks_iam_role = local.create_task_definition && var.create_tasks_iam_role +} + +data "aws_iam_policy_document" "tasks_assume" { + count = local.create_tasks_iam_role ? 1 : 0 + + statement { + sid = "ECSTasksAssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ecs-tasks.amazonaws.com"] + } + + # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html#create_task_iam_policy_and_role + condition { + test = "ArnLike" + variable = "aws:SourceArn" + values = ["arn:${local.partition}:ecs:${local.region}:${local.account_id}:*"] + } + + condition { + test = "StringEquals" + variable = "aws:SourceAccount" + values = [local.account_id] + } + } +} + +resource "aws_iam_role" "tasks" { + count = local.create_tasks_iam_role ? 1 : 0 + + name = var.tasks_iam_role_use_name_prefix ? null : local.tasks_iam_role_name + name_prefix = var.tasks_iam_role_use_name_prefix ? "${local.tasks_iam_role_name}-" : null + path = var.tasks_iam_role_path + description = var.tasks_iam_role_description + + assume_role_policy = data.aws_iam_policy_document.tasks_assume[0].json + permissions_boundary = var.tasks_iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.tasks_iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "tasks" { + for_each = { for k, v in var.tasks_iam_role_policies : k => v if local.create_tasks_iam_role } + + role = aws_iam_role.tasks[0].name + policy_arn = each.value +} + +data "aws_iam_policy_document" "tasks" { + count = local.create_tasks_iam_role && (length(var.tasks_iam_role_statements) > 0 || var.enable_execute_command) ? 1 : 0 + + dynamic "statement" { + for_each = var.enable_execute_command ? [1] : [] + + content { + sid = "ECSExec" + actions = [ + "ssmmessages:CreateControlChannel", + "ssmmessages:CreateDataChannel", + "ssmmessages:OpenControlChannel", + "ssmmessages:OpenDataChannel", + ] + resources = ["*"] + } + } + + dynamic "statement" { + for_each = var.tasks_iam_role_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, null) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_iam_role_policy" "tasks" { + count = local.create_tasks_iam_role && (length(var.tasks_iam_role_statements) > 0 || var.enable_execute_command) ? 1 : 0 + + name = var.tasks_iam_role_use_name_prefix ? null : local.tasks_iam_role_name + name_prefix = var.tasks_iam_role_use_name_prefix ? "${local.tasks_iam_role_name}-" : null + policy = data.aws_iam_policy_document.tasks[0].json + role = aws_iam_role.tasks[0].id +} + +################################################################################ +# Task Set +################################################################################ + +resource "aws_ecs_task_set" "this" { + # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskset.html + count = local.create_task_definition && local.is_external_deployment && !var.ignore_task_definition_changes ? 1 : 0 + + service = try(aws_ecs_service.this[0].id, aws_ecs_service.ignore_task_definition[0].id) + cluster = var.cluster_arn + external_id = var.external_id + task_definition = local.task_definition + + dynamic "network_configuration" { + for_each = var.network_mode == "awsvpc" ? [local.network_configuration] : [] + + content { + assign_public_ip = network_configuration.value.assign_public_ip + security_groups = network_configuration.value.security_groups + subnets = network_configuration.value.subnets + } + } + + dynamic "load_balancer" { + for_each = var.load_balancer + + content { + load_balancer_name = try(load_balancer.value.load_balancer_name, null) + target_group_arn = try(load_balancer.value.target_group_arn, null) + container_name = load_balancer.value.container_name + container_port = try(load_balancer.value.container_port, null) + } + } + + dynamic "service_registries" { + for_each = length(var.service_registries) > 0 ? [var.service_registries] : [] + + content { + container_name = try(service_registries.value.container_name, null) + container_port = try(service_registries.value.container_port, null) + port = try(service_registries.value.port, null) + registry_arn = service_registries.value.registry_arn + } + } + + launch_type = length(var.capacity_provider_strategy) > 0 ? null : var.launch_type + + dynamic "capacity_provider_strategy" { + for_each = var.capacity_provider_strategy + + content { + base = try(capacity_provider_strategy.value.base, null) + capacity_provider = capacity_provider_strategy.value.capacity_provider + weight = try(capacity_provider_strategy.value.weight, null) + } + } + + platform_version = local.is_fargate ? var.platform_version : null + + dynamic "scale" { + for_each = length(var.scale) > 0 ? [var.scale] : [] + + content { + unit = try(scale.value.unit, null) + value = try(scale.value.value, null) + } + } + + force_delete = var.force_delete + wait_until_stable = var.wait_until_stable + wait_until_stable_timeout = var.wait_until_stable_timeout + + tags = merge(var.tags, var.task_tags) + + lifecycle { + ignore_changes = [ + scale, # Always ignored + ] + } +} + +################################################################################ +# Task Set - Ignore `task_definition` +################################################################################ + +resource "aws_ecs_task_set" "ignore_task_definition" { + # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskset.html + count = local.create_task_definition && local.is_external_deployment && var.ignore_task_definition_changes ? 1 : 0 + + service = try(aws_ecs_service.this[0].id, aws_ecs_service.ignore_task_definition[0].id) + cluster = var.cluster_arn + external_id = var.external_id + task_definition = local.task_definition + + dynamic "network_configuration" { + for_each = var.network_mode == "awsvpc" ? [local.network_configuration] : [] + + content { + assign_public_ip = network_configuration.value.assign_public_ip + security_groups = network_configuration.value.security_groups + subnets = network_configuration.value.subnets + } + } + + dynamic "load_balancer" { + for_each = var.load_balancer + + content { + load_balancer_name = try(load_balancer.value.load_balancer_name, null) + target_group_arn = try(load_balancer.value.target_group_arn, null) + container_name = load_balancer.value.container_name + container_port = try(load_balancer.value.container_port, null) + } + } + + dynamic "service_registries" { + for_each = length(var.service_registries) > 0 ? [var.service_registries] : [] + + content { + container_name = try(service_registries.value.container_name, null) + container_port = try(service_registries.value.container_port, null) + port = try(service_registries.value.port, null) + registry_arn = service_registries.value.registry_arn + } + } + + launch_type = length(var.capacity_provider_strategy) > 0 ? null : var.launch_type + + dynamic "capacity_provider_strategy" { + for_each = var.capacity_provider_strategy + + content { + base = try(capacity_provider_strategy.value.base, null) + capacity_provider = capacity_provider_strategy.value.capacity_provider + weight = try(capacity_provider_strategy.value.weight, null) + } + } + + platform_version = local.is_fargate ? var.platform_version : null + + dynamic "scale" { + for_each = length(var.scale) > 0 ? [var.scale] : [] + + content { + unit = try(scale.value.unit, null) + value = try(scale.value.value, null) + } + } + + force_delete = var.force_delete + wait_until_stable = var.wait_until_stable + wait_until_stable_timeout = var.wait_until_stable_timeout + + tags = merge(var.tags, var.task_tags) + + lifecycle { + ignore_changes = [ + scale, # Always ignored + task_definition, + ] + } +} + +################################################################################ +# Autoscaling +################################################################################ + +locals { + enable_autoscaling = local.create_service && var.enable_autoscaling && !local.is_daemon + + cluster_name = try(element(split("/", var.cluster_arn), 1), "") +} + +resource "aws_appautoscaling_target" "this" { + count = local.enable_autoscaling ? 1 : 0 + + # Desired needs to be between or equal to min/max + min_capacity = min(var.autoscaling_min_capacity, var.desired_count) + max_capacity = max(var.autoscaling_max_capacity, var.desired_count) + + resource_id = "service/${local.cluster_name}/${try(aws_ecs_service.this[0].name, aws_ecs_service.ignore_task_definition[0].name)}" + scalable_dimension = "ecs:service:DesiredCount" + service_namespace = "ecs" + tags = var.tags +} + +resource "aws_appautoscaling_policy" "this" { + for_each = { for k, v in var.autoscaling_policies : k => v if local.enable_autoscaling } + + name = try(each.value.name, each.key) + policy_type = try(each.value.policy_type, "TargetTrackingScaling") + resource_id = aws_appautoscaling_target.this[0].resource_id + scalable_dimension = aws_appautoscaling_target.this[0].scalable_dimension + service_namespace = aws_appautoscaling_target.this[0].service_namespace + + dynamic "step_scaling_policy_configuration" { + for_each = try([each.value.step_scaling_policy_configuration], []) + + content { + adjustment_type = try(step_scaling_policy_configuration.value.adjustment_type, null) + cooldown = try(step_scaling_policy_configuration.value.cooldown, null) + metric_aggregation_type = try(step_scaling_policy_configuration.value.metric_aggregation_type, null) + min_adjustment_magnitude = try(step_scaling_policy_configuration.value.min_adjustment_magnitude, null) + + dynamic "step_adjustment" { + for_each = try(step_scaling_policy_configuration.value.step_adjustment, []) + + content { + metric_interval_lower_bound = try(step_adjustment.value.metric_interval_lower_bound, null) + metric_interval_upper_bound = try(step_adjustment.value.metric_interval_upper_bound, null) + scaling_adjustment = try(step_adjustment.value.scaling_adjustment, null) + } + } + } + } + + dynamic "target_tracking_scaling_policy_configuration" { + for_each = try(each.value.policy_type, null) == "TargetTrackingScaling" ? try([each.value.target_tracking_scaling_policy_configuration], []) : [] + + content { + dynamic "customized_metric_specification" { + for_each = try([target_tracking_scaling_policy_configuration.value.customized_metric_specification], []) + + content { + dynamic "dimensions" { + for_each = try(customized_metric_specification.value.dimensions, []) + + content { + name = dimensions.value.name + value = dimensions.value.value + } + } + + metric_name = customized_metric_specification.value.metric_name + namespace = customized_metric_specification.value.namespace + statistic = customized_metric_specification.value.statistic + unit = try(customized_metric_specification.value.unit, null) + } + } + + disable_scale_in = try(target_tracking_scaling_policy_configuration.value.disable_scale_in, null) + + dynamic "predefined_metric_specification" { + for_each = try([target_tracking_scaling_policy_configuration.value.predefined_metric_specification], []) + + content { + predefined_metric_type = predefined_metric_specification.value.predefined_metric_type + resource_label = try(predefined_metric_specification.value.resource_label, null) + } + } + + scale_in_cooldown = try(target_tracking_scaling_policy_configuration.value.scale_in_cooldown, 300) + scale_out_cooldown = try(target_tracking_scaling_policy_configuration.value.scale_out_cooldown, 60) + target_value = try(target_tracking_scaling_policy_configuration.value.target_value, 75) + } + } +} + +resource "aws_appautoscaling_scheduled_action" "this" { + for_each = { for k, v in var.autoscaling_scheduled_actions : k => v if local.enable_autoscaling } + + name = try(each.value.name, each.key) + service_namespace = aws_appautoscaling_target.this[0].service_namespace + resource_id = aws_appautoscaling_target.this[0].resource_id + scalable_dimension = aws_appautoscaling_target.this[0].scalable_dimension + + scalable_target_action { + min_capacity = each.value.min_capacity + max_capacity = each.value.max_capacity + } + + schedule = each.value.schedule + start_time = try(each.value.start_time, null) + end_time = try(each.value.end_time, null) + timezone = try(each.value.timezone, null) +} + +################################################################################ +# Security Group +################################################################################ + +locals { + create_security_group = var.create && var.create_security_group && var.network_mode == "awsvpc" + security_group_name = try(coalesce(var.security_group_name, var.name), "") +} + +resource "aws_security_group" "this" { + count = local.create_security_group ? 1 : 0 + + name = var.security_group_use_name_prefix ? null : local.security_group_name + name_prefix = var.security_group_use_name_prefix ? "${local.security_group_name}-" : null + description = var.security_group_description + vpc_id = var.vpc_id + + tags = merge( + var.tags, + { "Name" = local.security_group_name }, + var.security_group_tags + ) + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_security_group_rule" "this" { + for_each = { for k, v in var.security_group_rules : k => v if local.create_security_group } + + # Required + security_group_id = aws_security_group.this[0].id + protocol = each.value.protocol + from_port = each.value.from_port + to_port = each.value.to_port + type = each.value.type + + # Optional + description = lookup(each.value, "description", null) + cidr_blocks = lookup(each.value, "cidr_blocks", null) + ipv6_cidr_blocks = lookup(each.value, "ipv6_cidr_blocks", null) + prefix_list_ids = lookup(each.value, "prefix_list_ids", null) + self = lookup(each.value, "self", null) + source_security_group_id = lookup(each.value, "source_security_group_id", null) +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/service/outputs.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/service/outputs.tf new file mode 100644 index 000000000..1eaa85100 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/service/outputs.tf @@ -0,0 +1,157 @@ +################################################################################ +# Service +################################################################################ + +output "id" { + description = "ARN that identifies the service" + value = try(aws_ecs_service.this[0].id, aws_ecs_service.ignore_task_definition[0].id, null) +} + +output "name" { + description = "Name of the service" + value = try(aws_ecs_service.this[0].name, aws_ecs_service.ignore_task_definition[0].name, null) +} + +################################################################################ +# IAM Role +################################################################################ + +output "iam_role_name" { + description = "Service IAM role name" + value = try(aws_iam_role.service[0].name, null) +} + +output "iam_role_arn" { + description = "Service IAM role ARN" + value = try(aws_iam_role.service[0].arn, var.iam_role_arn) +} + +output "iam_role_unique_id" { + description = "Stable and unique string identifying the service IAM role" + value = try(aws_iam_role.service[0].unique_id, null) +} + +################################################################################ +# Container Definition +################################################################################ + +output "container_definitions" { + description = "Container definitions" + value = module.container_definition +} + +################################################################################ +# Task Definition +################################################################################ + +output "task_definition_arn" { + description = "Full ARN of the Task Definition (including both `family` and `revision`)" + value = try(aws_ecs_task_definition.this[0].arn, var.task_definition_arn) +} + +output "task_definition_revision" { + description = "Revision of the task in a particular family" + value = try(aws_ecs_task_definition.this[0].revision, null) +} + +output "task_definition_family" { + description = "The unique name of the task definition" + value = try(aws_ecs_task_definition.this[0].family, null) +} + +output "task_definition_family_revision" { + description = "The family and revision (family:revision) of the task definition" + value = "${try(aws_ecs_task_definition.this[0].family, "")}:${local.max_task_def_revision}" +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +output "task_exec_iam_role_name" { + description = "Task execution IAM role name" + value = try(aws_iam_role.task_exec[0].name, null) +} + +output "task_exec_iam_role_arn" { + description = "Task execution IAM role ARN" + value = try(aws_iam_role.task_exec[0].arn, var.task_exec_iam_role_arn) +} + +output "task_exec_iam_role_unique_id" { + description = "Stable and unique string identifying the task execution IAM role" + value = try(aws_iam_role.task_exec[0].unique_id, null) +} + +################################################################################ +# Tasks - IAM role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html +################################################################################ + +output "tasks_iam_role_name" { + description = "Tasks IAM role name" + value = try(aws_iam_role.tasks[0].name, null) +} + +output "tasks_iam_role_arn" { + description = "Tasks IAM role ARN" + value = try(aws_iam_role.tasks[0].arn, var.tasks_iam_role_arn) +} + +output "tasks_iam_role_unique_id" { + description = "Stable and unique string identifying the tasks IAM role" + value = try(aws_iam_role.tasks[0].unique_id, null) +} + +################################################################################ +# Task Set +################################################################################ + +output "task_set_id" { + description = "The ID of the task set" + value = try(aws_ecs_task_set.this[0].task_set_id, aws_ecs_task_set.ignore_task_definition[0].task_set_id, null) +} + +output "task_set_arn" { + description = "The Amazon Resource Name (ARN) that identifies the task set" + value = try(aws_ecs_task_set.this[0].arn, aws_ecs_task_set.ignore_task_definition[0].arn, null) +} + +output "task_set_stability_status" { + description = "The stability status. This indicates whether the task set has reached a steady state" + value = try(aws_ecs_task_set.this[0].stability_status, aws_ecs_task_set.ignore_task_definition[0].stability_status, null) +} + +output "task_set_status" { + description = "The status of the task set" + value = try(aws_ecs_task_set.this[0].status, aws_ecs_task_set.ignore_task_definition[0].status, null) +} + +################################################################################ +# Autoscaling +################################################################################ + +output "autoscaling_policies" { + description = "Map of autoscaling policies and their attributes" + value = aws_appautoscaling_policy.this +} + +output "autoscaling_scheduled_actions" { + description = "Map of autoscaling scheduled actions and their attributes" + value = aws_appautoscaling_scheduled_action.this +} + +################################################################################ +# Security Group +################################################################################ + +output "security_group_arn" { + description = "Amazon Resource Name (ARN) of the security group" + value = try(aws_security_group.this[0].arn, null) +} + +output "security_group_id" { + description = "ID of the security group" + value = try(aws_security_group.this[0].id, null) +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/service/variables.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/service/variables.tf new file mode 100644 index 000000000..6690a5b5c --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/service/variables.tf @@ -0,0 +1,666 @@ +variable "create" { + description = "Determines whether resources will be created (affects all resources)" + type = bool + default = true +} + +variable "create_service" { + description = "Determines whether service resource will be created (set to `false` in case you want to create task definition only)" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +################################################################################ +# Service +################################################################################ + +variable "ignore_task_definition_changes" { + description = "Whether changes to service `task_definition` changes should be ignored" + type = bool + default = false +} + +variable "alarms" { + description = "Information about the CloudWatch alarms" + type = any + default = {} +} + +variable "capacity_provider_strategy" { + description = "Capacity provider strategies to use for the service. Can be one or more" + type = any + default = {} +} + +variable "cluster_arn" { + description = "ARN of the ECS cluster where the resources will be provisioned" + type = string + default = "" +} + +variable "deployment_circuit_breaker" { + description = "Configuration block for deployment circuit breaker" + type = any + default = {} +} + +variable "deployment_controller" { + description = "Configuration block for deployment controller configuration" + type = any + default = {} +} + +variable "deployment_maximum_percent" { + description = "Upper limit (as a percentage of the service's `desired_count`) of the number of running tasks that can be running in a service during a deployment" + type = number + default = 200 +} + +variable "deployment_minimum_healthy_percent" { + description = "Lower limit (as a percentage of the service's `desired_count`) of the number of running tasks that must remain running and healthy in a service during a deployment" + type = number + default = 66 +} + +variable "desired_count" { + description = "Number of instances of the task definition to place and keep running" + type = number + default = 1 +} + +variable "enable_ecs_managed_tags" { + description = "Specifies whether to enable Amazon ECS managed tags for the tasks within the service" + type = bool + default = true +} + +variable "enable_execute_command" { + description = "Specifies whether to enable Amazon ECS Exec for the tasks within the service" + type = bool + default = false +} + +variable "force_new_deployment" { + description = "Enable to force a new task deployment of the service. This can be used to update tasks to use a newer Docker image with same image/tag combination, roll Fargate tasks onto a newer platform version, or immediately deploy `ordered_placement_strategy` and `placement_constraints` updates" + type = bool + default = true +} + +variable "health_check_grace_period_seconds" { + description = "Seconds to ignore failing load balancer health checks on newly instantiated tasks to prevent premature shutdown, up to 2147483647. Only valid for services configured to use load balancers" + type = number + default = null +} + +variable "launch_type" { + description = "Launch type on which to run your service. The valid values are `EC2`, `FARGATE`, and `EXTERNAL`. Defaults to `FARGATE`" + type = string + default = "FARGATE" +} + +variable "load_balancer" { + description = "Configuration block for load balancers" + type = any + default = {} +} + +variable "name" { + description = "Name of the service (up to 255 letters, numbers, hyphens, and underscores)" + type = string + default = null +} + +variable "assign_public_ip" { + description = "Assign a public IP address to the ENI (Fargate launch type only)" + type = bool + default = false +} + +variable "security_group_ids" { + description = "List of security groups to associate with the task or service" + type = list(string) + default = [] +} + +variable "subnet_ids" { + description = "List of subnets to associate with the task or service" + type = list(string) + default = [] +} + +variable "ordered_placement_strategy" { + description = "Service level strategy rules that are taken into consideration during task placement. List from top to bottom in order of precedence" + type = any + default = {} +} + +variable "placement_constraints" { + description = "Configuration block for rules that are taken into consideration during task placement (up to max of 10). This is set at the service, see `task_definition_placement_constraints` for setting at the task definition" + type = any + default = {} +} + +variable "platform_version" { + description = "Platform version on which to run your service. Only applicable for `launch_type` set to `FARGATE`. Defaults to `LATEST`" + type = string + default = null +} + +variable "propagate_tags" { + description = "Specifies whether to propagate the tags from the task definition or the service to the tasks. The valid values are `SERVICE` and `TASK_DEFINITION`" + type = string + default = null +} + +variable "scheduling_strategy" { + description = "Scheduling strategy to use for the service. The valid values are `REPLICA` and `DAEMON`. Defaults to `REPLICA`" + type = string + default = null +} + +variable "service_connect_configuration" { + description = "The ECS Service Connect configuration for this service to discover and connect to services, and be discovered by, and connected from, other services within a namespace" + type = any + default = {} +} + +variable "service_registries" { + description = "Service discovery registries for the service" + type = any + default = {} +} + +variable "timeouts" { + description = "Create, update, and delete timeout configurations for the service" + type = map(string) + default = {} +} + +variable "triggers" { + description = "Map of arbitrary keys and values that, when changed, will trigger an in-place update (redeployment). Useful with `timestamp()`" + type = any + default = {} +} + +variable "wait_for_steady_state" { + description = "If true, Terraform will wait for the service to reach a steady state before continuing. Default is `false`" + type = bool + default = null +} + +variable "service_tags" { + description = "A map of additional tags to add to the service" + type = map(string) + default = {} +} + +################################################################################ +# Service - IAM Role +################################################################################ + +variable "create_iam_role" { + description = "Determines whether the ECS service IAM role should be created" + type = bool + default = true +} + +variable "iam_role_arn" { + description = "Existing IAM role ARN" + type = string + default = null +} + +variable "iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} + +variable "iam_role_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} + +################################################################################ +# Task Definition +################################################################################ + +variable "create_task_definition" { + description = "Determines whether to create a task definition or use existing/provided" + type = bool + default = true +} + +variable "task_definition_arn" { + description = "Existing task definition ARN. Required when `create_task_definition` is `false`" + type = string + default = null +} + +variable "container_definitions" { + description = "A map of valid [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html). Please note that you should only provide values that are part of the container definition document" + type = any + default = {} +} + +variable "container_definition_defaults" { + description = "A map of default values for [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html) created by `container_definitions`" + type = any + default = {} +} + +variable "cpu" { + description = "Number of cpu units used by the task. If the `requires_compatibilities` is `FARGATE` this field is required" + type = number + default = 1024 +} + +variable "ephemeral_storage" { + description = "The amount of ephemeral storage to allocate for the task. This parameter is used to expand the total amount of ephemeral storage available, beyond the default amount, for tasks hosted on AWS Fargate" + type = any + default = {} +} + +variable "family" { + description = "A unique name for your task definition" + type = string + default = null +} + +variable "inference_accelerator" { + description = "Configuration block(s) with Inference Accelerators settings" + type = any + default = {} +} + +variable "ipc_mode" { + description = "IPC resource namespace to be used for the containers in the task The valid values are `host`, `task`, and `none`" + type = string + default = null +} + +variable "memory" { + description = "Amount (in MiB) of memory used by the task. If the `requires_compatibilities` is `FARGATE` this field is required" + type = number + default = 2048 +} + +variable "network_mode" { + description = "Docker networking mode to use for the containers in the task. Valid values are `none`, `bridge`, `awsvpc`, and `host`" + type = string + default = "awsvpc" +} + +variable "pid_mode" { + description = "Process namespace to use for the containers in the task. The valid values are `host` and `task`" + type = string + default = null +} + +variable "task_definition_placement_constraints" { + description = "Configuration block for rules that are taken into consideration during task placement (up to max of 10). This is set at the task definition, see `placement_constraints` for setting at the service" + type = any + default = {} +} + +variable "proxy_configuration" { + description = "Configuration block for the App Mesh proxy" + type = any + default = {} +} + +variable "requires_compatibilities" { + description = "Set of launch types required by the task. The valid values are `EC2` and `FARGATE`" + type = list(string) + default = ["FARGATE"] +} + +variable "runtime_platform" { + description = "Configuration block for `runtime_platform` that containers in your task may use" + type = any + default = { + operating_system_family = "LINUX" + cpu_architecture = "X86_64" + } +} + +variable "skip_destroy" { + description = "If true, the task is not deleted when the service is deleted" + type = bool + default = null +} + +variable "volume" { + description = "Configuration block for volumes that containers in your task may use" + type = any + default = {} +} + +variable "task_tags" { + description = "A map of additional tags to add to the task definition/set created" + type = map(string) + default = {} +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +variable "create_task_exec_iam_role" { + description = "Determines whether the ECS task definition IAM role should be created" + type = bool + default = true +} + +variable "task_exec_iam_role_arn" { + description = "Existing IAM role ARN" + type = string + default = null +} + +variable "task_exec_iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "task_exec_iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "task_exec_iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "task_exec_iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "task_exec_iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "task_exec_iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} + +variable "task_exec_iam_role_policies" { + description = "Map of IAM role policy ARNs to attach to the IAM role" + type = map(string) + default = {} +} + +variable "task_exec_iam_role_max_session_duration" { + description = "Maximum session duration (in seconds) for ECS task execution role. Default is 3600." + type = number + default = null +} + +variable "create_task_exec_policy" { + description = "Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters" + type = bool + default = true +} + +variable "task_exec_ssm_param_arns" { + description = "List of SSM parameter ARNs the task execution role will be permitted to get/read" + type = list(string) + default = ["arn:aws:ssm:*:*:parameter/*"] +} + +variable "task_exec_secret_arns" { + description = "List of SecretsManager secret ARNs the task execution role will be permitted to get/read" + type = list(string) + default = ["arn:aws:secretsmanager:*:*:secret:*"] +} + +variable "task_exec_iam_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} + +################################################################################ +# Tasks - IAM role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html +################################################################################ + +variable "create_tasks_iam_role" { + description = "Determines whether the ECS tasks IAM role should be created" + type = bool + default = true +} + +variable "tasks_iam_role_arn" { + description = "Existing IAM role ARN" + type = string + default = null +} + +variable "tasks_iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "tasks_iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`tasks_iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "tasks_iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "tasks_iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "tasks_iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "tasks_iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} + +variable "tasks_iam_role_policies" { + description = "Map of IAM role policy ARNs to attach to the IAM role" + type = map(string) + default = {} +} + +variable "tasks_iam_role_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} + +################################################################################ +# Task Set +################################################################################ + +variable "external_id" { + description = "The external ID associated with the task set" + type = string + default = null +} + +variable "scale" { + description = "A floating-point percentage of the desired number of tasks to place and keep running in the task set" + type = any + default = {} +} + +variable "force_delete" { + description = "Whether to allow deleting the task set without waiting for scaling down to 0" + type = bool + default = null +} + +variable "wait_until_stable" { + description = "Whether terraform should wait until the task set has reached `STEADY_STATE`" + type = bool + default = null +} + +variable "wait_until_stable_timeout" { + description = "Wait timeout for task set to reach `STEADY_STATE`. Valid time units include `ns`, `us` (or µs), `ms`, `s`, `m`, and `h`. Default `10m`" + type = string + default = null +} + +################################################################################ +# Autoscaling +################################################################################ + +variable "enable_autoscaling" { + description = "Determines whether to enable autoscaling for the service" + type = bool + default = true +} + +variable "autoscaling_min_capacity" { + description = "Minimum number of tasks to run in your service" + type = number + default = 1 +} + +variable "autoscaling_max_capacity" { + description = "Maximum number of tasks to run in your service" + type = number + default = 10 +} + +variable "autoscaling_policies" { + description = "Map of autoscaling policies to create for the service" + type = any + default = { + cpu = { + policy_type = "TargetTrackingScaling" + + target_tracking_scaling_policy_configuration = { + predefined_metric_specification = { + predefined_metric_type = "ECSServiceAverageCPUUtilization" + } + } + } + memory = { + policy_type = "TargetTrackingScaling" + + target_tracking_scaling_policy_configuration = { + predefined_metric_specification = { + predefined_metric_type = "ECSServiceAverageMemoryUtilization" + } + } + } + } +} + +variable "autoscaling_scheduled_actions" { + description = "Map of autoscaling scheduled actions to create for the service" + type = any + default = {} +} + +################################################################################ +# Security Group +################################################################################ + +variable "create_security_group" { + description = "Determines if a security group is created" + type = bool + default = true +} + +variable "security_group_name" { + description = "Name to use on security group created" + type = string + default = null +} + +variable "security_group_use_name_prefix" { + description = "Determines whether the security group name (`security_group_name`) is used as a prefix" + type = bool + default = true +} + +variable "security_group_description" { + description = "Description of the security group created" + type = string + default = null +} + +variable "security_group_rules" { + description = "Security group rules to add to the security group created" + type = any + default = {} +} + +variable "security_group_tags" { + description = "A map of additional tags to add to the security group created" + type = map(string) + default = {} +} + +variable "vpc_id" { + description = "VPC ID" + type = string +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/service/versions.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/service/versions.tf new file mode 100644 index 000000000..17c7f7b40 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/modules/service/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws5" + version = ">= 4.66.1" + } + } +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/outputs.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/outputs.tf new file mode 100644 index 000000000..6e462c6e1 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/outputs.tf @@ -0,0 +1,62 @@ +################################################################################ +# Cluster +################################################################################ + +output "cluster_arn" { + description = "ARN that identifies the cluster" + value = module.cluster.arn +} + +output "cluster_id" { + description = "ID that identifies the cluster" + value = module.cluster.id +} + +output "cluster_name" { + description = "Name that identifies the cluster" + value = module.cluster.name +} + +output "cloudwatch_log_group_name" { + description = "Name of CloudWatch log group created" + value = module.cluster.cloudwatch_log_group_name +} + +output "cloudwatch_log_group_arn" { + description = "ARN of CloudWatch log group created" + value = module.cluster.cloudwatch_log_group_arn +} + +output "cluster_capacity_providers" { + description = "Map of cluster capacity providers attributes" + value = module.cluster.cluster_capacity_providers +} + +output "autoscaling_capacity_providers" { + description = "Map of autoscaling capacity providers created and their attributes" + value = module.cluster.autoscaling_capacity_providers +} + +output "task_exec_iam_role_name" { + description = "Task execution IAM role name" + value = module.cluster.task_exec_iam_role_name +} + +output "task_exec_iam_role_arn" { + description = "Task execution IAM role ARN" + value = module.cluster.task_exec_iam_role_arn +} + +output "task_exec_iam_role_unique_id" { + description = "Stable and unique string identifying the task execution IAM role" + value = module.cluster.task_exec_iam_role_unique_id +} + +################################################################################ +# Service(s) +################################################################################ + +output "services" { + description = "Map of services created and their attributes" + value = module.service +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/variables.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/variables.tf new file mode 100644 index 000000000..b624a302a --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/variables.tf @@ -0,0 +1,193 @@ +variable "create" { + description = "Determines whether resources will be created (affects all resources)" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +################################################################################ +# Cluster +################################################################################ + +variable "cluster_name" { + description = "Name of the cluster (up to 255 letters, numbers, hyphens, and underscores)" + type = string + default = "" +} + +variable "cluster_configuration" { + description = "The execute command configuration for the cluster" + type = any + default = {} +} + +variable "cluster_settings" { + description = "List of configuration block(s) with cluster settings. For example, this can be used to enable CloudWatch Container Insights for a cluster" + type = any + default = [ + { + name = "containerInsights" + value = "enabled" + } + ] +} + +variable "cluster_service_connect_defaults" { + description = "Configures a default Service Connect namespace" + type = map(string) + default = {} +} + +variable "cluster_tags" { + description = "A map of additional tags to add to the cluster" + type = map(string) + default = {} +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +variable "create_cloudwatch_log_group" { + description = "Determines whether a log group is created by this module for the cluster logs. If not, AWS will automatically create one if logging is enabled" + type = bool + default = true +} + +variable "cloudwatch_log_group_name" { + description = "Custom name of CloudWatch Log Group for ECS cluster" + type = string + default = null +} + +variable "cloudwatch_log_group_retention_in_days" { + description = "Number of days to retain log events" + type = number + default = 90 +} + +variable "cloudwatch_log_group_kms_key_id" { + description = "If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html)" + type = string + default = null +} + +variable "cloudwatch_log_group_tags" { + description = "A map of additional tags to add to the log group created" + type = map(string) + default = {} +} + +################################################################################ +# Capacity Providers +################################################################################ + +variable "default_capacity_provider_use_fargate" { + description = "Determines whether to use Fargate or autoscaling for default capacity provider strategy" + type = bool + default = true +} + +variable "fargate_capacity_providers" { + description = "Map of Fargate capacity provider definitions to use for the cluster" + type = any + default = {} +} + +variable "autoscaling_capacity_providers" { + description = "Map of autoscaling capacity provider definitions to create for the cluster" + type = any + default = {} +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +variable "create_task_exec_iam_role" { + description = "Determines whether the ECS task definition IAM role should be created" + type = bool + default = false +} + +variable "task_exec_iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "task_exec_iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "task_exec_iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "task_exec_iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "task_exec_iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "task_exec_iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} + +variable "task_exec_iam_role_policies" { + description = "Map of IAM role policy ARNs to attach to the IAM role" + type = map(string) + default = {} +} + +variable "create_task_exec_policy" { + description = "Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters" + type = bool + default = true +} + +variable "task_exec_ssm_param_arns" { + description = "List of SSM parameter ARNs the task execution role will be permitted to get/read" + type = list(string) + default = ["arn:aws:ssm:*:*:parameter/*"] +} + +variable "task_exec_secret_arns" { + description = "List of SecretsManager secret ARNs the task execution role will be permitted to get/read" + type = list(string) + default = ["arn:aws:secretsmanager:*:*:secret:*"] +} + +variable "task_exec_iam_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} + +################################################################################ +# Service(s) +################################################################################ + +variable "services" { + description = "Map of service definitions to create" + type = any + default = {} +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/versions.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/versions.tf new file mode 100644 index 000000000..682191e70 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.66.1" + } + } +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/README.md b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/README.md new file mode 100644 index 000000000..449acd8af --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/README.md @@ -0,0 +1,100 @@ +# Wrapper for the root module + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/ecs/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-ecs.git//wrappers?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/ecs/aws//wrappers" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/cluster/README.md b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/cluster/README.md new file mode 100644 index 000000000..1a795281f --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/cluster/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/cluster` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/ecs/aws//wrappers/cluster" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-ecs.git//wrappers/cluster?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/ecs/aws//wrappers/cluster" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/cluster/main.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/cluster/main.tf new file mode 100644 index 000000000..91f929d7f --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/cluster/main.tf @@ -0,0 +1,37 @@ +module "wrapper" { + source = "../../modules/cluster" + + for_each = var.items + + autoscaling_capacity_providers = try(each.value.autoscaling_capacity_providers, var.defaults.autoscaling_capacity_providers, {}) + cloudwatch_log_group_kms_key_id = try(each.value.cloudwatch_log_group_kms_key_id, var.defaults.cloudwatch_log_group_kms_key_id, null) + cloudwatch_log_group_name = try(each.value.cloudwatch_log_group_name, var.defaults.cloudwatch_log_group_name, null) + cloudwatch_log_group_retention_in_days = try(each.value.cloudwatch_log_group_retention_in_days, var.defaults.cloudwatch_log_group_retention_in_days, 90) + cloudwatch_log_group_tags = try(each.value.cloudwatch_log_group_tags, var.defaults.cloudwatch_log_group_tags, {}) + cluster_configuration = try(each.value.cluster_configuration, var.defaults.cluster_configuration, {}) + cluster_name = try(each.value.cluster_name, var.defaults.cluster_name, "") + cluster_service_connect_defaults = try(each.value.cluster_service_connect_defaults, var.defaults.cluster_service_connect_defaults, {}) + cluster_settings = try(each.value.cluster_settings, var.defaults.cluster_settings, [ + { + name = "containerInsights" + value = "enabled" + } + ]) + create = try(each.value.create, var.defaults.create, true) + create_cloudwatch_log_group = try(each.value.create_cloudwatch_log_group, var.defaults.create_cloudwatch_log_group, true) + create_task_exec_iam_role = try(each.value.create_task_exec_iam_role, var.defaults.create_task_exec_iam_role, false) + create_task_exec_policy = try(each.value.create_task_exec_policy, var.defaults.create_task_exec_policy, true) + default_capacity_provider_use_fargate = try(each.value.default_capacity_provider_use_fargate, var.defaults.default_capacity_provider_use_fargate, true) + fargate_capacity_providers = try(each.value.fargate_capacity_providers, var.defaults.fargate_capacity_providers, {}) + tags = try(each.value.tags, var.defaults.tags, {}) + task_exec_iam_role_description = try(each.value.task_exec_iam_role_description, var.defaults.task_exec_iam_role_description, null) + task_exec_iam_role_name = try(each.value.task_exec_iam_role_name, var.defaults.task_exec_iam_role_name, null) + task_exec_iam_role_path = try(each.value.task_exec_iam_role_path, var.defaults.task_exec_iam_role_path, null) + task_exec_iam_role_permissions_boundary = try(each.value.task_exec_iam_role_permissions_boundary, var.defaults.task_exec_iam_role_permissions_boundary, null) + task_exec_iam_role_policies = try(each.value.task_exec_iam_role_policies, var.defaults.task_exec_iam_role_policies, {}) + task_exec_iam_role_tags = try(each.value.task_exec_iam_role_tags, var.defaults.task_exec_iam_role_tags, {}) + task_exec_iam_role_use_name_prefix = try(each.value.task_exec_iam_role_use_name_prefix, var.defaults.task_exec_iam_role_use_name_prefix, true) + task_exec_iam_statements = try(each.value.task_exec_iam_statements, var.defaults.task_exec_iam_statements, {}) + task_exec_secret_arns = try(each.value.task_exec_secret_arns, var.defaults.task_exec_secret_arns, ["arn:aws:secretsmanager:*:*:secret:*"]) + task_exec_ssm_param_arns = try(each.value.task_exec_ssm_param_arns, var.defaults.task_exec_ssm_param_arns, ["arn:aws:ssm:*:*:parameter/*"]) +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/cluster/outputs.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/cluster/outputs.tf new file mode 100644 index 000000000..ec6da5f4a --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/cluster/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/cluster/variables.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/cluster/variables.tf new file mode 100644 index 000000000..a6ea0962c --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/cluster/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/cluster/versions.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/cluster/versions.tf new file mode 100644 index 000000000..682191e70 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/cluster/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.66.1" + } + } +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/container-definition/README.md b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/container-definition/README.md new file mode 100644 index 000000000..4731aa9a2 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/container-definition/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/container-definition` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/ecs/aws//wrappers/container-definition" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-ecs.git//wrappers/container-definition?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/ecs/aws//wrappers/container-definition" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/container-definition/main.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/container-definition/main.tf new file mode 100644 index 000000000..7bcba25d3 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/container-definition/main.tf @@ -0,0 +1,55 @@ +module "wrapper" { + source = "../../modules/container-definition" + + for_each = var.items + + cloudwatch_log_group_kms_key_id = try(each.value.cloudwatch_log_group_kms_key_id, var.defaults.cloudwatch_log_group_kms_key_id, null) + cloudwatch_log_group_name = try(each.value.cloudwatch_log_group_name, var.defaults.cloudwatch_log_group_name, null) + cloudwatch_log_group_retention_in_days = try(each.value.cloudwatch_log_group_retention_in_days, var.defaults.cloudwatch_log_group_retention_in_days, 30) + cloudwatch_log_group_use_name_prefix = try(each.value.cloudwatch_log_group_use_name_prefix, var.defaults.cloudwatch_log_group_use_name_prefix, false) + command = try(each.value.command, var.defaults.command, []) + cpu = try(each.value.cpu, var.defaults.cpu, null) + create_cloudwatch_log_group = try(each.value.create_cloudwatch_log_group, var.defaults.create_cloudwatch_log_group, true) + dependencies = try(each.value.dependencies, var.defaults.dependencies, []) + disable_networking = try(each.value.disable_networking, var.defaults.disable_networking, null) + dns_search_domains = try(each.value.dns_search_domains, var.defaults.dns_search_domains, []) + dns_servers = try(each.value.dns_servers, var.defaults.dns_servers, []) + docker_labels = try(each.value.docker_labels, var.defaults.docker_labels, {}) + docker_security_options = try(each.value.docker_security_options, var.defaults.docker_security_options, []) + enable_cloudwatch_logging = try(each.value.enable_cloudwatch_logging, var.defaults.enable_cloudwatch_logging, true) + enable_execute_command = try(each.value.enable_execute_command, var.defaults.enable_execute_command, false) + entrypoint = try(each.value.entrypoint, var.defaults.entrypoint, []) + environment = try(each.value.environment, var.defaults.environment, []) + environment_files = try(each.value.environment_files, var.defaults.environment_files, []) + essential = try(each.value.essential, var.defaults.essential, null) + extra_hosts = try(each.value.extra_hosts, var.defaults.extra_hosts, []) + firelens_configuration = try(each.value.firelens_configuration, var.defaults.firelens_configuration, {}) + health_check = try(each.value.health_check, var.defaults.health_check, {}) + hostname = try(each.value.hostname, var.defaults.hostname, null) + image = try(each.value.image, var.defaults.image, null) + interactive = try(each.value.interactive, var.defaults.interactive, false) + links = try(each.value.links, var.defaults.links, []) + linux_parameters = try(each.value.linux_parameters, var.defaults.linux_parameters, {}) + log_configuration = try(each.value.log_configuration, var.defaults.log_configuration, {}) + memory = try(each.value.memory, var.defaults.memory, null) + memory_reservation = try(each.value.memory_reservation, var.defaults.memory_reservation, null) + mount_points = try(each.value.mount_points, var.defaults.mount_points, []) + name = try(each.value.name, var.defaults.name, null) + operating_system_family = try(each.value.operating_system_family, var.defaults.operating_system_family, "LINUX") + port_mappings = try(each.value.port_mappings, var.defaults.port_mappings, []) + privileged = try(each.value.privileged, var.defaults.privileged, false) + pseudo_terminal = try(each.value.pseudo_terminal, var.defaults.pseudo_terminal, false) + readonly_root_filesystem = try(each.value.readonly_root_filesystem, var.defaults.readonly_root_filesystem, true) + repository_credentials = try(each.value.repository_credentials, var.defaults.repository_credentials, {}) + resource_requirements = try(each.value.resource_requirements, var.defaults.resource_requirements, []) + secrets = try(each.value.secrets, var.defaults.secrets, []) + service = try(each.value.service, var.defaults.service, "") + start_timeout = try(each.value.start_timeout, var.defaults.start_timeout, 30) + stop_timeout = try(each.value.stop_timeout, var.defaults.stop_timeout, 120) + system_controls = try(each.value.system_controls, var.defaults.system_controls, []) + tags = try(each.value.tags, var.defaults.tags, {}) + ulimits = try(each.value.ulimits, var.defaults.ulimits, []) + user = try(each.value.user, var.defaults.user, null) + volumes_from = try(each.value.volumes_from, var.defaults.volumes_from, []) + working_directory = try(each.value.working_directory, var.defaults.working_directory, null) +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/container-definition/outputs.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/container-definition/outputs.tf new file mode 100644 index 000000000..ec6da5f4a --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/container-definition/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/container-definition/variables.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/container-definition/variables.tf new file mode 100644 index 000000000..a6ea0962c --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/container-definition/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/container-definition/versions.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/container-definition/versions.tf new file mode 100644 index 000000000..682191e70 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/container-definition/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.66.1" + } + } +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/main.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/main.tf new file mode 100644 index 000000000..5aca83728 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/main.tf @@ -0,0 +1,39 @@ +module "wrapper" { + source = "../" + + for_each = var.items + + autoscaling_capacity_providers = try(each.value.autoscaling_capacity_providers, var.defaults.autoscaling_capacity_providers, {}) + cloudwatch_log_group_kms_key_id = try(each.value.cloudwatch_log_group_kms_key_id, var.defaults.cloudwatch_log_group_kms_key_id, null) + cloudwatch_log_group_name = try(each.value.cloudwatch_log_group_name, var.defaults.cloudwatch_log_group_name, null) + cloudwatch_log_group_retention_in_days = try(each.value.cloudwatch_log_group_retention_in_days, var.defaults.cloudwatch_log_group_retention_in_days, 90) + cloudwatch_log_group_tags = try(each.value.cloudwatch_log_group_tags, var.defaults.cloudwatch_log_group_tags, {}) + cluster_configuration = try(each.value.cluster_configuration, var.defaults.cluster_configuration, {}) + cluster_name = try(each.value.cluster_name, var.defaults.cluster_name, "") + cluster_service_connect_defaults = try(each.value.cluster_service_connect_defaults, var.defaults.cluster_service_connect_defaults, {}) + cluster_settings = try(each.value.cluster_settings, var.defaults.cluster_settings, [ + { + name = "containerInsights" + value = "enabled" + } + ]) + cluster_tags = try(each.value.cluster_tags, var.defaults.cluster_tags, {}) + create = try(each.value.create, var.defaults.create, true) + create_cloudwatch_log_group = try(each.value.create_cloudwatch_log_group, var.defaults.create_cloudwatch_log_group, true) + create_task_exec_iam_role = try(each.value.create_task_exec_iam_role, var.defaults.create_task_exec_iam_role, false) + create_task_exec_policy = try(each.value.create_task_exec_policy, var.defaults.create_task_exec_policy, true) + default_capacity_provider_use_fargate = try(each.value.default_capacity_provider_use_fargate, var.defaults.default_capacity_provider_use_fargate, true) + fargate_capacity_providers = try(each.value.fargate_capacity_providers, var.defaults.fargate_capacity_providers, {}) + services = try(each.value.services, var.defaults.services, {}) + tags = try(each.value.tags, var.defaults.tags, {}) + task_exec_iam_role_description = try(each.value.task_exec_iam_role_description, var.defaults.task_exec_iam_role_description, null) + task_exec_iam_role_name = try(each.value.task_exec_iam_role_name, var.defaults.task_exec_iam_role_name, null) + task_exec_iam_role_path = try(each.value.task_exec_iam_role_path, var.defaults.task_exec_iam_role_path, null) + task_exec_iam_role_permissions_boundary = try(each.value.task_exec_iam_role_permissions_boundary, var.defaults.task_exec_iam_role_permissions_boundary, null) + task_exec_iam_role_policies = try(each.value.task_exec_iam_role_policies, var.defaults.task_exec_iam_role_policies, {}) + task_exec_iam_role_tags = try(each.value.task_exec_iam_role_tags, var.defaults.task_exec_iam_role_tags, {}) + task_exec_iam_role_use_name_prefix = try(each.value.task_exec_iam_role_use_name_prefix, var.defaults.task_exec_iam_role_use_name_prefix, true) + task_exec_iam_statements = try(each.value.task_exec_iam_statements, var.defaults.task_exec_iam_statements, {}) + task_exec_secret_arns = try(each.value.task_exec_secret_arns, var.defaults.task_exec_secret_arns, ["arn:aws:secretsmanager:*:*:secret:*"]) + task_exec_ssm_param_arns = try(each.value.task_exec_ssm_param_arns, var.defaults.task_exec_ssm_param_arns, ["arn:aws:ssm:*:*:parameter/*"]) +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/outputs.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/outputs.tf new file mode 100644 index 000000000..ec6da5f4a --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/service/README.md b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/service/README.md new file mode 100644 index 000000000..219da9167 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/service/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/service` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/ecs/aws//wrappers/service" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-ecs.git//wrappers/service?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/ecs/aws//wrappers/service" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/service/main.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/service/main.tf new file mode 100644 index 000000000..3dbd9e46b --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/service/main.tf @@ -0,0 +1,129 @@ +module "wrapper" { + source = "../../modules/service" + + for_each = var.items + + alarms = try(each.value.alarms, var.defaults.alarms, {}) + assign_public_ip = try(each.value.assign_public_ip, var.defaults.assign_public_ip, false) + autoscaling_max_capacity = try(each.value.autoscaling_max_capacity, var.defaults.autoscaling_max_capacity, 10) + autoscaling_min_capacity = try(each.value.autoscaling_min_capacity, var.defaults.autoscaling_min_capacity, 1) + autoscaling_policies = try(each.value.autoscaling_policies, var.defaults.autoscaling_policies, { + cpu = { + policy_type = "TargetTrackingScaling" + + target_tracking_scaling_policy_configuration = { + predefined_metric_specification = { + predefined_metric_type = "ECSServiceAverageCPUUtilization" + } + } + } + memory = { + policy_type = "TargetTrackingScaling" + + target_tracking_scaling_policy_configuration = { + predefined_metric_specification = { + predefined_metric_type = "ECSServiceAverageMemoryUtilization" + } + } + } + }) + autoscaling_scheduled_actions = try(each.value.autoscaling_scheduled_actions, var.defaults.autoscaling_scheduled_actions, {}) + capacity_provider_strategy = try(each.value.capacity_provider_strategy, var.defaults.capacity_provider_strategy, {}) + cluster_arn = try(each.value.cluster_arn, var.defaults.cluster_arn, "") + container_definition_defaults = try(each.value.container_definition_defaults, var.defaults.container_definition_defaults, {}) + container_definitions = try(each.value.container_definitions, var.defaults.container_definitions, {}) + cpu = try(each.value.cpu, var.defaults.cpu, 1024) + create = try(each.value.create, var.defaults.create, true) + create_iam_role = try(each.value.create_iam_role, var.defaults.create_iam_role, true) + create_security_group = try(each.value.create_security_group, var.defaults.create_security_group, true) + create_service = try(each.value.create_service, var.defaults.create_service, true) + create_task_definition = try(each.value.create_task_definition, var.defaults.create_task_definition, true) + create_task_exec_iam_role = try(each.value.create_task_exec_iam_role, var.defaults.create_task_exec_iam_role, true) + create_task_exec_policy = try(each.value.create_task_exec_policy, var.defaults.create_task_exec_policy, true) + create_tasks_iam_role = try(each.value.create_tasks_iam_role, var.defaults.create_tasks_iam_role, true) + deployment_circuit_breaker = try(each.value.deployment_circuit_breaker, var.defaults.deployment_circuit_breaker, {}) + deployment_controller = try(each.value.deployment_controller, var.defaults.deployment_controller, {}) + deployment_maximum_percent = try(each.value.deployment_maximum_percent, var.defaults.deployment_maximum_percent, 200) + deployment_minimum_healthy_percent = try(each.value.deployment_minimum_healthy_percent, var.defaults.deployment_minimum_healthy_percent, 66) + desired_count = try(each.value.desired_count, var.defaults.desired_count, 1) + enable_autoscaling = try(each.value.enable_autoscaling, var.defaults.enable_autoscaling, true) + enable_ecs_managed_tags = try(each.value.enable_ecs_managed_tags, var.defaults.enable_ecs_managed_tags, true) + enable_execute_command = try(each.value.enable_execute_command, var.defaults.enable_execute_command, false) + ephemeral_storage = try(each.value.ephemeral_storage, var.defaults.ephemeral_storage, {}) + external_id = try(each.value.external_id, var.defaults.external_id, null) + family = try(each.value.family, var.defaults.family, null) + force_delete = try(each.value.force_delete, var.defaults.force_delete, null) + force_new_deployment = try(each.value.force_new_deployment, var.defaults.force_new_deployment, true) + health_check_grace_period_seconds = try(each.value.health_check_grace_period_seconds, var.defaults.health_check_grace_period_seconds, null) + iam_role_arn = try(each.value.iam_role_arn, var.defaults.iam_role_arn, null) + iam_role_description = try(each.value.iam_role_description, var.defaults.iam_role_description, null) + iam_role_name = try(each.value.iam_role_name, var.defaults.iam_role_name, null) + iam_role_path = try(each.value.iam_role_path, var.defaults.iam_role_path, null) + iam_role_permissions_boundary = try(each.value.iam_role_permissions_boundary, var.defaults.iam_role_permissions_boundary, null) + iam_role_statements = try(each.value.iam_role_statements, var.defaults.iam_role_statements, {}) + iam_role_tags = try(each.value.iam_role_tags, var.defaults.iam_role_tags, {}) + iam_role_use_name_prefix = try(each.value.iam_role_use_name_prefix, var.defaults.iam_role_use_name_prefix, true) + ignore_task_definition_changes = try(each.value.ignore_task_definition_changes, var.defaults.ignore_task_definition_changes, false) + inference_accelerator = try(each.value.inference_accelerator, var.defaults.inference_accelerator, {}) + ipc_mode = try(each.value.ipc_mode, var.defaults.ipc_mode, null) + launch_type = try(each.value.launch_type, var.defaults.launch_type, "FARGATE") + load_balancer = try(each.value.load_balancer, var.defaults.load_balancer, {}) + memory = try(each.value.memory, var.defaults.memory, 2048) + name = try(each.value.name, var.defaults.name, null) + network_mode = try(each.value.network_mode, var.defaults.network_mode, "awsvpc") + ordered_placement_strategy = try(each.value.ordered_placement_strategy, var.defaults.ordered_placement_strategy, {}) + pid_mode = try(each.value.pid_mode, var.defaults.pid_mode, null) + placement_constraints = try(each.value.placement_constraints, var.defaults.placement_constraints, {}) + platform_version = try(each.value.platform_version, var.defaults.platform_version, null) + propagate_tags = try(each.value.propagate_tags, var.defaults.propagate_tags, null) + proxy_configuration = try(each.value.proxy_configuration, var.defaults.proxy_configuration, {}) + requires_compatibilities = try(each.value.requires_compatibilities, var.defaults.requires_compatibilities, ["FARGATE"]) + runtime_platform = try(each.value.runtime_platform, var.defaults.runtime_platform, { + operating_system_family = "LINUX" + cpu_architecture = "X86_64" + }) + scale = try(each.value.scale, var.defaults.scale, {}) + scheduling_strategy = try(each.value.scheduling_strategy, var.defaults.scheduling_strategy, null) + security_group_description = try(each.value.security_group_description, var.defaults.security_group_description, null) + security_group_ids = try(each.value.security_group_ids, var.defaults.security_group_ids, []) + security_group_name = try(each.value.security_group_name, var.defaults.security_group_name, null) + security_group_rules = try(each.value.security_group_rules, var.defaults.security_group_rules, {}) + security_group_tags = try(each.value.security_group_tags, var.defaults.security_group_tags, {}) + security_group_use_name_prefix = try(each.value.security_group_use_name_prefix, var.defaults.security_group_use_name_prefix, true) + service_connect_configuration = try(each.value.service_connect_configuration, var.defaults.service_connect_configuration, {}) + service_registries = try(each.value.service_registries, var.defaults.service_registries, {}) + service_tags = try(each.value.service_tags, var.defaults.service_tags, {}) + skip_destroy = try(each.value.skip_destroy, var.defaults.skip_destroy, null) + subnet_ids = try(each.value.subnet_ids, var.defaults.subnet_ids, []) + tags = try(each.value.tags, var.defaults.tags, {}) + task_definition_arn = try(each.value.task_definition_arn, var.defaults.task_definition_arn, null) + task_definition_placement_constraints = try(each.value.task_definition_placement_constraints, var.defaults.task_definition_placement_constraints, {}) + task_exec_iam_role_arn = try(each.value.task_exec_iam_role_arn, var.defaults.task_exec_iam_role_arn, null) + task_exec_iam_role_description = try(each.value.task_exec_iam_role_description, var.defaults.task_exec_iam_role_description, null) + task_exec_iam_role_max_session_duration = try(each.value.task_exec_iam_role_max_session_duration, var.defaults.task_exec_iam_role_max_session_duration, null) + task_exec_iam_role_name = try(each.value.task_exec_iam_role_name, var.defaults.task_exec_iam_role_name, null) + task_exec_iam_role_path = try(each.value.task_exec_iam_role_path, var.defaults.task_exec_iam_role_path, null) + task_exec_iam_role_permissions_boundary = try(each.value.task_exec_iam_role_permissions_boundary, var.defaults.task_exec_iam_role_permissions_boundary, null) + task_exec_iam_role_policies = try(each.value.task_exec_iam_role_policies, var.defaults.task_exec_iam_role_policies, {}) + task_exec_iam_role_tags = try(each.value.task_exec_iam_role_tags, var.defaults.task_exec_iam_role_tags, {}) + task_exec_iam_role_use_name_prefix = try(each.value.task_exec_iam_role_use_name_prefix, var.defaults.task_exec_iam_role_use_name_prefix, true) + task_exec_iam_statements = try(each.value.task_exec_iam_statements, var.defaults.task_exec_iam_statements, {}) + task_exec_secret_arns = try(each.value.task_exec_secret_arns, var.defaults.task_exec_secret_arns, ["arn:aws:secretsmanager:*:*:secret:*"]) + task_exec_ssm_param_arns = try(each.value.task_exec_ssm_param_arns, var.defaults.task_exec_ssm_param_arns, ["arn:aws:ssm:*:*:parameter/*"]) + task_tags = try(each.value.task_tags, var.defaults.task_tags, {}) + tasks_iam_role_arn = try(each.value.tasks_iam_role_arn, var.defaults.tasks_iam_role_arn, null) + tasks_iam_role_description = try(each.value.tasks_iam_role_description, var.defaults.tasks_iam_role_description, null) + tasks_iam_role_name = try(each.value.tasks_iam_role_name, var.defaults.tasks_iam_role_name, null) + tasks_iam_role_path = try(each.value.tasks_iam_role_path, var.defaults.tasks_iam_role_path, null) + tasks_iam_role_permissions_boundary = try(each.value.tasks_iam_role_permissions_boundary, var.defaults.tasks_iam_role_permissions_boundary, null) + tasks_iam_role_policies = try(each.value.tasks_iam_role_policies, var.defaults.tasks_iam_role_policies, {}) + tasks_iam_role_statements = try(each.value.tasks_iam_role_statements, var.defaults.tasks_iam_role_statements, {}) + tasks_iam_role_tags = try(each.value.tasks_iam_role_tags, var.defaults.tasks_iam_role_tags, {}) + tasks_iam_role_use_name_prefix = try(each.value.tasks_iam_role_use_name_prefix, var.defaults.tasks_iam_role_use_name_prefix, true) + timeouts = try(each.value.timeouts, var.defaults.timeouts, {}) + triggers = try(each.value.triggers, var.defaults.triggers, {}) + volume = try(each.value.volume, var.defaults.volume, {}) + wait_for_steady_state = try(each.value.wait_for_steady_state, var.defaults.wait_for_steady_state, null) + wait_until_stable = try(each.value.wait_until_stable, var.defaults.wait_until_stable, null) + wait_until_stable_timeout = try(each.value.wait_until_stable_timeout, var.defaults.wait_until_stable_timeout, null) +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/service/outputs.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/service/outputs.tf new file mode 100644 index 000000000..ec6da5f4a --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/service/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/service/variables.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/service/variables.tf new file mode 100644 index 000000000..a6ea0962c --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/service/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/service/versions.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/service/versions.tf new file mode 100644 index 000000000..682191e70 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/service/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.66.1" + } + } +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/variables.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/variables.tf new file mode 100644 index 000000000..a6ea0962c --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/versions.tf b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/versions.tf new file mode 100644 index 000000000..682191e70 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/terraform-aws-ecs/wrappers/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.66.1" + } + } +} diff --git a/modules/ecs_cluster/default/0.1/variables.tf b/modules/ecs_cluster/default/0.1/variables.tf new file mode 100644 index 000000000..03b3e6081 --- /dev/null +++ b/modules/ecs_cluster/default/0.1/variables.tf @@ -0,0 +1,63 @@ +variable "cluster" { + type = any + +} + +variable "baseinfra" { + type = any + +} + +variable "cc_metadata" { + type = any +} + +variable "instance" { + type = any + default = {} +} + +variable "advanced" { + type = any + default = {} +} + +variable "instance_name" { + type = string + default = "" +} + +variable "inputs" { + type = any + default = [] +} + +variable "environment" { + type = any + default = {} +} + +variable "release_metadata" { + type = any + default = {} +} + +variable "instance_type" { + type = string + default = "" +} + +variable "iac_version" { + type = string + default = "" +} + +variable "generate_release_metadata" { + type = bool + default = false +} + +variable "settings" { + type = any + default = {} +} diff --git a/modules/helm/k8s/0.1/facets.yaml b/modules/helm/k8s/0.1/facets.yaml index 785703507..091c5a853 100644 --- a/modules/helm/k8s/0.1/facets.yaml +++ b/modules/helm/k8s/0.1/facets.yaml @@ -27,10 +27,10 @@ spec: repository: type: string title: Repository - description: URL for the helm repository - x-ui-placeholder: Enter repository URL - pattern: "[a-zA-Z0-9:/.-]+" - x-ui-error-message: "Invalid URL format. The URL must start with :// and contain only valid URL characters" + description: URL or relative path for the helm repository + x-ui-placeholder: Enter URL or relative path of the repository + pattern: "[a-zA-Z0-9:/._-]+" + x-ui-error-message: "Invalid input format. The URL or relative path should contain only valid regex `[a-zA-Z0-9:/._-]+`" version: type: string title: Version @@ -88,4 +88,4 @@ sample: chart: datadog namespace: default wait: false - values: \ No newline at end of file + values: diff --git a/modules/kafka_user/default/0.2/facets.yaml b/modules/kafka_user/default/0.2/facets.yaml new file mode 100644 index 000000000..44e62f8e6 --- /dev/null +++ b/modules/kafka_user/default/0.2/facets.yaml @@ -0,0 +1,28 @@ +intent: kafka_user +flavor: default +version: '0.2' +description: Adds kafka_user - default flavor +clouds: + - aws + - gcp + - azure + - kubernetes +lifecycle: ENVIRONMENT +input_type: instance +inputs: + kafka_details: + type: "@output/kafka" + displayName: Kafka + description: The details of Kafka where the user needs to be created + optional: false +spec: + title: Kafka User + type: object + description: Specifications of user for Kafka +sample: + kind: kafka_user + flavor: default + version: '0.2' + metadata: { } + spec: { } + disabled: true diff --git a/modules/kafka_user/default/0.2/locals.tf b/modules/kafka_user/default/0.2/locals.tf new file mode 100644 index 000000000..d555d862b --- /dev/null +++ b/modules/kafka_user/default/0.2/locals.tf @@ -0,0 +1,3 @@ +locals { + +} diff --git a/modules/kafka_user/default/0.2/main.tf b/modules/kafka_user/default/0.2/main.tf new file mode 100644 index 000000000..e69de29bb diff --git a/modules/kafka_user/default/0.2/outputs.tf b/modules/kafka_user/default/0.2/outputs.tf new file mode 100644 index 000000000..70733601b --- /dev/null +++ b/modules/kafka_user/default/0.2/outputs.tf @@ -0,0 +1,8 @@ +locals { + output_attributes = { + username = var.inputs.kafka_details.interfaces.cluster.username + password = sensitive(var.inputs.kafka_details.interfaces.cluster.password) + secrets = ["password"] + } + output_interfaces = {} +} diff --git a/modules/kafka_user/default/0.2/variables.tf b/modules/kafka_user/default/0.2/variables.tf new file mode 100644 index 000000000..66ea5559f --- /dev/null +++ b/modules/kafka_user/default/0.2/variables.tf @@ -0,0 +1,42 @@ +variable "cluster" { + type = any + default = { + } +} + +variable "baseinfra" { + type = any + default = { + k8s_details = { + registry_secret_objects = [] + } + } +} + +variable "cc_metadata" { + type = any + default = { + tenant_base_domain : "tenant.facets.cloud" + } +} + +variable "instance" { + type = any +} + + +variable "instance_name" { + type = string +} + +variable "environment" { + type = any + default = { + namespace = "default" + } +} + +variable "inputs" { + type = any + default = {} +} diff --git a/modules/mongo_user/default/0.2/facets.yaml b/modules/mongo_user/default/0.2/facets.yaml new file mode 100644 index 000000000..6edbd19f2 --- /dev/null +++ b/modules/mongo_user/default/0.2/facets.yaml @@ -0,0 +1,123 @@ +intent: mongo_user +flavor: default +version: '0.2' +description: Adds mongo_user - default flavor +clouds: +- aws +- gcp +- azure +- kubernetes +depends_on: [ ] +lifecycle: ENVIRONMENT +input_type: instance +inputs: + kubernetes_details: + optional: false + type: "@outputs/kubernetes" + default: + displayName: Kubernetes Cluster + resource_type: kubernetes_cluster + resource_name: default + mongo_details: + type: "@outputs/mongo" + displayName: Mongo + description: The details of Mongo where the user needs to be created + optional: false +spec: + title: MongoDB User + type: object + description: Specifications of user for MongoDB + properties: + database: + type: string + title: Database + description: The database name + permissions: + type: object + title: Permissions + description: The permissions for the user + patternProperties: + type: string + title: Permission Name + description: The permission Configurations for a specific user + keyPattern: "^[0-9]+[m]?$" + properties: + permission: + type: string + title: Permission + description: The permission for the user + x-ui-placeholder: "Enter comma separated permissions" + database: + type: string + title: Database + description: The database name for which the permission should be given + x-ui-placeholder: "Enter the database name" + collection: + type: string + title: Collection + description: The collection name of the database + x-ui-placeholder: "Enter the collection name" + cluster: + type: boolean + title: Cluster + description: Determines if permission should be at cluster level + mongo_user: + type: object + title: Mongo User + description: The user details for MongoDB + properties: + user: + type: object + title: User + description: The custom user details + properties: + username: + type: string + title: Username + description: The username for the user + x-ui-placeholder: "Enter the username" + password: + type: string + title: Password + description: The password for the user + x-ui-placeholder: "Enter the password" + customData: + type: object + title: Custom Data + description: The custom data for the database + x-ui-yaml-editor: true + mechanisms: + type: string + title: Mechanisms + description: The mechanisms for the user + x-ui-placeholder: "Enter the mechanisms" + dbRoles: + type: object + title: DB Roles + description: The database roles for the user + x-ui-yaml-editor: true + rolesToRole: + type: string + title: Roles to Role + description: The roles to role for the user +sample: + $schema: https://facets-cloud.github.io/facets-schemas/schemas/mongo_user/mongo_user.schema.json + kind: mongo_user + flavor: default + version: '0.2' + disabled: false + metadata: {} + out: {} + spec: + database: stage + permissions: + p1: + permission: createCollection,listCollections + database: stage + collection: '' + cluster: true + p2: + permission: find,update,insert + database: stage + collection: myCollection2 + cluster: false diff --git a/modules/mongo_user/default/0.2/locals.tf b/modules/mongo_user/default/0.2/locals.tf new file mode 100644 index 000000000..c21586013 --- /dev/null +++ b/modules/mongo_user/default/0.2/locals.tf @@ -0,0 +1,15 @@ +locals { + metadata = lookup(var.instance, "metadata", {}) + spec = lookup(var.instance, "spec", {}) + advanced = lookup(var.instance, "advanced", {}) + namespace = lookup(local.metadata, "namespace", var.environment.namespace) + permissions = lookup(local.spec, "permissions", {}) + database = lookup(local.spec, "database", "") + + mongo_user = lookup(local.spec, "mongo_user", {}) + user = lookup(local.mongo_user, "user", {}) + role = lookup(local.mongo_user, "role", {}) + + user_name = lookup(local.user, "username", module.unique_name.name) + user_password = lookup(local.user, "password", module.user_password.result) +} diff --git a/modules/mongo_user/default/0.2/main.tf b/modules/mongo_user/default/0.2/main.tf new file mode 100644 index 000000000..e6b584dac --- /dev/null +++ b/modules/mongo_user/default/0.2/main.tf @@ -0,0 +1,109 @@ +module "unique_name" { + source = "github.com/Facets-cloud/facets-utility-modules//name" + environment = var.environment + limit = 32 + resource_name = var.instance_name + resource_type = "mongo-user" + is_k8s = true + globally_unique = false +} + +module "user_password" { + source = "github.com/Facets-cloud/facets-utility-modules//password" + length = 32 +} + +resource "kubernetes_secret" "user-password" { + metadata { + name = module.unique_name.name + } + + data = { + "${local.user_name}" = local.user_password + } +} + +module "mongo-role" { + for_each = local.permissions + + source = "github.com/Facets-cloud/facets-utility-modules//any-k8s-resource" + name = "${module.unique_name.name}-${each.key}" + namespace = local.namespace + advanced_config = {} + data = { + apiVersion = "mongo.facets.cloud/v1alpha1" + kind = "Role" + metadata = { + name = "${module.unique_name.name}-${each.key}" + namespace = var.environment.namespace + annotations = { + "app.kubernetes.io/managed-by" = "mongodb-auth-operator" + } + } + spec = { + connectionString = var.inputs.mongo_details.interfaces.writer.connection_string + database = local.database + dbRoles = [for role in lookup(local.role, "dbRoles", {}) : role] + privileges = [ + { + actions = split(",", each.value.permission) + resource = { + collection = lookup(each.value, "collection", "") + db = lookup(each.value, "database", "") + cluster = lookup(each.value, "cluster", lookup(each.value, "collection", "") == "" ? true : false) + } + + } + ] + rolesToRole = try(split(",", lookup(local.role, "rolesToRole", [])),[]) + } + } +} + +module "mongo-user" { + depends_on = [ + module.mongo-role + ] + + source = "github.com/Facets-cloud/facets-utility-modules//any-k8s-resource" + name = local.user_name + namespace = local.namespace + advanced_config = {} + data = { + apiVersion = "mongo.facets.cloud/v1alpha1" + kind = "User" + metadata = { + name = local.user_name + namespace = local.namespace + annotations = { + "app.kubernetes.io/managed-by" = "mongodb-auth-operator" + } + } + spec = { + authenticationRestrictions = [ + for val in lookup(local.user, "authenticationRestrictions", {}) : { + clientSource = split(",", val.clientSource) + serverAddress = split(",", val.serverAddress) + } + ] + connectionString = var.inputs.mongo_details.interfaces.writer.connection_string + customData = lookup(local.user, "customData", {}) + database = local.database + dbRoles = concat([ + for key, val in local.spec.permissions : + { + db = val.database + role = "${module.unique_name.name}-${key}" + } + ], + [for role in lookup(local.user, "dbRoles", {}) : role] + ) + mechanisms = split(",", lookup(local.user, "mechanisms", "SCRAM-SHA-1")) + passwordRef = { + name = kubernetes_secret.user-password.metadata[0].name + namespace = kubernetes_secret.user-password.metadata[0].namespace + } + rolesToRole = try(split(",", lookup(local.user, "rolesToRole", [])),[]) + } + } +} \ No newline at end of file diff --git a/modules/mongo_user/default/0.2/module.json b/modules/mongo_user/default/0.2/module.json new file mode 100644 index 000000000..0885d89cd --- /dev/null +++ b/modules/mongo_user/default/0.2/module.json @@ -0,0 +1,18 @@ +{ + "provides": "mongo_user", + "flavors": [ + "default" + ], + "supported_clouds": [ + "aws", + "gcp", + "azure", + "kubernetes" + ], + "inputs": {}, + "version": "0.2", + "depends_on": [], + "lifecycle": "ENVIRONMENT", + "input_type": "instance", + "composition": {} +} \ No newline at end of file diff --git a/modules/mongo_user/default/0.2/outputs.tf b/modules/mongo_user/default/0.2/outputs.tf new file mode 100644 index 000000000..70e1e1dd9 --- /dev/null +++ b/modules/mongo_user/default/0.2/outputs.tf @@ -0,0 +1,16 @@ +locals { + output_attributes = { + username = local.user_name + password = local.user_password + secrets = ["password"] + } + output_interfaces = {} +} + +output "username" { + value = local.user_name +} + +output "password" { + value = local.user_password +} diff --git a/modules/mongo_user/default/0.2/test/test.json b/modules/mongo_user/default/0.2/test/test.json new file mode 100644 index 000000000..b9a866b9d --- /dev/null +++ b/modules/mongo_user/default/0.2/test/test.json @@ -0,0 +1,41 @@ +{ + "kind": "mongo_user", + "flavor": "default", + "version": "0.1", + "disabled": false, + "spec": { + "permissions": { + "p1": { + "permission": "createCollection,listCollections", + "database": "stage", + "collection": "", + "cluster": true + }, + "p2": { + "permission": "find,update,insert", + "database": "stage", + "collection": "myCollection2", + "cluster": true + } + }, + "mongo_user": { + "user": { + "username": "tester", + "password": "xyz", + "customData": { + "employeeID": "167824", + "profile": "sysAdmin" + }, + "mechanisms": "SCRAM-SHA-1", + "dbRoles": { + "dbrole3": { + "db": "stage", + "role": "stage4" + } + }, + "rolesToRole": "stage3" + } + } + }, + "advanced": {} +} diff --git a/modules/mongo_user/default/0.2/test/test.tf b/modules/mongo_user/default/0.2/test/test.tf new file mode 100644 index 000000000..4688e67fd --- /dev/null +++ b/modules/mongo_user/default/0.2/test/test.tf @@ -0,0 +1,34 @@ +locals { + definition_object = jsondecode(file("test.json")) +} + +module "tested" { + source = "../" + instance = local.definition_object + instance_name = "test-user" + + environment = { + namespace = "default" + unique_name = "mongo" + } +} + +output "username" { + value = module.tested.username + sensitive = true +} + +output "password" { + value = module.tested.password + sensitive = true +} + +provider "kubernetes" { + config_path = "" +} + +provider "helm" { + kubernetes { + config_path = "" + } +} diff --git a/modules/mongo_user/default/0.2/variables.tf b/modules/mongo_user/default/0.2/variables.tf new file mode 100644 index 000000000..231fe473c --- /dev/null +++ b/modules/mongo_user/default/0.2/variables.tf @@ -0,0 +1,37 @@ +variable "cluster" { + type = any + default = { + } +} + +variable "baseinfra" { + type = any + default = { + k8s_details = { + registry_secret_objects = [] + } + } +} + +variable "cc_metadata" { + type = any + default = { + tenant_base_domain : "tenant.facets.cloud" + } +} + +variable "instance" { + type = any +} + + +variable "instance_name" { + type = string +} + +variable "environment" { + type = any + default = { + namespace = "default" + } +} diff --git a/modules/mongo_user/default/0.2/versions.tf b/modules/mongo_user/default/0.2/versions.tf new file mode 100644 index 000000000..5d7c4592f --- /dev/null +++ b/modules/mongo_user/default/0.2/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + } + } + required_version = ">= 0.13" +} diff --git a/modules/mysql_user/default/0.2/facets.yaml b/modules/mysql_user/default/0.2/facets.yaml new file mode 100644 index 000000000..ee9ce5f6f --- /dev/null +++ b/modules/mysql_user/default/0.2/facets.yaml @@ -0,0 +1,80 @@ +intent: mysql_user +flavor: default +version: '0.2' +description: Adds mysql_user - default flavor +clouds: + - aws + - gcp + - azure + - kubernetes +lifecycle: ENVIRONMENT +input_type: instance +composition: { } +inputs: + kubernetes_details: + type: "@output/kubernetes" + displayName: Kubernetes Cluster + description: The details of Kubernetes where the operator for creating mysql user will be installed + optional: false + default: + resource_type: kubernetes_cluster + resource_name: default + mysql_details: + type: "@output/mysql" + displayName: MySQL + description: The details of MySQL where the user needs to be created + optional: false +spec: + title: MySQL User Spec + description: Specification for mysql user + type: object + properties: + permissions: + title: Permissions + description: Map of permissions that will be provided to the postgres user + type: object + patternProperties: + keyPattern: "^[a-zA-Z0-9_-]+$" + type: object + title: Permission + description: Permission Object + properties: + permission: + type: string + title: Permission + description: Permission for the postgres user + enum: + - RO + - RWO + - ADMIN + - RWC + - RWD + - RWCT + database: + type: string + title: Database + description: Database name where this permission should be given + pattern: "\\*|^[a-zA-Z0-9]+([-_][a-zA-Z0-9]+)*$" + table: + type: string + title: Table + description: Table in the database where this permission should be given + patten: "\\*|^[a-zA-Z0-9]+([-_][a-zA-Z0-9]+)*$" + required: + - permission + - database + - table + required: + - permissions +sample: + kind: mysql_user + flavor: default + version: '0.2' + metadata: { } + spec: + permissions: + permission1: + permission: ADMIN + database: '*' + table: '*' + disabled: true diff --git a/modules/mysql_user/default/0.2/locals.tf b/modules/mysql_user/default/0.2/locals.tf new file mode 100644 index 000000000..f39b0381d --- /dev/null +++ b/modules/mysql_user/default/0.2/locals.tf @@ -0,0 +1,104 @@ +locals { + spec = lookup(var.instance, "spec", {}) + advanced = lookup(var.instance, "advanced", {}) + namespace = lookup(lookup(var.instance, "metadata", {}), "namespace", var.environment.namespace) + username = var.inputs.mysql_details.interfaces.writer.username + password = var.inputs.mysql_details.interfaces.writer.password + host = var.inputs.mysql_details.interfaces.writer.host + port = var.inputs.mysql_details.interfaces.writer.port + canned_permissions = { + RO = ["SELECT"] + RWO = ["SELECT", "INSERT", "UPDATE"] + ADMIN = ["ALL"] + RWC = ["SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "ALTER", "INDEX", "REFERENCES"] + RWD = ["SELECT", "INSERT", "UPDATE", "DELETE"] + RWCT = [ + "SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "ALTER", "INDEX", "REFERENCES", + "CREATE TEMPORARY TABLES" + ] + } + mysql_user = lookup(local.advanced, "mysql_user", {}) + user_name = lookup(local.mysql_user, "user_name", module.username.name) + resources_data = concat([ + { + apiVersion = "mysql.sql.crossplane.io/v1alpha1" + kind = "ProviderConfig" + metadata = { + name = "${module.release_name.name}-pc", + namespace = local.namespace + labels = { + resourceName = "${module.release_name.name}-pc" + resourceType = "k8s_resource" + } + } + spec = { + credentials = { + source = "MySQLConnectionSecret" + connectionSecretRef = { + name = kubernetes_secret.db_conn_details.metadata[0].name + namespace = kubernetes_secret.db_conn_details.metadata[0].namespace + } + } + "tls" = "skip-verify" + } + }, + { + apiVersion = "mysql.sql.crossplane.io/v1alpha1" + kind = "User" + metadata = { + name = "${module.release_name.name}-user", + namespace = local.namespace + labels = { + resourceName = "${module.release_name.name}-user" + resourceType = "k8s_resource" + } + annotations = { + "crossplane.io/external-name" = local.user_name + } + } + spec = { + providerConfigRef = { + name = "${module.release_name.name}-pc" + } + forProvider = { + # Decide whether to have resourceOptions + # https://doc.crds.dev/github.com/crossplane-contrib/provider-sql/mysql.sql.crossplane.io/User/v1alpha1@v0.7.0 + passwordSecretRef = { + name = kubernetes_secret.db_conn_details.metadata[0].name + namespace = kubernetes_secret.db_conn_details.metadata[0].namespace + key = "user_password" + }, + resourceOptions = lookup(local.mysql_user, "resource_options", {}) + } + } + }, + ], local.grants) + grants = [ + for permission_name, permission_detail in local.spec.permissions : { + apiVersion = "mysql.sql.crossplane.io/v1alpha1" + kind = "Grant" + metadata = { + name = "${module.release_name.name}-${permission_name}", + namespace = local.namespace + labels = { + resourceName = "${module.release_name.name}-pc" + resourceType = "k8s_resource" + } + } + spec = { + providerConfigRef = { + name = "${module.release_name.name}-pc" + } + forProvider = { + privileges = lookup(local.canned_permissions, permission_detail.permission) + user = local.user_name + userRef = { + name = "${module.release_name.name}-user" + } + database = permission_detail.database + table = permission_detail.table + } + } + } + ] +} diff --git a/modules/mysql_user/default/0.2/main.tf b/modules/mysql_user/default/0.2/main.tf new file mode 100644 index 000000000..ea472bd53 --- /dev/null +++ b/modules/mysql_user/default/0.2/main.tf @@ -0,0 +1,49 @@ +module "release_name" { + source = "github.com/Facets-cloud/facets-utility-modules//name" + environment = var.environment + limit = 248 + resource_name = var.instance_name + resource_type = "mysql-user" + is_k8s = true + globally_unique = false +} + +module "username" { + source = "github.com/Facets-cloud/facets-utility-modules//name" + environment = var.environment + limit = 32 + resource_name = var.instance_name + resource_type = "mysql-user" + is_k8s = true + globally_unique = false +} + +module "user_password" { + source = "github.com/Facets-cloud/facets-utility-modules//password" + length = 32 + special = false +} + +resource "kubernetes_secret" "db_conn_details" { + metadata { + name = module.release_name.name + namespace = local.namespace + } + + data = { + endpoint = local.host + username = local.username + password = local.password + port = local.port + user_password = lookup(local.mysql_user, "user_password", module.user_password.result) + user_name = local.user_name + } +} + +module "mysql_user_resources" { + source = "github.com/Facets-cloud/facets-utility-modules//any-k8s-resources" + namespace = local.namespace + advanced_config = {} + name = module.release_name.name + resources_data = local.resources_data +} diff --git a/modules/mysql_user/default/0.2/outputs.tf b/modules/mysql_user/default/0.2/outputs.tf new file mode 100644 index 000000000..19fa6b4e0 --- /dev/null +++ b/modules/mysql_user/default/0.2/outputs.tf @@ -0,0 +1,8 @@ +locals { + output_attributes = { + username = lookup(local.mysql_user, "user_name", module.username.name) + password = lookup(local.mysql_user, "user_password", module.user_password.result) + secrets = ["password"] + } + output_interfaces = {} +} diff --git a/modules/mysql_user/default/0.2/variables.tf b/modules/mysql_user/default/0.2/variables.tf new file mode 100644 index 000000000..58031a881 --- /dev/null +++ b/modules/mysql_user/default/0.2/variables.tf @@ -0,0 +1,42 @@ +variable "cluster" { + type = any + default = { + } +} + +variable "baseinfra" { + type = any + default = { + k8s_details = { + registry_secret_objects = [] + } + } +} + +variable "cc_metadata" { + type = any + default = { + tenant_base_domain : "tenant.facets.cloud" + } +} + +variable "instance" { + type = any +} + + +variable "instance_name" { + type = string +} + +variable "environment" { + type = any + default = { + namespace = "default" + } +} + +variable "inputs" { + type = any + default = {} +} diff --git a/modules/postgres/external/0.1/facets.yaml b/modules/postgres/external/0.1/facets.yaml new file mode 100644 index 000000000..3b137e6e4 --- /dev/null +++ b/modules/postgres/external/0.1/facets.yaml @@ -0,0 +1,60 @@ +intent: postgres +flavor: external +version: 0.1 +clouds: + - aws + - gcp + - azure + - kubernetes + +spec: + type: object + properties: + writer_details: + title: Writer Details + x-ui-overrides-only: true + type: object + properties: + host: + title: Host + type: string + username: + title: Username + type: string + password: + title: Password + type: string + x-ui-mask: true + port: + title: Port + type: string + required: ["port", "host", "username", "password"] + reader_details: + title: Reader Details + x-ui-overrides-only: true + type: object + properties: + host: + title: Host + type: string + username: + title: Username + type: string + password: + title: Password + type: string + x-ui-secret-ref: true + port: + title: Port + type: string + required: ["port", "host", "username", "password"] + +outputs: + default: + type: "@outputs/postgres" + +sample: + kind: "postgres" + flavor: "external" + version: "0.1" + spec: {} diff --git a/modules/postgres/external/0.1/main.tf b/modules/postgres/external/0.1/main.tf new file mode 100644 index 000000000..36a225b98 --- /dev/null +++ b/modules/postgres/external/0.1/main.tf @@ -0,0 +1,24 @@ +locals { + output_interfaces = { + reader = { + host = var.instance.spec.reader_details.host + username = var.instance.spec.reader_details.username + password = sensitive(var.instance.spec.reader_details.password) + port = var.instance.spec.reader_details.port + connection_string = sensitive("postgres://${var.instance.spec.reader_details.username}:${var.instance.spec.reader_details.password}@${var.instance.spec.reader_details.host}:${var.instance.spec.reader_details.port}") + secrets = ["password", "connection_string"] + db_names = [] # Assuming db_names are not provided + } + writer = { + host = var.instance.spec.writer_details.host + username = var.instance.spec.writer_details.username + password = sensitive(var.instance.spec.writer_details.password) + port = var.instance.spec.writer_details.port + connection_string = sensitive("postgres://${var.instance.spec.writer_details.username}:${var.instance.spec.writer_details.password}@${var.instance.spec.writer_details.host}:${var.instance.spec.writer_details.port}") + secrets = ["password", "connection_string"] + db_names = [] # Assuming db_names are not provided + } + } + + output_attributes = {} // Leaving attributes empty +} diff --git a/modules/postgres/external/0.1/variables.tf b/modules/postgres/external/0.1/variables.tf new file mode 100644 index 000000000..f5718cb0b --- /dev/null +++ b/modules/postgres/external/0.1/variables.tf @@ -0,0 +1,40 @@ +variable "instance" { + description = "The JSON representation of the resource in the Facets blueprint." + type = object({ + kind = string + flavor = string + version = string + spec = object({ + writer_details = object({ + host = string + username = string + password = string + port = string + }) + reader_details = object({ + host = string + username = string + password = string + port = string + }) + }) + }) +} + +variable "instance_name" { + description = "The architectural name for the resource as added in the Facets blueprint designer." + type = string +} + +variable "environment" { + description = "An object containing details about the environment." + type = object({ + name = string + unique_name = string + }) +} + +variable "inputs" { + description = "A map of inputs requested by the module developer." + type = map(any) +} diff --git a/modules/postgres_user/default/0.2/facets.yaml b/modules/postgres_user/default/0.2/facets.yaml new file mode 100644 index 000000000..e1d0b99b3 --- /dev/null +++ b/modules/postgres_user/default/0.2/facets.yaml @@ -0,0 +1,93 @@ +intent: postgres_user +flavor: default +version: '0.2' +description: Adds postgres_user - default flavor +clouds: + - aws + - gcp + - azure + - kubernetes +lifecycle: ENVIRONMENT +input_type: instance +composition: { } +inputs: + kubernetes_details: + type: "@output/kubernetes" + displayName: Kubernetes Cluster + description: Details of Kubernetes where the postgres operator will be deployed + optional: false + default: + resource_type: kubernetes_cluster + resource_name: default + postgres_details: + type: "@output/postgres" + displayName: Postgres + description: Details of Postgres where the user needs to be created + optional: false +spec: + title: Postgres User Spec + description: Specification for postgres user + type: object + properties: + role_name: + title: Role Name + description: Name of role and user to be created in postgres + type: string + permissions: + title: Permissions + description: Map of permissions that will be provided to the postgres user + type: object + patternProperties: + keyPattern: "^[a-zA-Z0-9_-]+$" + type: object + title: Permission + description: Permission Object + properties: + permission: + type: string + title: Permission + description: Permission for the postgres user + enum: + - RO + - RWO + - ADMIN + database: + type: string + title: Database + description: Database name where this permission should be given + pattern: "^[a-zA-Z0-9]+([-_][a-zA-Z0-9]+)*$" + schema: + type: string + title: Schema + description: Schema in the database where this permission should be given + pattern: "^[a-zA-Z0-9]+([-_][a-zA-Z0-9]+)*$" + table: + type: string + title: Table + description: Table in the database where this permission should be given + patten: "\\*|^[a-zA-Z0-9]+([-_][a-zA-Z0-9]+)*$" + required: + - permission + - database + - schema + - table + required: + - permissions +sample: + kind: postgres_user + flavor: default + version: '0.2' + metadata: { } + spec: + permissions: + permission1: + permission: ADMIN + database: test_db1 + schema: public + table: test1 + permission2: + permission: ADMIN + database: test_db1 + schema: public + table: '*' + disabled: true diff --git a/modules/postgres_user/default/0.2/locals.tf b/modules/postgres_user/default/0.2/locals.tf new file mode 100644 index 000000000..696153fe9 --- /dev/null +++ b/modules/postgres_user/default/0.2/locals.tf @@ -0,0 +1,18 @@ +locals { + spec = lookup(var.instance, "spec", {}) + advanced = lookup(var.instance, "advanced", {}) + namespace = lookup(lookup(var.instance, "metadata", {}), "namespace", var.environment.namespace) + username = var.inputs.postgres_details.interfaces.writer.username + password = var.inputs.postgres_details.interfaces.writer.password + host = var.inputs.postgres_details.interfaces.writer.host + port = var.inputs.postgres_details.interfaces.writer.port + canned_permissions = { + RO = ["SELECT"] + RWO = ["SELECT", "INSERT", "UPDATE"] + ADMIN = ["ALL"] + } + postgres_user = lookup(local.advanced, "postgres_user", {}) + role = lookup(local.postgres_user, "role", {}) + privileges = lookup(local.role, "privileges", {}) + role_name = lookup(local.spec, "role_name", module.unique_name.name) +} diff --git a/modules/postgres_user/default/0.2/main.tf b/modules/postgres_user/default/0.2/main.tf new file mode 100644 index 000000000..7d425cbd3 --- /dev/null +++ b/modules/postgres_user/default/0.2/main.tf @@ -0,0 +1,107 @@ +module "unique_name" { + source = "github.com/Facets-cloud/facets-utility-modules//name" + environment = var.environment + limit = 32 + resource_name = var.instance_name + resource_type = "facets" + is_k8s = true + globally_unique = false +} + +module "user_password" { + source = "github.com/Facets-cloud/facets-utility-modules//password" + length = 32 +} + +resource "kubernetes_secret" "db_conn_details" { + metadata { + name = local.role_name + namespace = local.namespace + } + + data = { + endpoint = local.host + username = local.username + password = local.password + port = local.port + user_password = lookup(local.postgres_user, "user_password", module.user_password.result) + } +} + +module "postgres-user" { + source = "github.com/Facets-cloud/facets-utility-modules//any-k8s-resource" + name = local.role_name + namespace = local.namespace + advanced_config = {} + data = { + apiVersion = "postgresql.facets.cloud/v1alpha1" + kind = "Role" + metadata = { + name = local.role_name + namespace = local.namespace + annotations = { + "app.kubernetes.io/managed-by" = "database-operator" + } + } + spec = { + connectSecretRef = { + name = kubernetes_secret.db_conn_details.metadata[0].name + namespace = kubernetes_secret.db_conn_details.metadata[0].namespace + } + passwordSecretRef = { + name = kubernetes_secret.db_conn_details.metadata[0].name + namespace = kubernetes_secret.db_conn_details.metadata[0].namespace + key = "user_password" + }, + connectionLimit = lookup(local.role, "connectionLimit", 100) + privileges = { + login = true + bypassRls = lookup(local.privileges, "bypassRls", false) + createDb = lookup(local.privileges, "createDb", false) + createRole = lookup(local.privileges, "createRole", false) + inherit = lookup(local.privileges, "inherit", false) + replication = lookup(local.privileges, "replication", false) + superUser = lookup(local.privileges, "superUser", false) + } + } + } +} + + +module "postgres-grants" { + for_each = local.spec.permissions + + depends_on = [ + module.postgres-user + ] + + source = "github.com/Facets-cloud/facets-utility-modules//any-k8s-resource" + name = "${local.role_name}-${each.key}" + namespace = local.namespace + advanced_config = {} + data = { + apiVersion = "postgresql.facets.cloud/v1alpha1" + kind = "Grant" + metadata = { + name = "${local.role_name}-${each.key}" + namespace = local.namespace + annotations = { + "app.kubernetes.io/managed-by" = "database-operator" + } + } + spec = merge({ + roleRef = { + name = local.role_name + namespace = local.namespace + } + privileges = lookup(local.canned_permissions, each.value.permission) + database = each.value.database + }, + lookup(each.value, "schema", null) != null ? { + schema = each.value.schema + } : {}, + lookup(each.value, "table", null) != null ? { + table = each.value.table + } : {}) + } +} diff --git a/modules/postgres_user/default/0.2/outputs.tf b/modules/postgres_user/default/0.2/outputs.tf new file mode 100644 index 000000000..2f7a530ad --- /dev/null +++ b/modules/postgres_user/default/0.2/outputs.tf @@ -0,0 +1,16 @@ +locals { + output_attributes = { + username = local.role_name + password = lookup(local.postgres_user,"user_password",module.user_password.result) + secrets = ["password"] + } + output_interfaces = {} +} + +output "username" { + value = local.role_name +} + +output "password" { + value = lookup(local.postgres_user,"user_password",module.user_password.result) +} diff --git a/modules/postgres_user/default/0.2/variables.tf b/modules/postgres_user/default/0.2/variables.tf new file mode 100644 index 000000000..3e7e4fd37 --- /dev/null +++ b/modules/postgres_user/default/0.2/variables.tf @@ -0,0 +1,41 @@ +variable "cluster" { + type = any + default = { + } +} + +variable "baseinfra" { + type = any + default = { + k8s_details = { + registry_secret_objects = [] + } + } +} + +variable "cc_metadata" { + type = any + default = { + tenant_base_domain : "tenant.facets.cloud" + } +} + +variable "instance" { + type = any +} + +variable "instance_name" { + type = string +} + +variable "environment" { + type = any + default = { + namespace = "default" + } +} + +variable "inputs" { + type = any + default = {} +} diff --git a/modules/service/deployment/0.1/facets.yaml b/modules/service/deployment/0.1/facets.yaml index e075c3fa9..55f5d806f 100644 --- a/modules/service/deployment/0.1/facets.yaml +++ b/modules/service/deployment/0.1/facets.yaml @@ -73,9 +73,6 @@ spec: x-ui-placeholder: "IAM policy example. Eg: arn:aws:iam::123456789012:policy/policy1" x-ui-error-message: "Value doesn't match pattern, accepted value pattern Eg: arn:aws:iam::123456789012:policy/policy1" x-ui-output-type: "iam_policy_arn" - x-ui-api-source: - endpoint: "/cc-ui/v1/dropdown/{{stackName}}/output/{{outputType}}/references" - method: GET x-ui-typeable: true required: - arn @@ -704,7 +701,7 @@ spec: disruption_policy: type: object title: Disruption Policy - description: The disruption policy for the service + description: The disruption policy for the service. Both min available and max unavailable cannot be specified simultaneously in disruption policy. You must fill one of the two fields when defining a disruption policy to avoid release failures. properties: min_available: type: string diff --git a/modules/service/statefulset/0.1/facets.yaml b/modules/service/statefulset/0.1/facets.yaml index f60d62b93..a2690fca1 100644 --- a/modules/service/statefulset/0.1/facets.yaml +++ b/modules/service/statefulset/0.1/facets.yaml @@ -101,9 +101,6 @@ spec: x-ui-placeholder: "IAM policy example. Eg: arn:aws:iam::123456789012:policy/policy1" x-ui-error-message: "Value doesn't match pattern, accepted value pattern Eg: arn:aws:iam::123456789012:policy/policy1" x-ui-output-type: "iam_policy_arn" - x-ui-api-source: - endpoint: "/cc-ui/v1/dropdown/{{stackName}}/output/{{outputType}}/references" - method: GET x-ui-typeable: true required: - arn @@ -732,7 +729,7 @@ spec: disruption_policy: type: object title: Disruption Policy - description: The disruption policy for the service + description: The disruption policy for the service. Both min available and max unavailable cannot be specified simultaneously in disruption policy. You must fill one of the two fields when defining a disruption policy to avoid release failures. properties: min_available: type: string diff --git a/modules/service_bus/azure_service_bus/0.1/facets.yaml b/modules/service_bus/azure_service_bus/0.1/facets.yaml index 30f4ecfd6..66a97a04e 100644 --- a/modules/service_bus/azure_service_bus/0.1/facets.yaml +++ b/modules/service_bus/azure_service_bus/0.1/facets.yaml @@ -1,23 +1,235 @@ intent: service_bus flavor: azure_service_bus -version: '0.1' -description: Adds service_bus - azure_service_bus flavor +version: "0.1" +description: Create an Azure Service Bus clouds: -- azure + - azure +inputs: + network_details: + optional: true + type: "@outputs/azure_vpc" + default: + resource_type: network + resource_name: default +spec: + title: Azure Service Bus + description: Specification of the Azure Service Bus + type: object + properties: + sku: + type: string + title: SKU + description: The SKU of the Azure Service Bus + enum: + - Basic + - Standard + - Premium + default: Standard + capacity: + type: integer + title: Capacity + description: The capacity of the Azure Service Bus + default: 0 + queues: + type: object + title: Queues + description: Configuration for the queues + patternProperties: + keyPattern: "^[a-zA-Z0-9_.-]*$" + type: object + properties: + name: + type: string + title: Queue Name + description: Name of the queue + status: + type: string + title: Status + description: Status of the queue + enum: + - Active + - Disabled + default: Active + lock_duration: + type: integer + title: Lock Duration + description: Lock duration in minutes (enter number only) + default: 1 + maximum: 5 + max_message_size_in_kilobytes: + type: integer + title: Max Message Size (KB) + description: Maximum message size in kilobytes + max_size_in_megabytes: + type: integer + title: Max Size (MB) + description: Maximum size in megabytes + requires_duplicate_detection: + type: boolean + title: Requires Duplicate Detection + description: Whether duplicate detection is required + default: false + requires_session: + type: boolean + title: Requires Session + description: Whether session is required + default: false + dead_lettering_on_message_expiration: + type: boolean + title: Dead Lettering on Message Expiration + description: Whether dead lettering on message expiration is enabled + default: false + max_delivery_count: + type: integer + title: Max Delivery Count + description: Maximum delivery count + default: 10 + enable_batched_operations: + type: boolean + title: Enable Batched Operations + description: Whether batched operations are enabled + default: true + enable_partitioning: + type: boolean + title: Enable Partitioning + description: Whether partitioning is enabled + default: false + enable_express: + type: boolean + title: Enable Express + description: Whether express is enabled + default: false + forward_to: + type: string + title: Forward To + description: Forward to another queue or topic + forward_dead_lettered_messages_to: + type: string + title: Forward Dead Lettered Messages To + description: Forward dead lettered messages to another queue or topic + + topics: + type: object + title: Topics + description: Configuration for the topics + patternProperties: + keyPattern: "^[a-zA-Z0-9_.-]*$" + type: object + properties: + name: + type: string + title: Topic Name + description: Name of the topic + status: + type: string + title: Status + description: Status of the topic + enum: + - Active + - Disabled + default: Active + enable_batched_operations: + type: boolean + title: Enable Batched Operations + description: Whether batched operations are enabled + default: true + enable_express: + type: boolean + title: Enable Express + description: Whether express is enabled + default: false + enable_partitioning: + type: boolean + title: Enable Partitioning + description: Whether partitioning is enabled + default: false + max_message_size_in_kilobytes: + type: integer + title: Max Message Size (KB) + description: Maximum message size in kilobytes + max_size_in_megabytes: + type: integer + title: Max Size (MB) + description: Maximum size in megabytes + requires_duplicate_detection: + type: boolean + title: Requires Duplicate Detection + description: Whether duplicate detection is required + default: false + support_ordering: + type: boolean + title: Support Ordering + description: Whether ordering is supported + default: false + subscriptions: + type: object + title: Subscriptions + description: Configuration for the subscriptions + patternProperties: + keyPattern: "^[a-zA-Z0-9_.-]*$" + type: object + properties: + name: + type: string + title: Subscription Name + description: Name of the subscription + max_delivery_count: + type: integer + title: Max Delivery Count + description: Maximum delivery count + default: 10 + lock_duration: + type: integer + title: Lock Duration + description: Lock duration in minutes (enter number only) + default: 1 + maximum: 5 + dead_lettering_on_message_expiration: + type: boolean + title: Dead Lettering on Message Expiration + description: Whether dead lettering on message expiration is enabled + default: false + enable_batched_operations: + type: boolean + title: Enable Batched Operations + description: Whether batched operations are enabled + default: true + requires_session: + type: boolean + title: Requires Session + description: Whether session is required + default: false + status: + type: string + title: Status + description: Status of the subscription + enum: + - Active + - Disabled + default: Active + forward_to: + type: string + title: Forward To + description: Forward to another queue or topic + forward_dead_lettered_messages_to: + type: string + title: Forward Dead Lettered Messages To + description: Forward dead lettered messages to another queue or topic + sample: - $schema: https://facets-cloud.github.io/facets-schemas/schemas/service_bus/service_bus.schema.json kind: service_bus flavor: azure_service_bus - version: '0.1' + version: "0.1" + lifecycle: ENVIRONMENT disabled: true + depends_on: [] metadata: + name: azure_service_bus tags: {} spec: - size: - sku: '' - capacity: '' - topics: - test: - name: '' - status: Active - test2: {} + instance_name: "test_instance" + spec: + sku: "Standard" + capacity: 0 + queues: {} + topics: {} diff --git a/modules/vault/k8s/0.1/facets.yaml b/modules/vault/k8s/0.1/facets.yaml index 4ce6fbc85..220c6a5e3 100644 --- a/modules/vault/k8s/0.1/facets.yaml +++ b/modules/vault/k8s/0.1/facets.yaml @@ -7,6 +7,13 @@ clouds: - azure - gcp - kubernetes +inputs: + kubernetes_details: + optional: false + type: "@output/kubernetes" + default: + resource_type: kubernetes_cluster + resource_name: default spec: title: K8s type: object diff --git a/modules/vault/k8s/0.1/main.tf b/modules/vault/k8s/0.1/main.tf new file mode 100644 index 000000000..7d0709f6b --- /dev/null +++ b/modules/vault/k8s/0.1/main.tf @@ -0,0 +1,134 @@ +locals { + advanced = lookup(var.instance, "advanced", {}) + k8s = lookup(local.advanced, "k8s", {}) + vault = lookup(local.k8s, "vault", {}) + user_defined_helm_values = lookup(local.vault, "values", {}) + namespace = lookup(lookup(var.instance, "metadata", {}), "namespace", var.environment.namespace) + spec = lookup(var.instance, "spec", {}) + size = lookup(local.spec, "size", {}) + replica_count = tonumber(lookup(local.size, "instance_count", 1)) + standalone = local.replica_count == 1 ? true : false + limit_value = contains(keys(local.size), "cpu") && contains(keys(local.size), "memory") ? { + limits = { + cpu = lookup(local.size, "cpu_limit", local.size.cpu) + memory = lookup(local.size, "memory_limit", local.size.memory) + } + } : {} + requests_value = contains(keys(local.size), "cpu") && contains(keys(local.size), "memory") ? { + requests = { + cpu = local.size.cpu + memory = local.size.memory + } + } : {} + constructed_helm_values = { + server = { + image = { + tag = local.spec.vault_version + } + standalone = { + enabled = local.standalone + } + ha = { + enabled = !local.standalone + replicas = !local.standalone ? local.replica_count : 0 + } + resources = merge(local.limit_value, local.requests_value) + tolerations = var.environment.default_tolerations + dataStorage = { + size = local.size.volume + } + postStart = ["/bin/sh", "-c", + <<-EOT + sleep 10 + vault operator init -key-shares=1 -key-threshold=1 >> /vault/data/init.txt + cat /vault/data/init.txt | grep "Key " | awk '{print $NF}' | xargs -I{} vault operator unseal {} + echo $(cat /vault/data/init.txt | grep "Initial Root Token" | awk '{print $NF}') > /vault/data/root_token + EOT + ] + } + injector = { + tolerations = var.environment.default_tolerations + } + } +} + +module "name" { + source = "github.com/Facets-cloud/facets-utility-modules//name" + is_k8s = true + globally_unique = false + resource_type = "vault" + resource_name = var.instance_name + environment = var.environment + limit = 30 +} + +module "vault-pvc" { + count = local.replica_count == 1 ? 1 : 0 + source = "github.com/Facets-cloud/facets-utility-modules//pvc" + name = "data-${module.name.name}-0" + namespace = local.namespace + access_modes = ["ReadWriteOnce"] + volume_size = local.size.volume + provisioned_for = "${module.name.name}-0" + instance_name = module.name.name + kind = "vault" + additional_labels = lookup(lookup(local.user_defined_helm_values, "dataStorage", {}), "lables", {}) + cloud_tags = var.environment.cloud_tags +} + +resource "helm_release" "vault" { + depends_on = [module.vault-pvc] + name = module.name.name + repository = "https://helm.releases.hashicorp.com" + chart = "vault" + version = lookup(local.vault, "chart_version", "0.28.1") + namespace = local.namespace + wait = true + cleanup_on_fail = true + timeout = lookup(local.vault, "timeout", "300") + max_history = 10 + atomic = lookup(local.vault, "atomic", false) + values = [ + yamlencode(local.constructed_helm_values), + yamlencode(local.user_defined_helm_values) + ] +} + +resource "null_resource" "save_root_token" { + depends_on = [helm_release.vault] + provisioner "local-exec" { + environment = { + SERVER = var.inputs.kubernetes_details.attributes.legacy_outputs.k8s_details.auth.host + CA = base64encode(var.inputs.kubernetes_details.attributes.legacy_outputs.k8s_details.auth.cluster_ca_certificate) + TOKEN = var.inputs.kubernetes_details.attributes.legacy_outputs.k8s_details.auth.token + } + command = <: [{\"name\": \"\"}]}" + interfaces: diff --git a/outputs/kafka/output.facets.yaml b/outputs/kafka/output.facets.yaml new file mode 100644 index 000000000..6759a7840 --- /dev/null +++ b/outputs/kafka/output.facets.yaml @@ -0,0 +1,21 @@ +name: kafka +out: + type: object + title: Kafka + description: Kafka + properties: + attributes: null + interfaces: + cluster: + username: + required: true + type: string + password: + required: true + type: string + endpoint: + required: true + type: string + connection_string: + required: true + type: string diff --git a/outputs/kafka_user/output.facets.yaml b/outputs/kafka_user/output.facets.yaml new file mode 100644 index 000000000..3b5d3cf9c --- /dev/null +++ b/outputs/kafka_user/output.facets.yaml @@ -0,0 +1,15 @@ +name: kafka_user +out: + type: object + title: Kafka User + description: Kafka User + properties: + interfaces: null + attributes: + username: + required: true + type: string + password: + required: true + secret: true + type: string diff --git a/outputs/kubernetes_cluster/output.facets.yaml b/outputs/kubernetes_cluster/output.facets.yaml new file mode 100644 index 000000000..23a61f6e9 --- /dev/null +++ b/outputs/kubernetes_cluster/output.facets.yaml @@ -0,0 +1,12 @@ +name: kubernetes +out: + type: object + title: Kubernetes Cluster + description: Kubernetes + properties: + interfaces: null + attributes: + legacy_outputs: + type: object + required: true + secret: true diff --git a/outputs/mongo_user/output.facets.yaml b/outputs/mongo_user/output.facets.yaml new file mode 100644 index 000000000..f2de1e06f --- /dev/null +++ b/outputs/mongo_user/output.facets.yaml @@ -0,0 +1,15 @@ +name: mongo_user +out: + type: object + title: Mongo User + description: Mongo User + properties: + interfaces: null + attributes: + username: + required: true + type: string + password: + required: true + secret: true + type: string \ No newline at end of file diff --git a/outputs/mysql_user/output.facets.yaml b/outputs/mysql_user/output.facets.yaml new file mode 100644 index 000000000..a4b3fd88f --- /dev/null +++ b/outputs/mysql_user/output.facets.yaml @@ -0,0 +1,15 @@ +name: mysql_user +out: + type: object + title: Mysql User + description: Mysql User + properties: + interfaces: null + attributes: + username: + required: true + type: string + password: + required: true + secret: true + type: string diff --git a/outputs/postgres_user/output.facets.yaml b/outputs/postgres_user/output.facets.yaml new file mode 100644 index 000000000..b8a67608f --- /dev/null +++ b/outputs/postgres_user/output.facets.yaml @@ -0,0 +1,15 @@ +name: postgres_user +out: + type: object + title: Postgres User + description: Postgres User + properties: + interfaces: null + attributes: + username: + required: true + type: string + password: + required: true + secret: true + type: string diff --git a/outputs/service_bus/azure_service_bus/outputs.facets.yaml b/outputs/service_bus/azure_service_bus/outputs.facets.yaml new file mode 100644 index 000000000..ac68e2815 --- /dev/null +++ b/outputs/service_bus/azure_service_bus/outputs.facets.yaml @@ -0,0 +1,2 @@ +name: azure_service_bus_details +out: