├── .gitignore ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── LICENSE ├── README.rst ├── buildspec-dev.yml ├── buildspec-prod.yml ├── cfn.yaml ├── deploy-cfn.sh ├── environments ├── dev │ ├── config.tf │ ├── efs.tf │ ├── rds.tf │ ├── security_group.tf │ ├── vpc.tf │ └── web_app.tf ├── iam │ ├── config.tf │ └── iam_role.tf └── prod │ ├── config.tf │ ├── efs.tf │ ├── rds.tf │ ├── security_group.tf │ ├── vpc.tf │ └── web_app.tf ├── modules └── aws │ ├── ec2 │ └── autoscaling │ │ ├── files │ │ └── wordpress.sh │ │ ├── main.tf │ │ ├── outputs.tf │ │ └── variables.tf │ ├── efs │ ├── main.tf │ ├── output.tf │ └── variables.tf │ ├── network │ ├── sg │ │ ├── main.tf │ │ ├── output.tf │ │ └── variables.tf │ └── vpc │ │ ├── output.tf │ │ ├── variables.tf │ │ └── vpc.tf │ └── rds │ ├── main.tf │ ├── output.tf │ └── variables.tf ├── resources ├── AWS_Pop_Up_Loft_Munich.jpg ├── architecture.jpeg └── pipeline.jpeg └── test └── http_test.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | */.serverless 10 | */.plan.out 11 | **/*.tfstate.backup 12 | **/*terraform.tfstate 13 | **/.terraform 14 | 15 | # Distribution / packaging 16 | .Python 17 | env/ 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *,cover 52 | .hypothesis/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # dotenv 85 | .env 86 | 87 | # virtualenv 88 | .venv/ 89 | venv/ 90 | ENV/ 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | 99 | 100 | # Module directory 101 | **/.terraform/modules/ 102 | **/.terraform/plugins/ 103 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Owner 6 | ----- 7 | 8 | * Giulio Calzolari `@giuliocalzo `_ 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Hello! Thank you for choosing to help contribute to one of the Cloudreach OpenSource projects. There are many ways you can contribute and help is always welcome. We simply ask that you follow the following contribution policies. 6 | 7 | - `How to Contribute`_ 8 | 9 | - `Report Bugs`_ 10 | - `Enhancement Proposal`_ 11 | - `Contributing Code`_ 12 | 13 | - `Get Started`_ 14 | - `Credits`_ 15 | 16 | 17 | How to Contribute 18 | ----------------- 19 | 20 | Report Bugs 21 | *********** 22 | 23 | Note: DO NOT include your credentials in ANY code examples, descriptions, or media you make public. 24 | 25 | 26 | Before submitting a bug, please check our `issues page `_ to see if it's already been reported. 27 | 28 | When reporting a bug, fill out the required template, and please include as much detail as possible as it helps us resolve issues faster. 29 | 30 | 31 | Enhancement Proposal 32 | ******************** 33 | 34 | Enhancement proposals should: 35 | 36 | * Use a descriptive title. 37 | * Provide a step-by-step description of the suggested enhancement. 38 | * Provide specific examples to demonstrate the steps. 39 | * Describe the current behaviour and explain which behaviour you expected to see instead. 40 | * Keep the scope as narrow as possible, to make it easier to implement. 41 | 42 | Remember that this is a volunteer-driven project, and that contributions are welcome. 43 | 44 | 45 | Contributing Code 46 | ***************** 47 | 48 | Contributions should be made in response to a particular GitHub Issue. We find it easier to review code if we've already discussed what it should do, and assessed if it fits with the wider codebase. 49 | 50 | Beginner friendly issues are marked with the ``beginner friendly`` tag. We'll endeavour to write clear instructions on what we want to do, why we want to do it, and roughly how to do it. Feel free to ask us any questions that may arise. 51 | 52 | A good pull request: 53 | 54 | * Is clear. 55 | * Works across all supported version of Python. 56 | * Complies with the existing codebase style (`flake8 `_, `pylint `_). 57 | * Includes `docstrings `_ and comments for unintuitive sections of code. 58 | * Includes documentation for new features. 59 | * Includes tests cases that demonstrates the previous flaw that now passes with the included patch, or demonstrates the newly added feature. Tests should have 100% code coverage. 60 | * Is appropriately licensed (Apache 2.0). 61 | 62 | 63 | 64 | 65 | Get Started 66 | ----------- 67 | 68 | 1. Fork the ``awsloft-terraform-ci`` repository on GitHub. 69 | 2. Clone your fork locally:: 70 | 71 | $ git clone git@github.org:your_name_here/awsloft-terraform-ci.git 72 | 73 | 3. Install your local copy into a `virtual environment `_. Assuming you have virtualenv installed, this is how you set up your fork for local development: 74 | 75 | .. code-block:: shell 76 | 77 | $ cd awsloft-terraform-ci/ 78 | $ # Enable your virtual environment 79 | $ virtualenv env 80 | $ source env/bin/activate 81 | $ # Install python requirements 82 | $ pip install -r requirements.txt 83 | 84 | 4. Create a branch for local development: 85 | 86 | .. code-block:: shell 87 | 88 | $ git checkout -b - 89 | 90 | 5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox: 91 | 92 | .. code-block:: shell 93 | 94 | $ make lint 95 | $ make test-all 96 | $ make coverage # coverage should be 100% 97 | 98 | 6. Make sure the changes comply with the pull request guidelines in the section on `Contributing Code`_. 99 | 100 | 7. Commit your changes: 101 | 102 | .. code-block:: shell 103 | 104 | $ git add . 105 | $ git commit 106 | 107 | Commit messages should follow `these guidelines `_. 108 | 109 | Push your branch to GitHub:: 110 | 111 | $ git push origin 112 | 113 | 8. Submit a pull request through the GitHub website. 114 | 115 | 116 | Credits 117 | ------- 118 | 119 | This document took inspiration from the CONTRIBUTING files of the `Atom `_ and `Boto3 `_ projects. 120 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache Software License 2.0 2 | 3 | Copyright 2017 Cloudreach Europe Limited or its affiliates. All Rights Reserved. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ================================================== 2 | DevOps with Terraform 3 | ================================================== 4 | 5 | Overview 6 | -------- 7 | During our session at AWS Loft in Munich on November 22nd we presented “How to deploy AWS Infrastructure in a true DevOps fashion” using Terraform, AWS Codepipeline and AWS Codebuild. 8 | 9 | As best practise Cloudreach suggests to deploy infrastructure as a code using AWS Cloudformation, AWS CLI or our tool `Sceptre `_. For the purpose of this article we will focus on Terraform in order to evaluate a different tool. 10 | 11 | Terraform is a general purpose infrastructure construction tool which supports multiple public cloud providers. Although it’s not an abstraction layer allowing users to create infrastructure on any cloud provider using the same code, it is powerful in that the same syntax can be used to create resources in multiple public cloud providers. 12 | 13 | Terraform uses its own configuration format (TF) to describe desired infrastructure resources. The TF format also provides for variables, conditionals, and iterators while still being readable. JSON is also supported, as an alternative to the TF format. JSON allows for other tools and languages to leverage Terraform’s wide array of features. 14 | 15 | One of Terraform’s most attractive features is how it tracks and maintains its configuration data. Configurations are tracked through the “terraform.tfstate” file, which can be stored locally or in a shared location (e.g. S3 bucket) for collaborative efforts. In the latter case, Terraform provides for execution locks so that the target environment is only manipulated by one Terraform execution at a time, preventing overlapping errors. In those cases where infrastructure resources have been modified outside of Terraform, Terraform provides facilities to safely ingest new/modified resources or delete non-existent resources from the terraform.tfstate file. 16 | 17 | Terraform is an active open source project, where the community keeps the tool up to date with new features quickly. The source code can be found here. 18 | 19 | Architecture 20 | For the scope of this demonstration we will deploy the followings architecture 21 | 22 | 23 | 24 | Diagram 25 | ******* 26 | 27 | .. image:: resources/architecture.jpeg 28 | :align: center 29 | 30 | 31 | 32 | 33 | Pipeline 34 | ******** 35 | 36 | Dedicated pipeline defined in Cloudformation template will provide the automatic deployment of the full environment. 37 | This pipeline allows: 38 | 39 | - Use of AWS technology only (such as CodePipeline, CodeCommit, CodeBuild, SNS) 40 | - No use of Jenkins to orchestrate deployments 41 | - Reduction of costs 42 | - Performing a full deployment on every “git commit” 43 | 44 | 45 | .. image:: resources/pipeline.jpeg 46 | :align: center 47 | 48 | 49 | Technology 50 | ---------- 51 | 52 | CodeBuild 53 | ********** 54 | AWS CodeBuild is a fully managed build service in the cloud. AWS CodeBuild compiles your source code, runs unit tests, and produces artifacts that are ready to deploy. AWS CodeBuild eliminates the need to provision, manage, and scale your own build servers. It provides prepackaged build environments for the most popular programming languages and build tools such as Apache Maven, Gradle, and more. You can also customize build environments in AWS CodeBuild to use your own build tools. AWS CodeBuild scales automatically to meet peak build requests 55 | 56 | 57 | CodePipeline 58 | ************ 59 | AWS CodePipeline is a CI/CD service that allows defining processes for application and infrastructure updates. CodePipeline integrates with code repositories (e.g. AWS CodeCommit, GitHub), build systems (e.g. Jenkins), testing products, deployment tools (e.g. AWS CodeDeploy). It also orchestrates the inputs, actions, and outputs of each step in the process. CodePipeline comes with pre-built plugins and, if needed, custom plugins can be created to integrate with other third-party tools. 60 | 61 | 62 | Getting Started 63 | --------------- 64 | 65 | How to initialize Parameters store 66 | 67 | .. code:: bash 68 | 69 | $ aws ssm put-parameter --name 'dev.db.password' --value 'WordpressSecretPassword-234' --type SecureString --region eu-central-1 70 | $ aws ssm put-parameter --name 'dev.db.username' --value 'wordpress_user' --type String --region eu-central-1 71 | 72 | $ aws ssm put-parameter --name 'prod.db.password' --value 'WordpressSecretPassword-567' --type SecureString --region eu-central-1 73 | $ aws ssm put-parameter --name 'prod.db.username' --value 'wordpress_user_prod' --type String --region eu-central-1 74 | 75 | $ aws ssm put-parameter --name 'common.base_ami' --value 'ami-c7ee5ca8' --type String --region eu-central-1 76 | 77 | 78 | How to bootstrap AWS pipeline using AWS Cloudformation 79 | 80 | .. code:: bash 81 | 82 | $ ./deploy-cfn.sh 83 | Enter the GitHub OAuth Token: 84 | 85 | Please read the official `GitHub documentation `_ to retrieve your OAuth Token and the related `AWS documentation `_ 86 | 87 | 88 | 89 | 90 | How to Contribute 91 | ***************** 92 | 93 | We encourage contribution to our projects, please see our `CONTRIBUTING `_ guide for details. 94 | 95 | 96 | License 97 | ------- 98 | 99 | 100 | **awsloft-terraform-ci** is licensed under the `Apache Software License 2.0 `_. 101 | 102 | Thanks 103 | ------ 104 | 105 | 106 | Keep It Cloudy (`@CloudreachKIC `_) 107 | -------------------------------------------------------------------------------- /buildspec-dev.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | TF_VERSION: "0.10.7" 6 | phases: 7 | 8 | install: 9 | commands: 10 | # install required binary 11 | - "curl -s -qL -o /usr/bin/jq https://stedolan.github.io/jq/download/linux64/jq" 12 | - "chmod +x /usr/bin/jq" 13 | - "cd /usr/bin" 14 | - "curl -s -qL -o terraform.zip https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip" 15 | - "unzip -o terraform.zip" 16 | 17 | pre_build: 18 | commands: 19 | # Workaround until TF supports creds via Task Roles when running on ECS or CodeBuild 20 | # See: https://github.com/hashicorp/terraform/issues/8746 21 | - export AWS_RAW_CRED=$(curl --silent http://169.254.170.2:80$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) 22 | - export AWS_ACCESS_KEY_ID=$(echo $AWS_RAW_CRED | jq -r '.AccessKeyId') 23 | - export AWS_SECRET_ACCESS_KEY=$(echo $AWS_RAW_CRED | jq -r '.SecretAccessKey') 24 | - export AWS_SESSION_TOKEN=$(echo $AWS_RAW_CRED | jq -r '.Token') 25 | 26 | 27 | 28 | build: 29 | commands: 30 | - cd "$CODEBUILD_SRC_DIR" 31 | - cd "environments/$TF_ENV/" 32 | - echo " building $TF_ENV" 33 | - terraform init -no-color 34 | - terraform plan -no-color 35 | - terraform apply -no-color 36 | 37 | post_build: 38 | commands: 39 | - echo "terraform completed on `date`" 40 | 41 | 42 | artifacts: 43 | files: 44 | - '**/*' 45 | -------------------------------------------------------------------------------- /buildspec-prod.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | TF_VERSION: "0.10.7" 6 | 7 | phases: 8 | 9 | install: 10 | commands: 11 | # install required binary 12 | - "curl -s -qL -o /usr/bin/jq https://stedolan.github.io/jq/download/linux64/jq" 13 | - "chmod +x /usr/bin/jq" 14 | - "cd /usr/bin" 15 | - "curl -s -qL -o terraform.zip https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip" 16 | - "unzip -o terraform.zip" 17 | pre_build: 18 | commands: 19 | # Workaround until TF supports creds via Task Roles when running on ECS or CodeBuild 20 | # See: https://github.com/hashicorp/terraform/issues/8746 21 | - export AWS_RAW_CRED=$(curl --silent http://169.254.170.2:80$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) 22 | - export AWS_ACCESS_KEY_ID=$(echo $AWS_RAW_CRED | jq -r '.AccessKeyId') 23 | - export AWS_SECRET_ACCESS_KEY=$(echo $AWS_RAW_CRED | jq -r '.SecretAccessKey') 24 | - export AWS_SESSION_TOKEN=$(echo $AWS_RAW_CRED | jq -r '.Token') 25 | 26 | build: 27 | commands: 28 | - cd "$CODEBUILD_SRC_DIR" 29 | - cd "environments/$TF_ENV/" 30 | - terraform init -no-color 31 | - terraform $TF_ACTION -no-color 32 | 33 | post_build: 34 | commands: 35 | - echo "terraform $TF_ACTION completed on `date`" 36 | 37 | artifacts: 38 | files: 39 | - '**/*' 40 | -------------------------------------------------------------------------------- /cfn.yaml: -------------------------------------------------------------------------------- 1 | 2 | AWSTemplateFormatVersion: "2010-09-09" 3 | Description: Serverless deployment pipeline for Terraform projects 4 | Parameters: 5 | S3ObjectKey: 6 | Type: String 7 | Default: "workspace/data.zip" 8 | Description: The Git branch to be used 9 | 10 | ArtifactBucketName: 11 | Type: String 12 | Default: "aws-loft-de-terraform" 13 | Description: Artifact S3 BucketName 14 | 15 | DockerBuildImage: 16 | Type: String 17 | Default: "aws/codebuild/docker:1.12.1" 18 | Description: Docker Image used by CodeBuild 19 | 20 | GitHubOwner: 21 | Description: 'The owner of the GitHub repository.' 22 | Default: "cloudreach" 23 | Type: String 24 | GitHubOAuthToken: 25 | Description: 'The OAuthToken of the GitHub user.' 26 | Type: String 27 | NoEcho: true 28 | GitHubRepo: 29 | Description: 'The GitHub repository.' 30 | Type: String 31 | Default: "awsloft-terraform-ci" 32 | GitHubRepoBranch: 33 | Description: 'The GitHub repository branch' 34 | Default: "master" 35 | Type: String 36 | 37 | SnsTopicName: 38 | Type: String 39 | Default: "sns-manual-approval" 40 | Description: Sns Topic for Manual approval 41 | 42 | Resources: 43 | ArtifactStoreBucket: 44 | Type: AWS::S3::Bucket 45 | Properties: 46 | BucketName: !Sub ${ArtifactBucketName}-${AWS::Region} 47 | VersioningConfiguration: 48 | Status: Enabled 49 | AccessControl: BucketOwnerFullControl 50 | 51 | PipelineRole: 52 | Type: AWS::IAM::Role 53 | Properties: 54 | AssumeRolePolicyDocument: 55 | Version: "2012-10-17" 56 | Statement: 57 | Effect: Allow 58 | Principal: 59 | Service: codepipeline.amazonaws.com 60 | Action: sts:AssumeRole 61 | Path: / 62 | ManagedPolicyArns: 63 | - arn:aws:iam::aws:policy/AdministratorAccess 64 | 65 | InvokeTerraformBuildRole: 66 | Type: AWS::IAM::Role 67 | Properties: 68 | AssumeRolePolicyDocument: 69 | Version: "2012-10-17" 70 | Statement: 71 | Effect: Allow 72 | Principal: 73 | Service: codebuild.amazonaws.com 74 | Action: sts:AssumeRole 75 | ManagedPolicyArns: 76 | - arn:aws:iam::aws:policy/AdministratorAccess 77 | 78 | SNSManualApproval: 79 | Type: "AWS::SNS::Topic" 80 | Properties: 81 | DisplayName: !Ref SnsTopicName 82 | TopicName: !Ref SnsTopicName 83 | 84 | 85 | Pipeline: 86 | Type: AWS::CodePipeline::Pipeline 87 | Properties: 88 | Name: aws-loft-pipeline 89 | RoleArn: !GetAtt PipelineRole.Arn 90 | ArtifactStore: 91 | Location: !Ref ArtifactStoreBucket 92 | Type: S3 93 | Stages: 94 | 95 | - Name: SourceCode 96 | Actions: 97 | - InputArtifacts: [] 98 | Name: Source 99 | RunOrder: 1 100 | ActionTypeId: 101 | Category: Source 102 | Owner: ThirdParty 103 | Version: 1 104 | Provider: GitHub 105 | OutputArtifacts: 106 | - Name: TFWorkspace 107 | Configuration: 108 | Owner: !Ref GitHubOwner 109 | Repo: !Ref GitHubRepo 110 | Branch: !Ref GitHubRepoBranch 111 | OAuthToken: !Ref GitHubOAuthToken 112 | 113 | - Name: Dev 114 | Actions: 115 | - Name: terraform-apply 116 | RunOrder: 1 117 | ActionTypeId: 118 | Category: Build 119 | Owner: AWS 120 | Version: '1' 121 | Provider: CodeBuild 122 | InputArtifacts: 123 | - Name: TFWorkspace 124 | OutputArtifacts: [] 125 | Configuration: 126 | ProjectName: !Ref InvokeDevTerraformApply 127 | 128 | - Name: Dev-HTTP-Test 129 | Actions: 130 | - Name: http-test 131 | RunOrder: 1 132 | ActionTypeId: 133 | Category: Build 134 | Owner: AWS 135 | Version: '1' 136 | Provider: CodeBuild 137 | InputArtifacts: 138 | - Name: TFWorkspace 139 | OutputArtifacts: [] 140 | Configuration: 141 | ProjectName: !Ref HttpDevTest 142 | 143 | - Name: Prod 144 | Actions: 145 | - Name: terraform-plan 146 | RunOrder: 1 147 | ActionTypeId: 148 | Category: Build 149 | Owner: AWS 150 | Version: '1' 151 | Provider: CodeBuild 152 | InputArtifacts: 153 | - Name: TFWorkspace 154 | OutputArtifacts: 155 | - Name: TFWorkspaceProdPlan 156 | Configuration: 157 | ProjectName: !Ref InvokeProdTerraformPlan 158 | 159 | - Name: Manual-Approval 160 | RunOrder: 2 161 | ActionTypeId: 162 | Category: Approval 163 | Owner: AWS 164 | Version: '1' 165 | Provider: Manual 166 | Configuration: 167 | NotificationArn: !Ref SNSManualApproval 168 | 169 | - Name: terraform-apply 170 | RunOrder: 3 171 | ActionTypeId: 172 | Category: Build 173 | Owner: AWS 174 | Version: '1' 175 | Provider: CodeBuild 176 | InputArtifacts: 177 | - Name: TFWorkspaceProdPlan 178 | OutputArtifacts: [] 179 | Configuration: 180 | ProjectName: !Ref InvokeProdTerraformApply 181 | 182 | 183 | InvokeDevTerraformApply: 184 | Type: AWS::CodeBuild::Project 185 | Properties: 186 | Name: !Sub ${AWS::StackName}-DevApply 187 | ServiceRole: !Ref InvokeTerraformBuildRole 188 | Artifacts: 189 | Type: CODEPIPELINE 190 | Environment: 191 | ComputeType: BUILD_GENERAL1_SMALL 192 | Image: !Ref DockerBuildImage 193 | Type: LINUX_CONTAINER 194 | EnvironmentVariables: 195 | - Name: TF_ENV 196 | Value: dev 197 | Source: 198 | Type: CODEPIPELINE 199 | BuildSpec: buildspec-dev.yml 200 | 201 | 202 | HttpDevTest: 203 | Type: AWS::CodeBuild::Project 204 | Properties: 205 | Name: !Sub ${AWS::StackName}-HttpTest 206 | ServiceRole: !Ref InvokeTerraformBuildRole 207 | Artifacts: 208 | Type: CODEPIPELINE 209 | Environment: 210 | ComputeType: BUILD_GENERAL1_SMALL 211 | Image: !Ref DockerBuildImage 212 | Type: LINUX_CONTAINER 213 | EnvironmentVariables: 214 | - Name: SSM_VAL 215 | Value: dev.www.target 216 | 217 | Source: 218 | Type: CODEPIPELINE 219 | BuildSpec: !Sub | 220 | version: 0.2 221 | phases: 222 | build: 223 | commands: 224 | - cd "$CODEBUILD_SRC_DIR" 225 | - bash ./test/http_test.sh 226 | 227 | 228 | InvokeProdTerraformPlan: 229 | Type: AWS::CodeBuild::Project 230 | Properties: 231 | Name: !Sub ${AWS::StackName}-ProdPlan 232 | ServiceRole: !Ref InvokeTerraformBuildRole 233 | Artifacts: 234 | Type: CODEPIPELINE 235 | Environment: 236 | ComputeType: BUILD_GENERAL1_SMALL 237 | Image: !Ref DockerBuildImage 238 | Type: LINUX_CONTAINER 239 | EnvironmentVariables: 240 | - Name: TF_ACTION 241 | Value: plan 242 | - Name: TF_ENV 243 | Value: prod 244 | Source: 245 | Type: CODEPIPELINE 246 | BuildSpec: buildspec-prod.yml 247 | 248 | 249 | InvokeProdTerraformApply: 250 | Type: AWS::CodeBuild::Project 251 | Properties: 252 | ServiceRole: !Ref InvokeTerraformBuildRole 253 | Artifacts: 254 | Type: CODEPIPELINE 255 | Environment: 256 | ComputeType: BUILD_GENERAL1_SMALL 257 | Image: !Ref DockerBuildImage 258 | Type: LINUX_CONTAINER 259 | EnvironmentVariables: 260 | - Name: TF_ACTION 261 | Value: apply 262 | - Name: TF_ENV 263 | Value: prod 264 | Source: 265 | Type: CODEPIPELINE 266 | BuildSpec: buildspec-prod.yml 267 | -------------------------------------------------------------------------------- /deploy-cfn.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/bash -e 3 | STACKNAME="aws-loft-de" 4 | 5 | echo "Enter the GitHub OAuth Token:" 6 | read GITHUB_OAUTH_TOKEN 7 | 8 | 9 | aws cloudformation describe-stacks --stack-name $STACKNAME --query Stacks[0].StackId > /dev/null 2>&1 10 | if [ $? -eq 0 ]; then 11 | echo "UPDATE $STACKNAME" 12 | aws cloudformation update-stack --stack-name $STACKNAME --template-body file://cfn.yaml --parameters ParameterKey=GitHubOAuthToken,ParameterValue=$GITHUB_OAUTH_TOKEN --capabilities CAPABILITY_IAM 13 | else 14 | echo "CREATE $STACKNAME" 15 | aws cloudformation create-stack --stack-name $STACKNAME --template-body file://cfn.yaml --parameters ParameterKey=GitHubOAuthToken,ParameterValue=$GITHUB_OAUTH_TOKEN --capabilities CAPABILITY_IAM 16 | fi 17 | -------------------------------------------------------------------------------- /environments/dev/config.tf: -------------------------------------------------------------------------------- 1 | 2 | variable "region" { 3 | default = "eu-central-1" 4 | description = "The region to deploy the cluster in, e.g: us-east-1." 5 | } 6 | 7 | 8 | variable "environment" { 9 | default = "dev" 10 | description = "environment name" 11 | } 12 | 13 | 14 | terraform { 15 | required_version = ">= 0.10.5" 16 | backend "s3" { 17 | bucket = "aws-loft-de-terraform" 18 | key = "dev/terraform.tfstate" 19 | region = "eu-central-1" 20 | encrypt = true 21 | 22 | } 23 | } 24 | 25 | data "terraform_remote_state" "default" { 26 | backend = "s3" 27 | config { 28 | bucket = "aws-loft-de-terraform" 29 | key = "dev/terraform.tfstate" 30 | region = "eu-central-1" 31 | encrypt = true 32 | } 33 | } 34 | 35 | 36 | # Define your AWS profile here 37 | provider "aws" { 38 | region = "eu-central-1" 39 | version = "~> 1.3.0" 40 | } 41 | 42 | 43 | 44 | data "aws_ssm_parameter" "db_username" { 45 | name = "${var.environment}.db.username" 46 | } 47 | 48 | data "aws_ssm_parameter" "db_password" { 49 | name = "${var.environment}.db.password" 50 | } 51 | -------------------------------------------------------------------------------- /environments/dev/efs.tf: -------------------------------------------------------------------------------- 1 | 2 | module "wordpress_efs" { 3 | # source = "git@github.com:cloudreach/awsloft-terraform-ci.git//modules/aws/efs" 4 | source = "../../modules/aws/efs" 5 | 6 | name = "wordpress-${var.environment}" 7 | security_groups = ["${module.wordpress_sg.efs_sg}"] 8 | subnets = "${module.vpc.private_subnets}" 9 | 10 | tags = { 11 | Owner = "user" 12 | Environment = "${var.environment}" 13 | Project = "wordpress-cluster" 14 | Scope = "aws-loft-de" 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /environments/dev/rds.tf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | module "wordpress_rds" { 5 | # source = "git@github.com:cloudreach/awsloft-terraform-ci.git//modules/aws/rds" 6 | source = "../../modules/aws/rds" 7 | name = "wordpress${var.environment}" 8 | 9 | security_group = ["${module.wordpress_sg.rds_sg}"] 10 | db_subnet_ids = "${module.vpc.database_subnets}" 11 | allocated_storage = 20 12 | engine = "mysql" 13 | engine_version = "5.7.19" 14 | instance_class = "db.t2.medium" 15 | username = "${data.aws_ssm_parameter.db_username.value}" 16 | password = "${data.aws_ssm_parameter.db_password.value}" 17 | multi_az = false 18 | 19 | tags = { 20 | Owner = "user" 21 | Environment = "${var.environment}" 22 | Scope = "aws-loft-de" 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /environments/dev/security_group.tf: -------------------------------------------------------------------------------- 1 | 2 | module "wordpress_sg" { 3 | # source = "git@github.com:cloudreach/awsloft-terraform-ci.git//modules/aws/network/sg" 4 | source = "../../modules/aws/network/sg" 5 | 6 | name = "wordpress-${var.environment}" 7 | vpc_id = "${module.vpc.vpc_id}" 8 | 9 | web_white_list = ["0.0.0.0/0"] 10 | ssh_white_list = ["0.0.0.0/0"] 11 | 12 | tags = { 13 | Owner = "user" 14 | Environment = "${var.environment}" 15 | Project = "wordpress-cluster" 16 | Scope = "aws-loft-de" 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /environments/dev/vpc.tf: -------------------------------------------------------------------------------- 1 | 2 | module "vpc" { 3 | # source = "git@github.com:cloudreach/awsloft-terraform-ci.git//modules/aws/network/vpc" 4 | source = "../../modules/aws/network/vpc" 5 | 6 | name = "${var.environment}" 7 | 8 | cidr = "10.20.0.0/16" 9 | 10 | azs = ["${var.region}a", "${var.region}b", "${var.region}c"] 11 | private_subnets = ["10.20.1.0/24", "10.20.2.0/24", "10.20.3.0/24"] 12 | public_subnets = ["10.20.11.0/24", "10.20.12.0/24", "10.20.13.0/24"] 13 | database_subnets = ["10.20.21.0/24", "10.20.22.0/24", "10.20.23.0/24"] 14 | 15 | 16 | enable_nat_gateway = false 17 | enable_s3_endpoint = false 18 | enable_dynamodb_endpoint = false 19 | 20 | tags = { 21 | Owner = "user" 22 | Environment = "${var.environment}" 23 | Scope = "aws-loft-de" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /environments/dev/web_app.tf: -------------------------------------------------------------------------------- 1 | 2 | 3 | data "aws_ssm_parameter" "base_ami" { 4 | name = "common.base_ami" 5 | } 6 | 7 | data "aws_iam_instance_profile" "ec2_instance_profile" { 8 | name = "webapp-instance-profile" 9 | } 10 | 11 | module "wordpress-cluster" { 12 | # source = "git@github.com:cloudreach/awsloft-terraform-ci.git//modules/aws/ec2/autoscaling" 13 | source = "../../modules/aws/ec2/autoscaling" 14 | region = "${var.region}" 15 | amisize = "t2.micro" 16 | min_size = "0" 17 | desired_capacity = "1" 18 | max_size = "2" 19 | 20 | ami_id = "${data.aws_ssm_parameter.base_ami.value}" 21 | 22 | environment = "${var.environment}" 23 | vpc_id = "${module.vpc.vpc_id}" 24 | subnets = "${module.vpc.public_subnets}" 25 | ec2_sg = ["${module.wordpress_sg.ec2_sg}"] 26 | elb_sg = ["${module.wordpress_sg.elb_sg}"] 27 | efs_id = "${module.wordpress_efs.efs_id}" 28 | 29 | db_username = "${data.aws_ssm_parameter.db_username.value}" 30 | db_password = "${data.aws_ssm_parameter.db_password.value}" 31 | db_name = "${module.wordpress_rds.rds_instance_id}" 32 | db_host = "${module.wordpress_rds.rds_instance_address}" 33 | 34 | iam_instance_profile = "${data.aws_iam_instance_profile.ec2_instance_profile.name}" 35 | key_name = "wordpress-cluster" 36 | asgname = "wordpress-asg" 37 | 38 | extra_tags = [ 39 | { 40 | key = "Environment" 41 | value = "${var.environment}" 42 | propagate_at_launch = true 43 | }, 44 | { 45 | key = "Scope" 46 | value = "aws-loft-de" 47 | propagate_at_launch = true 48 | } 49 | ] 50 | } 51 | 52 | 53 | output "www-record" { 54 | description = "public DNS" 55 | value = "${module.wordpress-cluster.www-record}" 56 | } 57 | -------------------------------------------------------------------------------- /environments/iam/config.tf: -------------------------------------------------------------------------------- 1 | 2 | variable "region" { 3 | default = "eu-central-1" 4 | description = "The region to deploy the cluster in, e.g: us-east-1." 5 | } 6 | 7 | 8 | 9 | terraform { 10 | required_version = ">= 0.10.5" 11 | backend "s3" { 12 | bucket = "aws-loft-de-terraform" 13 | key = "iam/terraform.tfstate" 14 | region = "eu-central-1" 15 | encrypt = true 16 | 17 | } 18 | } 19 | 20 | data "terraform_remote_state" "default" { 21 | backend = "s3" 22 | config { 23 | bucket = "aws-loft-de-terraform" 24 | key = "iam/terraform.tfstate" 25 | region = "eu-central-1" 26 | encrypt = true 27 | } 28 | } 29 | 30 | 31 | # Define your AWS profile here 32 | provider "aws" { 33 | region = "eu-central-1" 34 | version = "~> 0.1.4" 35 | } 36 | -------------------------------------------------------------------------------- /environments/iam/iam_role.tf: -------------------------------------------------------------------------------- 1 | // The policy allows an instance to forward logs to CloudWatch, and 2 | // create the Log Stream or Log Group if it doesn't exist. 3 | resource "aws_iam_policy" "forward-logs" { 4 | name = "webapp-forward-logs" 5 | path = "/" 6 | description = "Allows an instance to forward logs to CloudWatch" 7 | 8 | policy = < /var/log/user-data.log 2>&1 8 | 9 | 10 | # A few variables we will refer to later... 11 | REGION="${region}" 12 | 13 | 14 | # Update the packages, install CloudWatch tools. 15 | apt-get update -y 16 | apt-get install -y awslogs awscli 17 | apt-get install python-pip nfs-common -y 18 | 19 | mkdir -p /var/www/html/ 20 | EC2_AZ=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone) 21 | echo "$EC2_AZ.${efs_id}.efs.${region}.amazonaws.com:/ /var/www/html/ nfs4 nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 0 0" >> /etc/fstab 22 | 23 | mount -a 24 | 25 | 26 | # Create a config file for awslogs to push logs to the same region of the cluster. 27 | cat <<- EOF | sudo tee /etc/awslogs/awscli.conf 28 | [plugins] 29 | cwlogs = cwlogs 30 | [default] 31 | region = ${region} 32 | EOF 33 | 34 | # Create a config file for awslogs to log our user-data log. 35 | cat <<- EOF | sudo tee /etc/awslogs/config/user-data.conf 36 | [/var/log/user-data.log] 37 | file = /var/log/user-data.log 38 | log_group_name = /var/log/user-data.log 39 | log_stream_name = {instance_id} 40 | EOF 41 | 42 | # Start the awslogs service, also start on reboot. 43 | # Note: Errors go to /var/log/awslogs.log 44 | service awslogs start 45 | chkconfig awslogs on 46 | 47 | # Install packages 48 | apt-get install -y apache2 php php-mysql php7.0 php7.0-mysql libapache2-mod-php7.0 php7.0-cli php7.0-cgi php7.0-gd mysql-client sendmail 49 | 50 | service apache2 restart 51 | 52 | wget -q https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar -O /usr/local/bin/wp 53 | chmod +x /usr/local/bin/wp 54 | 55 | 56 | cd /var/www/html/ 57 | if ! $(sudo -u www-data /usr/local/bin/wp core is-installed); then 58 | 59 | echo "Download Wordpress" 60 | wget -q https://wordpress.org/wordpress-4.8.2.tar.gz -O wordpress.tar.gz 61 | tar -xzf wordpress.tar.gz 62 | cp ./wordpress/wp-config-sample.php ./wordpress/wp-config.php 63 | 64 | echo "Config Wordpress" 65 | sed -i "s/'database_name_here'/'${db_name}'/g" ./wordpress/wp-config.php 66 | sed -i "s/'username_here'/'${db_username}'/g" ./wordpress/wp-config.php 67 | sed -i "s/'password_here'/'${db_password}'/g" ./wordpress/wp-config.php 68 | sed -i "s/'localhost'/'${db_host}'/g" ./wordpress/wp-config.php 69 | 70 | 71 | 72 | mv -f /var/www/html/wordpress/* /var/www/html/ 73 | rm -f /var/www/html/index.html 74 | rm -rf /var/www/html/wordpress/ 75 | chown www-data:www-data /var/www/html/* -R 76 | 77 | sudo -u www-data /usr/local/bin/wp core install \ 78 | --url='www-${environment}.${route53_domain}' --title='Cloudreach AWS Loft - ${environment}' \ 79 | --admin_user='root' --admin_password='wordpress234' \ 80 | --admin_email='aws-loft@cloudreach.com' 81 | 82 | wget -q https://raw.githubusercontent.com/cloudreach/awsloft-terraform-ci/master/resources/AWS_Pop_Up_Loft_Munich.jpg -O /var/www/html/wp-content/themes/twentyseventeen/assets/images/header.jpg 83 | 84 | chown www-data:www-data /var/www/html/wp-content/themes/twentyseventeen/assets/images/header.jpg 85 | 86 | fi 87 | -------------------------------------------------------------------------------- /modules/aws/ec2/autoscaling/main.tf: -------------------------------------------------------------------------------- 1 | 2 | // AMIs by region for AWS Optimised Linux 3 | # data "aws_ami" "image" { 4 | # most_recent = true 5 | # 6 | # owners = ["137112412989"] 7 | # 8 | # filter { 9 | # name = "architecture" 10 | # values = ["x86_64"] 11 | # } 12 | # 13 | # filter { 14 | # name = "root-device-type" 15 | # values = ["ebs"] 16 | # } 17 | # 18 | # filter { 19 | # name = "virtualization-type" 20 | # values = ["hvm"] 21 | # } 22 | # 23 | # filter { 24 | # name = "name" 25 | # values = ["amzn-ami-hvm-*"] 26 | # } 27 | # } 28 | 29 | # data "aws_ami" "image" { 30 | # most_recent = true 31 | # owners = ["099720109477"] 32 | # 33 | # filter { 34 | # name = "architecture" 35 | # values = ["x86_64"] 36 | # } 37 | # 38 | # filter { 39 | # name = "name" 40 | # values = ["ubuntu/images/hvm-ssd/ubuntu-xenial-16.04-amd64-server-*"] 41 | # } 42 | # } 43 | 44 | data "template_file" "wordpress" { 45 | template = "${file("${path.module}/files/wordpress.sh")}" 46 | 47 | vars { 48 | region = "${var.region}" 49 | efs_id = "${var.efs_id}" 50 | environment = "${var.environment}" 51 | db_username = "${var.db_username}" 52 | db_password = "${var.db_password}" 53 | db_name = "${var.db_name}" 54 | db_host = "${var.db_host}" 55 | environment = "${var.environment}" 56 | route53_domain = "${var.route53_domain}" 57 | 58 | } 59 | } 60 | 61 | // Launch configuration for the cluster auto-scaling group. 62 | resource "aws_launch_configuration" "wordpress-cluster-lc" { 63 | name_prefix = "${var.environment}-wordpress-node-" 64 | image_id = "${var.ami_id}" 65 | instance_type = "${var.amisize}" 66 | user_data = "${data.template_file.wordpress.rendered}" 67 | associate_public_ip_address = true 68 | iam_instance_profile = "${var.iam_instance_profile}" 69 | key_name = "${var.key_name}" 70 | security_groups = ["${var.ec2_sg}"] 71 | 72 | lifecycle { 73 | create_before_destroy = true 74 | } 75 | } 76 | 77 | 78 | 79 | # Application Load Balancer 80 | resource "aws_alb" "alb" { 81 | name = "${var.environment}-wordpress-alb" 82 | internal = false 83 | security_groups = ["${var.elb_sg}"] 84 | subnets = ["${var.subnets}"] 85 | tags { 86 | Name = "${var.environment}-wordpress-alb" 87 | Environment = "${var.environment}" 88 | } 89 | } 90 | 91 | resource "aws_alb_target_group" "web" { 92 | name = "${var.environment}-wordpress-alb-tg" 93 | port = 80 94 | protocol = "HTTP" 95 | vpc_id = "${var.vpc_id}" 96 | 97 | health_check { 98 | healthy_threshold = 2 99 | unhealthy_threshold = 2 100 | timeout = 3 101 | path = "/" 102 | matcher = "200-299" 103 | } 104 | tags { 105 | Name = "${var.environment}-wordpress-alb-tg" 106 | Environment = "${var.environment}" 107 | } 108 | } 109 | 110 | resource "aws_alb_listener" "alb-http" { 111 | load_balancer_arn = "${aws_alb.alb.arn}" 112 | port = "80" 113 | protocol = "HTTP" 114 | 115 | default_action { 116 | target_group_arn = "${aws_alb_target_group.web.arn}" 117 | type = "forward" 118 | } 119 | } 120 | 121 | 122 | data "aws_route53_zone" "selected" { 123 | name = "${var.route53_domain}." 124 | } 125 | 126 | resource "aws_route53_record" "www" { 127 | zone_id = "${data.aws_route53_zone.selected.zone_id}" 128 | name = "www-${var.environment}.${var.route53_domain}" 129 | type = "CNAME" 130 | ttl = "60" 131 | records = ["${aws_alb.alb.dns_name}"] 132 | } 133 | 134 | 135 | resource "aws_ssm_parameter" "target_endpoint" { 136 | name = "${var.environment}.www.target" 137 | type = "String" 138 | # value = "${aws_alb.alb.dns_name}" 139 | value = "www-${var.environment}.${var.route53_domain}" 140 | overwrite = true 141 | } 142 | 143 | // Auto-scaling group for our cluster. 144 | resource "aws_autoscaling_group" "wordpress-cluster-asg" { 145 | depends_on = ["aws_launch_configuration.wordpress-cluster-lc"] 146 | name = "${var.environment}-${var.asgname}" 147 | launch_configuration = "${aws_launch_configuration.wordpress-cluster-lc.name}" 148 | min_size = "${var.min_size}" 149 | desired_capacity = "${var.desired_capacity}" 150 | max_size = "${var.max_size}" 151 | vpc_zone_identifier = ["${var.subnets}"] 152 | target_group_arns = ["${aws_alb_target_group.web.arn}"] 153 | 154 | health_check_grace_period = 300 155 | health_check_type = "EC2" 156 | force_delete = true 157 | 158 | lifecycle { 159 | create_before_destroy = true 160 | } 161 | 162 | tags = ["${concat( 163 | list( 164 | map("key", "Name", "value", "${var.environment}-${var.asgname}", "propagate_at_launch", true) 165 | ), 166 | var.extra_tags) 167 | }"] 168 | 169 | } 170 | -------------------------------------------------------------------------------- /modules/aws/ec2/autoscaling/outputs.tf: -------------------------------------------------------------------------------- 1 | output "www-record" { 2 | description = "public DNS" 3 | value = "${aws_route53_record.www.fqdn}" 4 | } 5 | -------------------------------------------------------------------------------- /modules/aws/ec2/autoscaling/variables.tf: -------------------------------------------------------------------------------- 1 | variable "region" { 2 | description = "The region to deploy the cluster in, e.g: us-east-1." 3 | } 4 | 5 | variable "amisize" { 6 | description = "The size of the cluster nodes, e.g: t2.micro" 7 | } 8 | 9 | variable "min_size" { 10 | description = "The minimum size of the cluter, e.g. 5" 11 | } 12 | 13 | variable "desired_capacity" { 14 | description = "The desired_capacity of the cluter, e.g. 5" 15 | } 16 | 17 | variable "max_size" { 18 | description = "The maximum size of the cluter, e.g. 5" 19 | } 20 | 21 | variable "environment" { 22 | description = "environment Name" 23 | } 24 | 25 | variable "ami_id" { 26 | description = "AMI Id" 27 | } 28 | 29 | variable "vpc_id" { 30 | description = "VPC Id" 31 | } 32 | 33 | variable "route53_domain" { 34 | description = "route53_domain" 35 | default = "aws-loft.cloudreach.com" 36 | } 37 | 38 | 39 | variable "key_name" { 40 | description = "The name of the key to user for ssh access, e.g: wordpress-cluster" 41 | } 42 | 43 | 44 | variable "asgname" { 45 | description = "The auto-scaling group name, e.g: wordpress-asg" 46 | } 47 | 48 | variable "iam_instance_profile" { 49 | description = "IAM profile" 50 | } 51 | 52 | variable "efs_id" { 53 | description = "EFS Id" 54 | } 55 | 56 | variable "subnets" { 57 | description = "A list of subnets inside the VPC" 58 | default = [] 59 | } 60 | 61 | variable "ec2_sg" { 62 | description = "A list of security_groups" 63 | default = [] 64 | } 65 | 66 | variable "elb_sg" { 67 | description = "A list of security_groups" 68 | default = [] 69 | } 70 | 71 | variable "extra_tags" { 72 | type = "list" 73 | } 74 | 75 | variable "db_username" { 76 | description = "RDS username" 77 | } 78 | variable "db_password" { 79 | description = "RDS password" 80 | } 81 | variable "db_name" { 82 | description = "RDS name" 83 | } 84 | variable "db_host" { 85 | description = "RDS address" 86 | } 87 | -------------------------------------------------------------------------------- /modules/aws/efs/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_efs_file_system" "efs" { 2 | creation_token = "${var.name}" 3 | performance_mode = "${var.performance_mode}" 4 | tags = "${merge(var.tags, map("Name", format("efs_%s", var.name)))}" 5 | } 6 | 7 | resource "aws_efs_mount_target" "efs" { 8 | # count = "${length(var.subnets)}" 9 | # Workaround mistake inside efs module https://github.com/terraform-providers/terraform-provider-aws/issues/1938 10 | count = "3" 11 | 12 | file_system_id = "${aws_efs_file_system.efs.id}" 13 | subnet_id = "${element(var.subnets, count.index)}" 14 | security_groups = ["${var.security_groups}"] 15 | } 16 | -------------------------------------------------------------------------------- /modules/aws/efs/output.tf: -------------------------------------------------------------------------------- 1 | 2 | output "efs_id" { 3 | value = "${aws_efs_file_system.efs.id}" 4 | } 5 | -------------------------------------------------------------------------------- /modules/aws/efs/variables.tf: -------------------------------------------------------------------------------- 1 | 2 | variable "name" { 3 | type = "string" 4 | description = "(Required) The reference_name of your file system. Also, used in tags." 5 | } 6 | 7 | variable "subnets" { 8 | type = "list" 9 | description = "(Required) A comma separated list of subnet ids where mount targets will be." 10 | } 11 | 12 | variable "security_groups" { 13 | type = "list" 14 | } 15 | 16 | variable "tags" { 17 | description = "A map of tags to add to all resources" 18 | default = {} 19 | } 20 | 21 | variable "performance_mode" { 22 | type = "string" 23 | description = "(Optional) The file system performance mode. Can be either generalPurpose/maxIO " 24 | default = "generalPurpose" 25 | } 26 | -------------------------------------------------------------------------------- /modules/aws/network/sg/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_security_group" "elb_sg" { 2 | name = "elb_${var.name}" 3 | description = "elb_${var.name}" 4 | vpc_id = "${var.vpc_id}" 5 | 6 | ingress { 7 | from_port = 80 8 | to_port = 80 9 | protocol = "tcp" 10 | cidr_blocks = ["${var.web_white_list}"] 11 | } 12 | 13 | ingress { 14 | from_port = 443 15 | to_port = 443 16 | protocol = "tcp" 17 | cidr_blocks = ["${var.web_white_list}"] 18 | } 19 | 20 | egress { 21 | from_port = 0 22 | to_port = 0 23 | protocol = "-1" 24 | cidr_blocks = ["0.0.0.0/0"] 25 | } 26 | 27 | tags = "${merge(var.tags, map("Name", format("elb_%s", var.name)))}" 28 | 29 | } 30 | 31 | 32 | resource "aws_security_group" "ec2_sg" { 33 | name = "ec2_${var.name}" 34 | description = "ec2_${var.name}" 35 | vpc_id = "${var.vpc_id}" 36 | 37 | ingress { 38 | from_port = 80 39 | to_port = 80 40 | protocol = "tcp" 41 | security_groups = ["${aws_security_group.elb_sg.id}"] 42 | } 43 | 44 | ingress { 45 | from_port = 22 46 | to_port = 22 47 | protocol = "tcp" 48 | cidr_blocks = ["${var.ssh_white_list}"] 49 | } 50 | 51 | egress { 52 | from_port = 0 53 | to_port = 0 54 | protocol = "-1" 55 | cidr_blocks = ["0.0.0.0/0"] 56 | } 57 | 58 | tags = "${merge(var.tags, map("Name", format("ec2_%s", var.name)))}" 59 | } 60 | 61 | 62 | 63 | resource "aws_security_group" "rds_sg" { 64 | name = "rds_${var.name}" 65 | description = "rds_${var.name}" 66 | vpc_id = "${var.vpc_id}" 67 | 68 | ingress { 69 | from_port = 3306 70 | to_port = 3306 71 | protocol = "tcp" 72 | security_groups = ["${aws_security_group.ec2_sg.id}"] 73 | } 74 | 75 | egress { 76 | from_port = 0 77 | to_port = 0 78 | protocol = "-1" 79 | cidr_blocks = ["0.0.0.0/0"] 80 | } 81 | 82 | tags = "${merge(var.tags, map("Name", format("rds_%s", var.name)))}" 83 | } 84 | 85 | resource "aws_security_group" "efs_sg" { 86 | name = "efs_${var.name}" 87 | description = "efs_${var.name}" 88 | vpc_id = "${var.vpc_id}" 89 | 90 | ingress { 91 | from_port = 2049 92 | to_port = 2049 93 | protocol = "tcp" 94 | security_groups = ["${aws_security_group.ec2_sg.id}"] 95 | } 96 | 97 | egress { 98 | from_port = 0 99 | to_port = 0 100 | protocol = "-1" 101 | cidr_blocks = ["0.0.0.0/0"] 102 | } 103 | 104 | tags = "${merge(var.tags, map("Name", format("efs_%s", var.name)))}" 105 | } 106 | -------------------------------------------------------------------------------- /modules/aws/network/sg/output.tf: -------------------------------------------------------------------------------- 1 | 2 | output "elb_sg" { 3 | description = "ELB SG ID" 4 | value = "${aws_security_group.elb_sg.id}" 5 | } 6 | 7 | output "ec2_sg" { 8 | description = "EC2 SG ID" 9 | value = "${aws_security_group.ec2_sg.id}" 10 | } 11 | 12 | output "rds_sg" { 13 | description = "RDS SG ID" 14 | value = "${aws_security_group.rds_sg.id}" 15 | } 16 | 17 | output "efs_sg" { 18 | description = "EFS SG ID" 19 | value = "${aws_security_group.efs_sg.id}" 20 | } 21 | -------------------------------------------------------------------------------- /modules/aws/network/sg/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | description = "Name to be used on all the resources as identifier" 3 | default = "" 4 | } 5 | 6 | variable "tags" { 7 | description = "A map of tags to add to all resources" 8 | default = {} 9 | } 10 | 11 | variable "ssh_white_list" { 12 | type = "list" 13 | default = ["0.0.0.0/0"] 14 | } 15 | 16 | variable "web_white_list" { 17 | type = "list" 18 | default = ["0.0.0.0/0"] 19 | } 20 | 21 | variable "vpc_id" { 22 | type = "string" 23 | } 24 | -------------------------------------------------------------------------------- /modules/aws/network/vpc/output.tf: -------------------------------------------------------------------------------- 1 | # VPC 2 | output "vpc_id" { 3 | description = "The ID of the VPC" 4 | value = "${aws_vpc.this.id}" 5 | } 6 | 7 | output "vpc_cidr_block" { 8 | description = "The CIDR block of the VPC" 9 | value = "${aws_vpc.this.cidr_block}" 10 | } 11 | 12 | output "default_security_group_id" { 13 | description = "The ID of the security group created by default on VPC creation" 14 | value = "${aws_vpc.this.default_security_group_id}" 15 | } 16 | 17 | output "default_network_acl_id" { 18 | description = "The ID of the default network ACL" 19 | value = "${aws_vpc.this.default_network_acl_id}" 20 | } 21 | 22 | # Subnets 23 | output "private_subnets" { 24 | description = "List of IDs of private subnets" 25 | value = ["${aws_subnet.private.*.id}"] 26 | } 27 | 28 | output "public_subnets" { 29 | description = "List of IDs of public subnets" 30 | value = ["${aws_subnet.public.*.id}"] 31 | } 32 | 33 | output "database_subnets" { 34 | description = "List of IDs of database subnets" 35 | value = ["${aws_subnet.database.*.id}"] 36 | } 37 | 38 | output "database_subnet_group" { 39 | description = "ID of database subnet group" 40 | value = "${aws_db_subnet_group.database.id}" 41 | } 42 | 43 | # Route tables 44 | output "public_route_table_ids" { 45 | description = "List of IDs of public route tables" 46 | value = ["${aws_route_table.public.*.id}"] 47 | } 48 | 49 | output "private_route_table_ids" { 50 | description = "List of IDs of private route tables" 51 | value = ["${aws_route_table.private.*.id}"] 52 | } 53 | 54 | output "nat_ids" { 55 | description = "List of allocation ID of Elastic IPs created for AWS NAT Gateway" 56 | value = ["${aws_eip.nat.*.id}"] 57 | } 58 | 59 | output "nat_public_ips" { 60 | description = "List of public Elastic IPs created for AWS NAT Gateway" 61 | value = ["${aws_eip.nat.*.public_ip}"] 62 | } 63 | 64 | output "natgw_ids" { 65 | description = "List of NAT Gateway IDs" 66 | value = ["${aws_nat_gateway.this.*.id}"] 67 | } 68 | 69 | # Internet Gateway 70 | output "igw_id" { 71 | description = "The ID of the Internet Gateway" 72 | value = "${aws_internet_gateway.this.id}" 73 | } 74 | 75 | # VPC Endpoints 76 | output "vpc_endpoint_s3_id" { 77 | description = "The ID of VPC endpoint for S3" 78 | value = "${aws_vpc_endpoint.s3.id}" 79 | } 80 | 81 | output "vpc_endpoint_dynamodb_id" { 82 | description = "The ID of VPC endpoint for DynamoDB" 83 | value = "${aws_vpc_endpoint.dynamodb.id}" 84 | } 85 | -------------------------------------------------------------------------------- /modules/aws/network/vpc/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | description = "Name to be used on all the resources as identifier" 3 | default = "" 4 | } 5 | 6 | variable "cidr" { 7 | description = "The CIDR block for the VPC" 8 | default = "" 9 | } 10 | 11 | variable "instance_tenancy" { 12 | description = "A tenancy option for instances launched into the VPC" 13 | default = "default" 14 | } 15 | 16 | variable "public_subnets" { 17 | description = "A list of public subnets inside the VPC" 18 | default = [] 19 | } 20 | 21 | variable "private_subnets" { 22 | description = "A list of private subnets inside the VPC" 23 | default = [] 24 | } 25 | 26 | variable "database_subnets" { 27 | type = "list" 28 | description = "A list of database subnets" 29 | default = [] 30 | } 31 | 32 | variable "elasticache_subnets" { 33 | type = "list" 34 | description = "A list of elasticache subnets" 35 | default = [] 36 | } 37 | 38 | variable "create_database_subnet_group" { 39 | description = "Controls if database subnet group should be created" 40 | default = false 41 | } 42 | 43 | variable "azs" { 44 | description = "A list of availability zones in the region" 45 | default = [] 46 | } 47 | 48 | variable "enable_dns_hostnames" { 49 | description = "Should be true if you want to use private DNS within the VPC" 50 | default = true 51 | } 52 | 53 | variable "enable_dns_support" { 54 | description = "Should be true if you want to use private DNS within the VPC" 55 | default = true 56 | } 57 | 58 | variable "enable_nat_gateway" { 59 | description = "Should be true if you want to provision NAT Gateways for each of your private networks" 60 | default = false 61 | } 62 | 63 | variable "single_nat_gateway" { 64 | description = "Should be true if you want to provision a single shared NAT Gateway across all of your private networks" 65 | default = false 66 | } 67 | 68 | variable "enable_dynamodb_endpoint" { 69 | description = "Should be true if you want to provision a DynamoDB endpoint to the VPC" 70 | default = false 71 | } 72 | 73 | variable "enable_s3_endpoint" { 74 | description = "Should be true if you want to provision an S3 endpoint to the VPC" 75 | default = false 76 | } 77 | 78 | variable "map_public_ip_on_launch" { 79 | description = "Should be false if you do not want to auto-assign public IP on launch" 80 | default = true 81 | } 82 | 83 | variable "private_propagating_vgws" { 84 | description = "A list of VGWs the private route table should propagate" 85 | default = [] 86 | } 87 | 88 | variable "public_propagating_vgws" { 89 | description = "A list of VGWs the public route table should propagate" 90 | default = [] 91 | } 92 | 93 | variable "tags" { 94 | description = "A map of tags to add to all resources" 95 | default = {} 96 | } 97 | 98 | variable "public_subnet_tags" { 99 | description = "Additional tags for the public subnets" 100 | default = {} 101 | } 102 | 103 | variable "private_subnet_tags" { 104 | description = "Additional tags for the private subnets" 105 | default = {} 106 | } 107 | 108 | variable "public_route_table_tags" { 109 | description = "Additional tags for the public route tables" 110 | default = {} 111 | } 112 | 113 | variable "private_route_table_tags" { 114 | description = "Additional tags for the private route tables" 115 | default = {} 116 | } 117 | 118 | variable "database_subnet_tags" { 119 | description = "Additional tags for the database subnets" 120 | default = {} 121 | } 122 | 123 | variable "elasticache_subnet_tags" { 124 | description = "Additional tags for the elasticache subnets" 125 | default = {} 126 | } 127 | -------------------------------------------------------------------------------- /modules/aws/network/vpc/vpc.tf: -------------------------------------------------------------------------------- 1 | # ===================================================== 2 | # Create a generic VPC 3 | # ===================================================== 4 | ###### 5 | # VPC 6 | ###### 7 | resource "aws_vpc" "this" { 8 | cidr_block = "${var.cidr}" 9 | instance_tenancy = "${var.instance_tenancy}" 10 | enable_dns_hostnames = "${var.enable_dns_hostnames}" 11 | enable_dns_support = "${var.enable_dns_support}" 12 | 13 | tags = "${merge(var.tags, map("Name", format("vpc-%s", var.name)))}" 14 | } 15 | 16 | ################### 17 | # Internet Gateway 18 | ################### 19 | resource "aws_internet_gateway" "this" { 20 | count = "${length(var.public_subnets) > 0 ? 1 : 0}" 21 | 22 | vpc_id = "${aws_vpc.this.id}" 23 | 24 | tags = "${merge(var.tags, map("Name", format("igw-%s", var.name)))}" 25 | } 26 | 27 | ################ 28 | # Publiс routes 29 | ################ 30 | resource "aws_route_table" "public" { 31 | count = "${length(var.public_subnets) > 0 ? 1 : 0}" 32 | 33 | vpc_id = "${aws_vpc.this.id}" 34 | propagating_vgws = ["${var.public_propagating_vgws}"] 35 | 36 | tags = "${merge(var.tags, var.public_route_table_tags, map("Name", format("%s-public", var.name)))}" 37 | } 38 | 39 | resource "aws_route" "public_internet_gateway" { 40 | count = "${length(var.public_subnets) > 0 ? 1 : 0}" 41 | 42 | route_table_id = "${aws_route_table.public.id}" 43 | destination_cidr_block = "0.0.0.0/0" 44 | gateway_id = "${aws_internet_gateway.this.id}" 45 | } 46 | 47 | ################# 48 | # Private routes 49 | ################# 50 | resource "aws_route_table" "private" { 51 | count = "${length(var.azs)}" 52 | 53 | vpc_id = "${aws_vpc.this.id}" 54 | propagating_vgws = ["${var.private_propagating_vgws}"] 55 | 56 | tags = "${merge(var.tags, var.private_route_table_tags, map("Name", format("%s-private-%s", var.name, element(var.azs, count.index))))}" 57 | } 58 | 59 | ################ 60 | # Public subnet 61 | ################ 62 | resource "aws_subnet" "public" { 63 | count = "${length(var.public_subnets)}" 64 | 65 | vpc_id = "${aws_vpc.this.id}" 66 | cidr_block = "${var.public_subnets[count.index]}" 67 | availability_zone = "${element(var.azs, count.index)}" 68 | map_public_ip_on_launch = "${var.map_public_ip_on_launch}" 69 | 70 | tags = "${merge(var.tags, var.public_subnet_tags, map("Name", format("%s-public-%s", var.name, element(var.azs, count.index))))}" 71 | } 72 | 73 | ################# 74 | # Private subnet 75 | ################# 76 | resource "aws_subnet" "private" { 77 | count = "${length(var.private_subnets)}" 78 | 79 | vpc_id = "${aws_vpc.this.id}" 80 | cidr_block = "${var.private_subnets[count.index]}" 81 | availability_zone = "${element(var.azs, count.index)}" 82 | 83 | tags = "${merge(var.tags, var.private_subnet_tags, map("Name", format("%s-private-%s", var.name, element(var.azs, count.index))))}" 84 | } 85 | 86 | ################## 87 | # Database subnet 88 | ################## 89 | resource "aws_subnet" "database" { 90 | count = "${length(var.database_subnets)}" 91 | 92 | vpc_id = "${aws_vpc.this.id}" 93 | cidr_block = "${var.database_subnets[count.index]}" 94 | availability_zone = "${element(var.azs, count.index)}" 95 | 96 | tags = "${merge(var.tags, var.database_subnet_tags, map("Name", format("%s-db-%s", var.name, element(var.azs, count.index))))}" 97 | } 98 | 99 | resource "aws_db_subnet_group" "database" { 100 | count = "${length(var.database_subnets) > 0 && var.create_database_subnet_group ? 1 : 0}" 101 | 102 | name = "${var.name}" 103 | description = "Database subnet group for ${var.name}" 104 | subnet_ids = ["${aws_subnet.database.*.id}"] 105 | 106 | tags = "${merge(var.tags, map("Name", format("%s", var.name)))}" 107 | } 108 | 109 | 110 | ############## 111 | # NAT Gateway 112 | ############## 113 | resource "aws_eip" "nat" { 114 | count = "${var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(var.azs)) : 0}" 115 | 116 | vpc = true 117 | } 118 | 119 | resource "aws_nat_gateway" "this" { 120 | count = "${var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(var.azs)) : 0}" 121 | 122 | allocation_id = "${element(aws_eip.nat.*.id, (var.single_nat_gateway ? 0 : count.index))}" 123 | subnet_id = "${element(aws_subnet.public.*.id, (var.single_nat_gateway ? 0 : count.index))}" 124 | 125 | depends_on = ["aws_internet_gateway.this"] 126 | } 127 | 128 | resource "aws_route" "private_nat_gateway" { 129 | count = "${var.enable_nat_gateway ? length(var.azs) : 0}" 130 | 131 | route_table_id = "${element(aws_route_table.private.*.id, count.index)}" 132 | destination_cidr_block = "0.0.0.0/0" 133 | nat_gateway_id = "${element(aws_nat_gateway.this.*.id, count.index)}" 134 | } 135 | 136 | ###################### 137 | # VPC Endpoint for S3 138 | ###################### 139 | data "aws_vpc_endpoint_service" "s3" { 140 | service = "s3" 141 | } 142 | 143 | resource "aws_vpc_endpoint" "s3" { 144 | count = "${var.enable_s3_endpoint}" 145 | 146 | vpc_id = "${aws_vpc.this.id}" 147 | service_name = "${data.aws_vpc_endpoint_service.s3.service_name}" 148 | } 149 | 150 | resource "aws_vpc_endpoint_route_table_association" "private_s3" { 151 | count = "${var.enable_s3_endpoint ? length(var.private_subnets) : 0}" 152 | 153 | vpc_endpoint_id = "${aws_vpc_endpoint.s3.id}" 154 | route_table_id = "${element(aws_route_table.private.*.id, count.index)}" 155 | } 156 | 157 | resource "aws_vpc_endpoint_route_table_association" "public_s3" { 158 | count = "${var.enable_s3_endpoint ? length(var.public_subnets) : 0}" 159 | 160 | vpc_endpoint_id = "${aws_vpc_endpoint.s3.id}" 161 | route_table_id = "${aws_route_table.public.id}" 162 | } 163 | 164 | ############################ 165 | # VPC Endpoint for DynamoDB 166 | ############################ 167 | data "aws_vpc_endpoint_service" "dynamodb" { 168 | count = "${var.enable_dynamodb_endpoint}" 169 | 170 | service = "dynamodb" 171 | } 172 | 173 | resource "aws_vpc_endpoint" "dynamodb" { 174 | count = "${var.enable_dynamodb_endpoint}" 175 | 176 | vpc_id = "${aws_vpc.this.id}" 177 | service_name = "${data.aws_vpc_endpoint_service.dynamodb.service_name}" 178 | } 179 | 180 | resource "aws_vpc_endpoint_route_table_association" "private_dynamodb" { 181 | count = "${var.enable_dynamodb_endpoint ? length(var.private_subnets) : 0}" 182 | 183 | vpc_endpoint_id = "${aws_vpc_endpoint.dynamodb.id}" 184 | route_table_id = "${element(aws_route_table.private.*.id, count.index)}" 185 | } 186 | 187 | resource "aws_vpc_endpoint_route_table_association" "public_dynamodb" { 188 | count = "${var.enable_dynamodb_endpoint ? length(var.public_subnets) : 0}" 189 | 190 | vpc_endpoint_id = "${aws_vpc_endpoint.dynamodb.id}" 191 | route_table_id = "${aws_route_table.public.id}" 192 | } 193 | 194 | ########################## 195 | # Route table association 196 | ########################## 197 | resource "aws_route_table_association" "private" { 198 | count = "${length(var.private_subnets)}" 199 | 200 | subnet_id = "${element(aws_subnet.private.*.id, count.index)}" 201 | route_table_id = "${element(aws_route_table.private.*.id, count.index)}" 202 | } 203 | 204 | resource "aws_route_table_association" "database" { 205 | count = "${length(var.database_subnets)}" 206 | 207 | subnet_id = "${element(aws_subnet.database.*.id, count.index)}" 208 | route_table_id = "${element(aws_route_table.private.*.id, count.index)}" 209 | } 210 | 211 | resource "aws_route_table_association" "public" { 212 | count = "${length(var.public_subnets)}" 213 | 214 | subnet_id = "${element(aws_subnet.public.*.id, count.index)}" 215 | route_table_id = "${aws_route_table.public.id}" 216 | } 217 | -------------------------------------------------------------------------------- /modules/aws/rds/main.tf: -------------------------------------------------------------------------------- 1 | resource "aws_db_subnet_group" "default" { 2 | name = "${var.name}" 3 | description = "${var.name}_subnet_group" 4 | subnet_ids = ["${var.db_subnet_ids}"] 5 | } 6 | 7 | resource "aws_db_instance" "default" { 8 | name = "${var.name}" 9 | identifier = "${var.name}" 10 | allocated_storage = "${var.allocated_storage}" 11 | storage_type = "gp2" 12 | engine = "${var.engine}" 13 | engine_version = "${var.engine_version}" 14 | instance_class = "${var.instance_class}" 15 | username = "${var.username}" 16 | password = "${var.password}" 17 | db_subnet_group_name = "${aws_db_subnet_group.default.id}" 18 | storage_encrypted = "true" 19 | skip_final_snapshot = true 20 | vpc_security_group_ids = ["${var.security_group}"] 21 | multi_az = "${var.multi_az}" 22 | 23 | tags = "${merge(var.tags, map("Name", format("%s", var.name)))}" 24 | 25 | } 26 | -------------------------------------------------------------------------------- /modules/aws/rds/output.tf: -------------------------------------------------------------------------------- 1 | 2 | # Output the ID of the RDS instance 3 | output "rds_instance_id" { 4 | value = "${aws_db_instance.default.id}" 5 | } 6 | 7 | # Output the address (aka hostname) of the RDS instance 8 | output "rds_instance_address" { 9 | value = "${aws_db_instance.default.address}" 10 | } 11 | 12 | # Output endpoint (hostname:port) of the RDS instance 13 | output "rds_instance_endpoint" { 14 | value = "${aws_db_instance.default.endpoint}" 15 | } 16 | -------------------------------------------------------------------------------- /modules/aws/rds/variables.tf: -------------------------------------------------------------------------------- 1 | variable "allocated_storage" { 2 | default = 20 3 | type = "string" 4 | } 5 | 6 | variable "security_group" { 7 | type = "list" 8 | } 9 | 10 | variable "engine" { 11 | default = "mysql" 12 | type = "string" 13 | } 14 | 15 | variable "engine_version" { 16 | type = "string" 17 | } 18 | 19 | variable "instance_class" { 20 | type = "string" 21 | default = "db.t2.medium" 22 | } 23 | 24 | variable "username" { 25 | type = "string" 26 | } 27 | 28 | variable "password" { 29 | type = "string" 30 | } 31 | 32 | variable "db_subnet_ids" { 33 | type = "list" 34 | } 35 | 36 | variable "name" { 37 | type = "string" 38 | } 39 | 40 | variable "multi_az" { 41 | type = "string" 42 | default = false 43 | } 44 | 45 | variable "tags" { 46 | description = "A map of tags to add to all resources" 47 | default = {} 48 | } 49 | -------------------------------------------------------------------------------- /resources/AWS_Pop_Up_Loft_Munich.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudreach/awsloft-terraform-ci/4d6d799d90b424194cd9a820ba414e94c12d52f7/resources/AWS_Pop_Up_Loft_Munich.jpg -------------------------------------------------------------------------------- /resources/architecture.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudreach/awsloft-terraform-ci/4d6d799d90b424194cd9a820ba414e94c12d52f7/resources/architecture.jpeg -------------------------------------------------------------------------------- /resources/pipeline.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudreach/awsloft-terraform-ci/4d6d799d90b424194cd9a820ba414e94c12d52f7/resources/pipeline.jpeg -------------------------------------------------------------------------------- /test/http_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | base_url="http://$(aws ssm get-parameters --name $SSM_VAL --query Parameters[0].Value --output text)" 4 | siteList=( 5 | '/' 6 | '/wp-content/themes/twentyseventeen/assets/images/header.jpg' 7 | '/wp-content/themes/twentyseventeen/style.css' 8 | '/wp-includes/js/jquery/jquery.js' 9 | ) 10 | 11 | 12 | for var in "${siteList[@]}" 13 | do 14 | status_code=$(curl -L --write-out %{http_code} --silent --output /dev/null ${base_url}${var} ) 15 | if [[ "$status_code" -ne 200 ]] ; then 16 | echo "[ERROR] ${base_url}${var} : $status_code" 17 | exit 1 18 | else 19 | echo "[SUCCESS] ${base_url}${var} : $status_code" 20 | fi 21 | done 22 | --------------------------------------------------------------------------------