Skip to content

Commit

Permalink
Implement windows server example
Browse files Browse the repository at this point in the history
  • Loading branch information
zackproser committed Jun 28, 2022
1 parent 0dd0f81 commit c04ec07
Show file tree
Hide file tree
Showing 9 changed files with 364 additions and 0 deletions.
57 changes: 57 additions & 0 deletions examples/terraform-aws-ec2-windows-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Windows Instance Example

This folder provides a Packer template that can be used to build an Amazon Machine Image (AMI) of a Windows 2016 Server that comes pre-installed with:

- The [Chocolately package manager](https://chocolatey.org/why-chocolatey) which makes it easy to install additional software packages onto Windows
- Git
- Python 3

In addition, this folder provides an example of how to launch a Windows instance based off this AMI that can be connected to via a Remote Desktop Protocol (RDP) client for the purposes of testing software or experimentation.

This setup is ideal for "hot-reloading" code that you're actively developing and testing it against the Windows server. You can develop your code in your usual environment, perhaps a Mac or Linux laptop, yet see your changes reflected on the Windows server in seconds, by sharing a folder from your development machine with the Windows server via the RDP client.

## Quick start

Pre-requistes:

- [Packer version v1.8.1 or newer](https://github.com/hashicorp/packer)
- [Terraform v1.0 or newer](https://github.com/hashicorp/terraform)
- An AWS account with valid security credentials

First, we'll build the AMI for the Windows Instance. Change into the packer directory:

`cd packer`

In order to build an Amazon Machine Image with Packer, you'll need to export your AWS account credentials. You can export your AWS credentials as the environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`.

For more information on authenticating to your AWS account from the command line, see our blog post [Authenticating to AWS with Environment Variables](https://blog.gruntwork.io/authenticating-to-aws-with-environment-variables-e793d6f6d02e).

With your credentials properly exported, you can now run the packer build:

`packer build build.pkr.hcl`

This may take upwards of 25 minutes to complete, but generally completes in about 5 minutes. Keep an eye on your EC2 dashboard and ensure that you have selected the correct region and that you are on the AMI view. Once your AMI status has changed from "Pending" to "Available", you can copy your AMI ID.

Create a new file named `terraform.tfvars` in this same directory and enter the following variables:

```hcl
ami_id = "<the AMI ID you copied in the previous step>"
region = "us-east-1"
root_volume_size = 100
```
Save the file.

You're now ready to run terraform plan and check the output before proceeding:

`terraform plan`

Take a look at the plan output and ensure everything looks correct. You should see a single EC2 instance being created along with supporting resources such as a security group and security group rules.

Once you're satisfied that the plan looks good, run terraform apply to create the infrastructure:

`terraform apply --auto-approve`

Once your resources apply successfully you'll see a similar output message containing the public IPv4 address of your Windows instance:

`instance_ip = "35.84.139.82"`

81 changes: 81 additions & 0 deletions examples/terraform-aws-ec2-windows-example/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# LAUNCH THE WINDOWS INSTANCE
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

terraform {
# This module is now only being tested with Terraform 1.1.x. However, to make upgrading easier, we are setting 1.0.0 as the minimum version.
required_version = ">= 1.0.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "< 4.0"
}
}
}

# ---------------------------------------------------------------------------------------------------------------------
# CONFIGURE OUR AWS CONNECTION
# ---------------------------------------------------------------------------------------------------------------------

provider "aws" {
# The AWS region in which all resources will be created
region = var.region
}

# ---------------------------------------------------------------------------------------------------------------------
# DEPLOY INTO THE DEFAULT VPC AND SUBNETS
# To keep this example simple, we are deploying into the Default VPC and its subnets. In real-world usage, you should
# deploy into a custom VPC and private subnets.
# ---------------------------------------------------------------------------------------------------------------------

data "aws_vpc" "default" {
default = true
}

data "aws_subnet_ids" "all" {
vpc_id = data.aws_vpc.default.id
}

# ---------------------------------------------------------------------------------------------------------------------
# CREATE A SECURITY GROUP TO ALLOW ACCESS TO THE RDS INSTANCE
# ---------------------------------------------------------------------------------------------------------------------

resource "aws_security_group" "windows_instance" {
name = var.name
vpc_id = data.aws_vpc.default.id
}

resource "aws_security_group_rule" "allow_rdp" {
type = "ingress"
security_group_id = aws_security_group.windows_instance.id

from_port = "3389"
to_port = "3389"
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

resource "aws_security_group_rule" "allow_egress" {
type = "egress"
security_group_id = aws_security_group.windows_instance.id

from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}

# ---------------------------------------------------------------------------------------------------------------------
# LAUNCH THE WINDOWS INSTANCE
# ---------------------------------------------------------------------------------------------------------------------

resource "aws_instance" "instance" {
ami = var.ami
instance_type = var.instance_type
vpc_security_group_ids = [aws_security_group.windows_instance.id]

tags = {
Name = var.instance_type
}
}

4 changes: 4 additions & 0 deletions examples/terraform-aws-ec2-windows-example/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
output "windows_instance_public_ip" {
description = "The IPv4 address of the Windows instance. Enter this value into your RDP client when connecting to your instance."
value = aws_instance.instance.public_ip
}
52 changes: 52 additions & 0 deletions examples/terraform-aws-ec2-windows-example/packer/build.pkr.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
variable "instance_type" {
type = string
description = "The EC2 instance size / type to launch"
}

variable "region" {
type = string
description = "The AWS region to deploy the Windows instance into"
}


data "amazon-ami" "windows_server_2016" {
filters = {
name = "Windows_Server-2016-English-Full-Base-*"
root-device-type = "ebs"
virtualization-type = "hvm"
}
most_recent = true
owners = ["801119661308"]
region = var.region
}

locals {
build_version = "${legacy_isotime("2006.01.02.150405")}"
}

source "amazon-ebs" "windows_server_2016" {
ami_name = "WIN2016-CUSTOM-${local.build_version}"
associate_public_ip_address = true
communicator = "winrm"
instance_type = var.instance_type
region = var.region
source_ami = "${data.amazon-ami.windows_server_2016.id}"
user_data_file = "${path.root}/scripts/bootstrap_windows.txt"
winrm_timeout = "15m"
winrm_password = "SuperS3cr3t!!!!"
winrm_username = "Administrator"

}

build {
sources = ["source.amazon-ebs.windows_server_2016"]

# Install Chocolatey package manager, then install any Chocolatey packages defined in scripts/install_packages.ps1
provisioner "powershell" {
scripts = ["${path.root}/scripts/install_chocolatey.ps1", "${path.root}/scripts/install_packages.ps1"]
}

provisioner "windows-restart" {
restart_timeout = "35m"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<powershell>
# This script is adapted from: https://learn.hashicorp.com/tutorials/packer/aws-windows-image?in=packer/integrations

# Set administrator password
net user Administrator SuperS3cr3t!!!!
wmic useraccount where "name='Administrator'" set PasswordExpires=FALSE

# First, make sure WinRM can't be connected to
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=block

# Delete any existing WinRM listeners
winrm delete winrm/config/listener?Address=*+Transport=HTTP 2>$Null
winrm delete winrm/config/listener?Address=*+Transport=HTTPS 2>$Null

# Disable group policies which block basic authentication and unencrypted login

Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Client -Name AllowBasic -Value 1
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Client -Name AllowUnencryptedTraffic -Value 1
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Service -Name AllowBasic -Value 1
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Service -Name AllowUnencryptedTraffic -Value 1


# Create a new WinRM listener and configure
winrm create winrm/config/listener?Address=*+Transport=HTTP
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="0"}'
winrm set winrm/config '@{MaxTimeoutms="7200000"}'
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
winrm set winrm/config/service '@{MaxConcurrentOperationsPerUser="12000"}'
winrm set winrm/config/service/auth '@{Basic="true"}'
winrm set winrm/config/client/auth '@{Basic="true"}'

# Configure UAC to allow privilege elevation in remote shells
$Key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
$Setting = 'LocalAccountTokenFilterPolicy'
Set-ItemProperty -Path $Key -Name $Setting -Value 1 -Force

# Configure and restart the WinRM Service; Enable the required firewall exception
Stop-Service -Name WinRM
Set-Service -Name WinRM -StartupType Automatic
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new action=allow localip=any remoteip=any
Start-Service -Name WinRM
</powershell>

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
choco install -y python3
choco install -y git
47 changes: 47 additions & 0 deletions examples/terraform-aws-ec2-windows-example/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# ---------------------------------------------------------------------------------------------------------------------
# ENVIRONMENT VARIABLES
# Define these secrets as environment variables
# ---------------------------------------------------------------------------------------------------------------------

# AWS_ACCESS_KEY_ID
# AWS_SECRET_ACCESS_KEY

# ---------------------------------------------------------------------------------------------------------------------
# REQUIRED PARAMETERS
# You must provide a value for each of these parameters.
# ---------------------------------------------------------------------------------------------------------------------

variable "region" {
description = "The AWS region in which all resources will be created"
type = string
default = "us-west-2"
}

variable "ami" {
description = "The ID of the AMI to run on the Windows instance."
type = string
}

# ---------------------------------------------------------------------------------------------------------------------
# OPTIONAL PARAMETERS
# These parameters have reasonable defaults.
# ---------------------------------------------------------------------------------------------------------------------

variable "name" {
description = "The name of the Windows instance"
type = string
default = "windows_test_instance"
}

variable "instance_type" {
description = "The instance type to deploy."
type = string
default = "t3.small"
}

variable "root_volume_size" {
description = "The size in GiB of the root volume. Must match the root volume size of the target AMI."
type = number
default = 30
}

77 changes: 77 additions & 0 deletions test/terraform_aws_ec2_windows_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package test

import (
"fmt"
"path/filepath"
"testing"

"github.com/gruntwork-io/terratest/modules/aws"
"github.com/gruntwork-io/terratest/modules/packer"
"github.com/gruntwork-io/terratest/modules/random"
"github.com/gruntwork-io/terratest/modules/terraform"
test_structure "github.com/gruntwork-io/terratest/modules/test-structure"
)

func TestWindowsInstance(t *testing.T) {
// Uncomment any of the following to skip that section during the test
//os.Setenv("SKIP_setup", "true")
//os.Setenv("SKIP_build_ami", "true")
//os.Setenv("SKIP_deploy", "true")
//os.Setenv("SKIP_validate", "true")
//os.Setenv("SKIP_cleanup", "true")

workingDir := filepath.Join(".", "stages", t.Name())
testBasePath := test_structure.CopyTerraformFolderToTemp(t, "..", "examples/terraform-aws-ec2-windows-example")

test_structure.RunTestStage(t, "setup", func() {
uniqueID := random.UniqueId()
region := aws.GetRandomRegion(t, []string{}, []string{})
roleName := fmt.Sprintf("%s-test-role", uniqueID)

instanceType := aws.GetRecommendedInstanceType(t, region, []string{"t2.micro, t3.micro", "t2.small", "t3.small"})
test_structure.SaveString(t, workingDir, "region", region)
test_structure.SaveString(t, workingDir, "uniqueID", uniqueID)
test_structure.SaveString(t, workingDir, "instanceType", instanceType)
test_structure.SaveString(t, workingDir, "roleName", roleName)
})

test_structure.RunTestStage(t, "build_ami", func() {
region := test_structure.LoadString(t, workingDir, "region")
instanceType := test_structure.LoadString(t, workingDir, "instanceType")
roleName := test_structure.LoadString(t, workingDir, "roleName")

varsMap := make(map[string]string)

varsMap["instance_type"] = instanceType
varsMap["region"] = region
packerOptions := &packer.Options{
Template: filepath.Join(testBasePath, "packer/build.pkr.hcl"),
Vars: varsMap,
}

amiID := packer.BuildArtifact(t, packerOptions)

test_structure.SaveString(t, workingDir, "amiID", amiID)

terratestOptions := &terraform.Options{
TerraformDir: testBasePath,
Vars: make(map[string]interface{}),
}

terratestOptions.Vars["ami"] = amiID
terratestOptions.Vars["region"] = region
terratestOptions.Vars["iam_role_name"] = roleName
test_structure.SaveTerraformOptions(t, workingDir, terratestOptions)
})

defer test_structure.RunTestStage(t, "cleanup", func() {
terratestOptions := test_structure.LoadTerraformOptions(t, workingDir)
terraform.Destroy(t, terratestOptions)
})

test_structure.RunTestStage(t, "deploy", func() {
terratestOptions := test_structure.LoadTerraformOptions(t, workingDir)
terraform.InitAndApply(t, terratestOptions)
})

}

0 comments on commit c04ec07

Please sign in to comment.