├── terraform ├── hcl │ ├── outputs.tf │ ├── variables.tf │ └── main.tf ├── hello_terraform │ └── main.tf ├── module │ ├── terraform-aws-lambda-python-archive │ │ ├── examples │ │ │ ├── python │ │ │ │ ├── requirements.txt │ │ │ │ └── my_lambda.py │ │ │ ├── artifacts │ │ │ │ └── lambda.zip │ │ │ └── lambda_python_archive.tf │ │ ├── scripts │ │ │ ├── .DS_Store │ │ │ └── build_lambda.py │ │ ├── main.tf │ │ ├── variables.tf │ │ ├── outputs.tf │ │ └── README.md │ └── README.md └── app │ ├── variables.tf │ ├── outputs.tf │ ├── lambda.py │ ├── README.md │ └── main.tf ├── README.md ├── .gitignore └── iacwtf.ipynb /terraform/hcl/outputs.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /terraform/hcl/variables.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iacwtf 2 | Introducing Infrastructure As Code With Terraform -------------------------------------------------------------------------------- /terraform/hello_terraform/main.tf: -------------------------------------------------------------------------------- 1 | output "greeting" { 2 | value = "Hello Terraform." 3 | } -------------------------------------------------------------------------------- /terraform/module/terraform-aws-lambda-python-archive/examples/python/requirements.txt: -------------------------------------------------------------------------------- 1 | requests -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .terraform 3 | terraform.tfstate 4 | terraform.tfstate.backup 5 | *.tfplan 6 | src.zip -------------------------------------------------------------------------------- /terraform/module/terraform-aws-lambda-python-archive/examples/python/my_lambda.py: -------------------------------------------------------------------------------- 1 | def entrypoint(): 2 | print("Hello world.") 3 | return 0 -------------------------------------------------------------------------------- /terraform/app/variables.tf: -------------------------------------------------------------------------------- 1 | variable "api_name" { 2 | type = "string" 3 | description = "Name of the REST API to create in AWS API Gateway" 4 | } -------------------------------------------------------------------------------- /terraform/app/outputs.tf: -------------------------------------------------------------------------------- 1 | 2 | /*output "invoke_url" { 3 | value = "${aws_api_gateway_deployment.deployment.invoke_url}${aws_api_gateway_resource.resource.path}" 4 | } 5 | */ -------------------------------------------------------------------------------- /terraform/module/README.md: -------------------------------------------------------------------------------- 1 | # Modules 2 | 3 | Terraform Modules provide a mechanism for code re-use and encapsulation. 4 | The state of the module is oapue to the consumer. 5 | 6 | -------------------------------------------------------------------------------- /terraform/module/terraform-aws-lambda-python-archive/scripts/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rojopolis/iacwtf/HEAD/terraform/module/terraform-aws-lambda-python-archive/scripts/.DS_Store -------------------------------------------------------------------------------- /terraform/module/terraform-aws-lambda-python-archive/examples/artifacts/lambda.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rojopolis/iacwtf/HEAD/terraform/module/terraform-aws-lambda-python-archive/examples/artifacts/lambda.zip -------------------------------------------------------------------------------- /terraform/app/lambda.py: -------------------------------------------------------------------------------- 1 | def entrypoint(event, context): 2 | message = 'Hello World.' 3 | return { 4 | "isBase64Encoded": False, 5 | "statusCode": 200, 6 | "headers": {}, 7 | "body": message 8 | } -------------------------------------------------------------------------------- /terraform/app/README.md: -------------------------------------------------------------------------------- 1 | #AWS API Gateway + Lambda Serverless App. 2 | 3 | ![terraform_lambda_architecture](https://user-images.githubusercontent.com/39421615/59810459-268f2b00-92ba-11e9-857f-cebda3b5917f.jpg) 4 | 5 | Learn how to deploy an AWS Serverless app with Terraform -------------------------------------------------------------------------------- /terraform/module/terraform-aws-lambda-python-archive/main.tf: -------------------------------------------------------------------------------- 1 | data "external" "lambda_archive" { 2 | program = ["python3", "${path.module}/scripts/build_lambda.py"] 3 | query = { 4 | src_dir = var.src_dir 5 | output_path = var.output_path 6 | } 7 | } -------------------------------------------------------------------------------- /terraform/module/terraform-aws-lambda-python-archive/variables.tf: -------------------------------------------------------------------------------- 1 | variable "src_dir" { 2 | description = "Path to root of Python source to package." 3 | type = "string" 4 | } 5 | 6 | variable "output_path" { 7 | description = "The output of the archive file." 8 | type = "string" 9 | } -------------------------------------------------------------------------------- /terraform/module/terraform-aws-lambda-python-archive/outputs.tf: -------------------------------------------------------------------------------- 1 | output "archive_path" { 2 | description = "Path of the archive file." 3 | value = data.external.lambda_archive.result.archive 4 | } 5 | 6 | output "source_code_hash" { 7 | description = "Base64 encoded SHA256 hash of the archive file." 8 | value = data.external.lambda_archive.result.base64sha256 9 | } -------------------------------------------------------------------------------- /terraform/module/terraform-aws-lambda-python-archive/examples/lambda_python_archive.tf: -------------------------------------------------------------------------------- 1 | module "python_lambda_archive" { 2 | source = "../" 3 | src_dir = "${path.module}/python" 4 | output_path = "${path.module}/artifacts/lambda.zip" 5 | } 6 | 7 | resource "aws_iam_role" "iam_for_lambda" { 8 | name = "iam_for_lambda" 9 | 10 | assume_role_policy = <\"Open" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": { 32 | "id": "QiMbwRZc41uE", 33 | "colab_type": "text" 34 | }, 35 | "source": [ 36 | "# Introducing Infrastructure As Code With Terraform\n", 37 | "\n", 38 | "Instructor:\n", 39 | "\n", 40 | " Robert Jordan\n", 41 | " Pragmatic AI Labs\n", 42 | " robert@paiml.com" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "metadata": { 48 | "id": "AMTagT7M5Aeu", 49 | "colab_type": "code", 50 | "cellView": "form", 51 | "outputId": "07e1c688-fe41-4304-9d07-0a5fb0d8217e", 52 | "colab": { 53 | "base_uri": "https://localhost:8080/", 54 | "height": 52 55 | } 56 | }, 57 | "source": [ 58 | "#@title Install Terraform\n", 59 | "#@markdown 1. Download from https://www.terraform.io/downloads.html\n", 60 | "#@markdown 2. unzip\n", 61 | "#@markdown 3. set `PATH`\n", 62 | "%%bash\n", 63 | "curl -so terraform.zip https://releases.hashicorp.com/terraform/0.12.2/terraform_0.12.2_linux_amd64.zip\n", 64 | "unzip terraform.zip > /dev/null && rm -f terraform.zip > /dev/null\n", 65 | "mv terraform /usr/local/bin > /dev/null\n", 66 | "terraform --version\n" 67 | ], 68 | "execution_count": 0, 69 | "outputs": [ 70 | { 71 | "output_type": "stream", 72 | "text": [ 73 | "Terraform v0.12.2\n", 74 | "\n" 75 | ], 76 | "name": "stdout" 77 | } 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "metadata": { 83 | "id": "V6ddWusE6QcJ", 84 | "colab_type": "code", 85 | "cellView": "both", 86 | "outputId": "cad93477-3126-4a53-b568-3c8aac4dd21c", 87 | "colab": { 88 | "base_uri": "https://localhost:8080/", 89 | "height": 34 90 | } 91 | }, 92 | "source": [ 93 | "#@title Clone Sample Code\n", 94 | "#@markdown `git clone https://github.com/rojopolis/iacwtf.git samples`\n", 95 | "%%bash\n", 96 | "rm -rf samples\n", 97 | "git clone https://github.com/rojopolis/iacwtf.git samples\n" 98 | ], 99 | "execution_count": 0, 100 | "outputs": [ 101 | { 102 | "output_type": "stream", 103 | "text": [ 104 | "Cloning into 'samples'...\n" 105 | ], 106 | "name": "stderr" 107 | } 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "metadata": { 113 | "id": "PfTahG4jA7-m", 114 | "colab_type": "text" 115 | }, 116 | "source": [ 117 | "#DEMO: Hello Terraform" 118 | ] 119 | }, 120 | { 121 | "cell_type": "code", 122 | "metadata": { 123 | "id": "P93IYhxoAlIP", 124 | "colab_type": "code", 125 | "cellView": "form", 126 | "outputId": "2fafb8aa-a7bb-4c09-9a3b-957dc190ebc0", 127 | "colab": { 128 | "base_uri": "https://localhost:8080/", 129 | "height": 121 130 | } 131 | }, 132 | "source": [ 133 | "#@title Demo\n", 134 | "#@markdown ```\n", 135 | "#@markdown cd samples/terraform/hello_terraform\n", 136 | "#@markdown terraform apply\n", 137 | "#@markdown ```\n", 138 | "%%bash\n", 139 | "cd samples/terraform/hello_terraform\n", 140 | "terraform apply\n" 141 | ], 142 | "execution_count": 0, 143 | "outputs": [ 144 | { 145 | "output_type": "stream", 146 | "text": [ 147 | "\u001b[0m\u001b[1m\u001b[32m\n", 148 | "Apply complete! Resources: 0 added, 0 changed, 0 destroyed.\u001b[0m\n", 149 | "\u001b[0m\u001b[1m\u001b[32m\n", 150 | "Outputs:\n", 151 | "\n", 152 | "greeting = Hello Terraform.\u001b[0m\n" 153 | ], 154 | "name": "stdout" 155 | } 156 | ] 157 | }, 158 | { 159 | "cell_type": "markdown", 160 | "metadata": { 161 | "id": "kkPhFEXGRqQZ", 162 | "colab_type": "text" 163 | }, 164 | "source": [ 165 | "#Infrastructure as Code\n", 166 | "\n", 167 | "##What is IaC?\n", 168 | "\n", 169 | " Infrastructure as code (IaC) is the process of managing and provisioning computer data centers through machine-readable definition files, rather than physical hardware configuration or interactive configuration tools.[^1]\n", 170 | " \n", 171 | "\n", 172 | "[^1]Andreas; Wittig, Michael (2016). Amazon Web Services in Action. Manning Press. p. 93" 173 | ] 174 | }, 175 | { 176 | "cell_type": "markdown", 177 | "metadata": { 178 | "id": "XiiVdNM-bk91", 179 | "colab_type": "text" 180 | }, 181 | "source": [ 182 | "##Terms\n", 183 | "1. Immutable vs. Mutable\n", 184 | "2. Declarative vs. Procedural\n", 185 | "3. Config Management vs. Orchestration" 186 | ] 187 | }, 188 | { 189 | "cell_type": "markdown", 190 | "metadata": { 191 | "id": "f8zBMRsQbhGL", 192 | "colab_type": "text" 193 | }, 194 | "source": [ 195 | "##Product Landscape\n", 196 | "\n", 197 | "| | Chef | Puppet | Ansible | CloudFormation | Pulumi | Terraform |\n", 198 | "|----------------|-------------------|-------------------|-------------------|----------------|---------------|---------------|\n", 199 | "| Code | Open Source | Open Source | Open Source | Proprietary | Open Source | Open Source |\n", 200 | "| Cloud | All | All | All | AWS | All | All |\n", 201 | "| Type | Config Management | Config Management | Config Management | Orchestration | Orchestration | Orchestration |\n", 202 | "| Infrastructure | Mutable | Mutable | Mutable | Immutable | Immutable | Immutable |\n", 203 | "| Language | Procedural | Declarative | Procedural | Declarative | Procedural? | Declarative |\n", 204 | "| Architecture | Client/Server | Client/Server | Client | Client | Client | Client |" 205 | ] 206 | }, 207 | { 208 | "cell_type": "markdown", 209 | "metadata": { 210 | "id": "oJvlAyvFbw6S", 211 | "colab_type": "text" 212 | }, 213 | "source": [ 214 | "##Why IaC?\n", 215 | "1. Trackable\n", 216 | "2. Repeatable\n", 217 | "3. Testable" 218 | ] 219 | } 220 | ] 221 | } -------------------------------------------------------------------------------- /terraform/hcl/main.tf: -------------------------------------------------------------------------------- 1 | #Terraform 2 | # Optional configuration for the Terraform Engine. 3 | terraform { 4 | required_version = ">=0.12.0" 5 | } 6 | 7 | 8 | #Provider 9 | # Implement cloud specific API and Terraform API. 10 | # Provider configuation is specific to each provider. 11 | # Providers expose Data Sources and Resources to Terraform. 12 | provider "aws" { 13 | version = "~> 2.0" 14 | region = "us-east-1" 15 | #access_key = "my-access-key" 16 | #secret_key = "my-secret-key" 17 | 18 | #Many providers also accept configuration via environment variables 19 | #or config files. The AWS provider will read the standard AWS CLI 20 | #settings if they are present. 21 | } 22 | 23 | 24 | /* 25 | #Data Sources 26 | # Objects NOT managed by Terraform. 27 | 28 | data "aws_caller_identity" "current" {} 29 | 30 | data "aws_availability_zones" "available" { 31 | state = "available" 32 | } 33 | 34 | 35 | #Resources 36 | # Objects managed by Terraform such as VMs or S3 Buckets. 37 | # Declaring a Resource tells Terraform that it should CREATE 38 | # and manage the Resource described. If the Resource already exists 39 | # it must be imported into Terraform's state. 40 | resource "aws_s3_bucket" "bucket1" { 41 | } 42 | 43 | 44 | #Outputs 45 | # Outputs are printed by the CLI after `apply`. 46 | # These can reveal calculated values. 47 | # Also used in more advanced use cases: modules, remote_state 48 | # Outputs can be retrieved at any time by running `terraform output` 49 | output "bucket_info" { 50 | value = aws_s3_bucket.bucket1 51 | } 52 | 53 | #output "aws_caller_info" { 54 | # value = aws_caller_identity.current 55 | #} 56 | 57 | #output "aws_availability_zones" { 58 | # value = aws_availability_zones.available 59 | #} 60 | 61 | 62 | #Interpolation 63 | # Substitute values in strings. 64 | resource "aws_s3_bucket" "bucket2" { 65 | bucket = "${data.aws_caller_identity.current.account_id}-bucket2" 66 | } 67 | 68 | 69 | #Dependency 70 | # Resources can depend on one another. Terraform will ensure that all 71 | # dependencies are met before creating the resource. Dependency can 72 | # be implicit or explicit. 73 | resource "aws_s3_bucket" "bucket3" { 74 | bucket = "${data.aws_caller_identity.current.account_id}-bucket3" 75 | tags = { 76 | # Implicit dependency 77 | dependency = aws_s3_bucket.bucket2.arn 78 | } 79 | } 80 | 81 | resource "aws_s3_bucket" "bucket4" { 82 | bucket = "${data.aws_caller_identity.current.account_id}-bucket4" 83 | # Explicit 84 | depends_on = [ 85 | aws_s3_bucket.bucket3 86 | ] 87 | } 88 | 89 | #Variables 90 | # Can be specified on the command line with -var bucket_name=my-bucket 91 | # or in files: terraform.tfvars or *.auto.tfvars 92 | # or in environment variables: TF_VAR_bucket_name 93 | variable "bucket_name" { 94 | # `type` is an optional data type specification 95 | type = "string" 96 | 97 | # `default` is the optional default value. If `default` is ommited 98 | # then a value must be supplied. 99 | #default = "my-bucket" 100 | } 101 | 102 | resource "aws_s3_bucket" "bucket5" { 103 | bucket = var.bucket_name 104 | } 105 | 106 | 107 | #Local Values 108 | # Local values allow you to assign a name to an expression. Locals 109 | # can make your code more readable. 110 | locals { 111 | aws_account = "${data.aws_caller_identity.current.account_id}-${lower(data.aws_caller_identity.current.user_id)}" 112 | } 113 | 114 | 115 | resource "aws_s3_bucket" "bucket6" { 116 | bucket = "${local.aws_account}-bucket6" 117 | } 118 | 119 | 120 | #Count 121 | # All resources have a `count` parameter. The default is 1. 122 | # If count is set then a list of resources is returned (even if there is only 1) 123 | # If `count` is set then a `count.index` value is available. This value contains 124 | # the current iteration number. 125 | # TIP: setting `count = 0` is a handy way to remove a resource but keep the config. 126 | resource "aws_s3_bucket" "bucketX" { 127 | count = 0 128 | bucket = "${local.aws_account}-bucket${count.index+7}" 129 | } 130 | 131 | output "bucketX" { 132 | value = aws_s3_bucket.bucketX 133 | } 134 | 135 | 136 | #Splat 137 | # To access an attribute of all elements in a list use the `splat` operator. 138 | output "bucket_names" { 139 | value = aws_s3_bucket.bucketX[*].bucket 140 | } 141 | 142 | 143 | #Data types 144 | # Terraform supports simple and complex data types 145 | locals { 146 | a_string = "This is a string." 147 | a_number = 3.1415 148 | a_boolean = true 149 | a_list = [ 150 | "element1", 151 | 2, 152 | "three" 153 | ] 154 | a_map = { 155 | key = "value" 156 | } 157 | 158 | # Complex 159 | person = { 160 | name = "Robert Jordan", 161 | phone_numbers = { 162 | home = "415-444-1212", 163 | mobile = "415-555-1313" 164 | }, 165 | active = false, 166 | age = 32 167 | } 168 | } 169 | 170 | output "home_phone" { 171 | value = local.person.phone_numbers.home 172 | } 173 | 174 | 175 | #Operators 176 | # Terraform supports arithmetic and logical operations in expressions too 177 | locals { 178 | //Arithmetic 179 | three = 1 + 2 // addition 180 | two = 3 - 1 // subtraction 181 | one = 2 / 2 // division 182 | zero = 1 * 0 // multiplication 183 | 184 | //Logical 185 | t = true || false // OR true if either value is true 186 | f = true && false // AND true if both values are true 187 | 188 | //Comparison 189 | gt = 2 > 1 // true if right value is greater 190 | gte = 2 >= 1 // true if right value is greater or equal 191 | lt = 1 < 2 // true if left value is greater 192 | lte = 1 <= 2 // true if left value is greate or equal 193 | eq = 1 == 1 // true if left and right are equal 194 | neq = 1 != 2 // true if left and right are not equal 195 | } 196 | 197 | output "arithmetic" { 198 | value = "${local.zero} ${local.one} ${local.two} ${local.three}" 199 | } 200 | 201 | output "logical" { 202 | value = "${local.t} ${local.f}" 203 | } 204 | 205 | output "comparison" { 206 | value = "${local.gt} ${local.gte} ${local.lt} ${local.lte} ${local.eq} ${local.neq}" 207 | } 208 | 209 | 210 | #Conditionals 211 | variable "bucket_count" { 212 | type = number 213 | } 214 | 215 | locals { 216 | minimum_number_of_buckets = 5 217 | number_of_buckets = var.bucket_count > 0 ? var.bucket_count : local.minimum_number_of_buckets //BUG! 218 | } 219 | 220 | resource "aws_s3_bucket" "buckets" { 221 | count = local.number_of_buckets 222 | bucket = "${local.aws_account}-bucket${count.index+7}" 223 | } 224 | 225 | #Functions 226 | # Terraform has 100+ built in functions (but no ability to define custom functions!) 227 | # https://www.terraform.io/docs/configuration/functions.html 228 | # The syntax for a function call is (, ). 229 | locals { 230 | //Date and Time 231 | ts = timestamp() //Returns the current date and time. 232 | current_month = formatdate("MMMM", local.ts) 233 | tomorrow = formatdate("DD", timeadd(local.ts, "24h")) 234 | } 235 | 236 | output "date_time" { 237 | value = "${local.current_month} ${local.tomorrow}" 238 | } 239 | 240 | locals { 241 | //Numeric 242 | number_of_buckets_2 = min(local.minimum_number_of_buckets, var.bucket_count) 243 | } 244 | 245 | locals { 246 | //String 247 | lcase = "${lower("A mixed case String")}" 248 | ucase = "${upper("a lower case string")}" 249 | trimmed = "${trimspace(" A string with leading and trailing spaces ")}" 250 | formatted = "${format("Hello %s", "World")}" 251 | formatted_list = "${formatlist("Hello %s", ["John", "Paul", "George", "Ringo"])}" 252 | } 253 | 254 | output "string_functions" { 255 | value = local.formatted_list 256 | } 257 | 258 | #Iteration 259 | # HCL has a `for` syntax for iterating over list values. 260 | locals { 261 | l = ["one", "two", "three"] 262 | upper_list = [for item in local.l: upper(item)] 263 | upper_map = {for item in local.l: item => upper(item)} 264 | } 265 | 266 | output "iterations" { 267 | value = local.upper_list 268 | } 269 | 270 | #Filtering 271 | # The `for` syntax can also take an `if` clause. 272 | locals { 273 | n = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 274 | evens = [for i in local.n: i if i % 2 == 0] 275 | } 276 | 277 | output "filtered"{ 278 | value = local.evens 279 | } 280 | 281 | #Directives and Heredocs 282 | # HCL supports more complex string templating that can be used to generate 283 | # full descriptive paragraphs too. 284 | output "heredoc" { 285 | value = <<-EOT 286 | This is called a `heredoc`. It's a string literal 287 | that can span multiple lines. 288 | EOT 289 | } 290 | 291 | output "directive" { 292 | value = <<-EOT 293 | This is a `heredoc` with directives. 294 | %{ if local.person.name == "" } 295 | Sorry, I don't know your name. 296 | %{ else } 297 | Hi ${local.person.name} 298 | %{ endif } 299 | EOT 300 | } 301 | 302 | output "iterated" { 303 | value = <<-EOT 304 | Directives can also iterate... 305 | %{ for number in local.evens } 306 | ${number} is even. 307 | %{ endfor } 308 | EOT 309 | } 310 | */ --------------------------------------------------------------------------------