Skip to content

Commit f9a7663

Browse files
authored
Merge pull request #1147 from gruntwork-io/feature/windows-instance-example
Implement Windows ec2 instance example
2 parents 0dd0f81 + c04ec07 commit f9a7663

File tree

9 files changed

+364
-0
lines changed

9 files changed

+364
-0
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Windows Instance Example
2+
3+
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:
4+
5+
- The [Chocolately package manager](https://chocolatey.org/why-chocolatey) which makes it easy to install additional software packages onto Windows
6+
- Git
7+
- Python 3
8+
9+
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.
10+
11+
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.
12+
13+
## Quick start
14+
15+
Pre-requistes:
16+
17+
- [Packer version v1.8.1 or newer](https://github.com/hashicorp/packer)
18+
- [Terraform v1.0 or newer](https://github.com/hashicorp/terraform)
19+
- An AWS account with valid security credentials
20+
21+
First, we'll build the AMI for the Windows Instance. Change into the packer directory:
22+
23+
`cd packer`
24+
25+
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`.
26+
27+
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).
28+
29+
With your credentials properly exported, you can now run the packer build:
30+
31+
`packer build build.pkr.hcl`
32+
33+
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.
34+
35+
Create a new file named `terraform.tfvars` in this same directory and enter the following variables:
36+
37+
```hcl
38+
ami_id = "<the AMI ID you copied in the previous step>"
39+
region = "us-east-1"
40+
root_volume_size = 100
41+
```
42+
Save the file.
43+
44+
You're now ready to run terraform plan and check the output before proceeding:
45+
46+
`terraform plan`
47+
48+
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.
49+
50+
Once you're satisfied that the plan looks good, run terraform apply to create the infrastructure:
51+
52+
`terraform apply --auto-approve`
53+
54+
Once your resources apply successfully you'll see a similar output message containing the public IPv4 address of your Windows instance:
55+
56+
`instance_ip = "35.84.139.82"`
57+
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2+
# LAUNCH THE WINDOWS INSTANCE
3+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4+
5+
terraform {
6+
# 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.
7+
required_version = ">= 1.0.0"
8+
required_providers {
9+
aws = {
10+
source = "hashicorp/aws"
11+
version = "< 4.0"
12+
}
13+
}
14+
}
15+
16+
# ---------------------------------------------------------------------------------------------------------------------
17+
# CONFIGURE OUR AWS CONNECTION
18+
# ---------------------------------------------------------------------------------------------------------------------
19+
20+
provider "aws" {
21+
# The AWS region in which all resources will be created
22+
region = var.region
23+
}
24+
25+
# ---------------------------------------------------------------------------------------------------------------------
26+
# DEPLOY INTO THE DEFAULT VPC AND SUBNETS
27+
# To keep this example simple, we are deploying into the Default VPC and its subnets. In real-world usage, you should
28+
# deploy into a custom VPC and private subnets.
29+
# ---------------------------------------------------------------------------------------------------------------------
30+
31+
data "aws_vpc" "default" {
32+
default = true
33+
}
34+
35+
data "aws_subnet_ids" "all" {
36+
vpc_id = data.aws_vpc.default.id
37+
}
38+
39+
# ---------------------------------------------------------------------------------------------------------------------
40+
# CREATE A SECURITY GROUP TO ALLOW ACCESS TO THE RDS INSTANCE
41+
# ---------------------------------------------------------------------------------------------------------------------
42+
43+
resource "aws_security_group" "windows_instance" {
44+
name = var.name
45+
vpc_id = data.aws_vpc.default.id
46+
}
47+
48+
resource "aws_security_group_rule" "allow_rdp" {
49+
type = "ingress"
50+
security_group_id = aws_security_group.windows_instance.id
51+
52+
from_port = "3389"
53+
to_port = "3389"
54+
protocol = "tcp"
55+
cidr_blocks = ["0.0.0.0/0"]
56+
}
57+
58+
resource "aws_security_group_rule" "allow_egress" {
59+
type = "egress"
60+
security_group_id = aws_security_group.windows_instance.id
61+
62+
from_port = 0
63+
to_port = 0
64+
protocol = "-1"
65+
cidr_blocks = ["0.0.0.0/0"]
66+
}
67+
68+
# ---------------------------------------------------------------------------------------------------------------------
69+
# LAUNCH THE WINDOWS INSTANCE
70+
# ---------------------------------------------------------------------------------------------------------------------
71+
72+
resource "aws_instance" "instance" {
73+
ami = var.ami
74+
instance_type = var.instance_type
75+
vpc_security_group_ids = [aws_security_group.windows_instance.id]
76+
77+
tags = {
78+
Name = var.instance_type
79+
}
80+
}
81+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
output "windows_instance_public_ip" {
2+
description = "The IPv4 address of the Windows instance. Enter this value into your RDP client when connecting to your instance."
3+
value = aws_instance.instance.public_ip
4+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
variable "instance_type" {
2+
type = string
3+
description = "The EC2 instance size / type to launch"
4+
}
5+
6+
variable "region" {
7+
type = string
8+
description = "The AWS region to deploy the Windows instance into"
9+
}
10+
11+
12+
data "amazon-ami" "windows_server_2016" {
13+
filters = {
14+
name = "Windows_Server-2016-English-Full-Base-*"
15+
root-device-type = "ebs"
16+
virtualization-type = "hvm"
17+
}
18+
most_recent = true
19+
owners = ["801119661308"]
20+
region = var.region
21+
}
22+
23+
locals {
24+
build_version = "${legacy_isotime("2006.01.02.150405")}"
25+
}
26+
27+
source "amazon-ebs" "windows_server_2016" {
28+
ami_name = "WIN2016-CUSTOM-${local.build_version}"
29+
associate_public_ip_address = true
30+
communicator = "winrm"
31+
instance_type = var.instance_type
32+
region = var.region
33+
source_ami = "${data.amazon-ami.windows_server_2016.id}"
34+
user_data_file = "${path.root}/scripts/bootstrap_windows.txt"
35+
winrm_timeout = "15m"
36+
winrm_password = "SuperS3cr3t!!!!"
37+
winrm_username = "Administrator"
38+
39+
}
40+
41+
build {
42+
sources = ["source.amazon-ebs.windows_server_2016"]
43+
44+
# Install Chocolatey package manager, then install any Chocolatey packages defined in scripts/install_packages.ps1
45+
provisioner "powershell" {
46+
scripts = ["${path.root}/scripts/install_chocolatey.ps1", "${path.root}/scripts/install_packages.ps1"]
47+
}
48+
49+
provisioner "windows-restart" {
50+
restart_timeout = "35m"
51+
}
52+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<powershell>
2+
# This script is adapted from: https://learn.hashicorp.com/tutorials/packer/aws-windows-image?in=packer/integrations
3+
4+
# Set administrator password
5+
net user Administrator SuperS3cr3t!!!!
6+
wmic useraccount where "name='Administrator'" set PasswordExpires=FALSE
7+
8+
# First, make sure WinRM can't be connected to
9+
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=block
10+
11+
# Delete any existing WinRM listeners
12+
winrm delete winrm/config/listener?Address=*+Transport=HTTP 2>$Null
13+
winrm delete winrm/config/listener?Address=*+Transport=HTTPS 2>$Null
14+
15+
# Disable group policies which block basic authentication and unencrypted login
16+
17+
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Client -Name AllowBasic -Value 1
18+
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Client -Name AllowUnencryptedTraffic -Value 1
19+
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Service -Name AllowBasic -Value 1
20+
Set-ItemProperty -Path HKLM:\Software\Policies\Microsoft\Windows\WinRM\Service -Name AllowUnencryptedTraffic -Value 1
21+
22+
23+
# Create a new WinRM listener and configure
24+
winrm create winrm/config/listener?Address=*+Transport=HTTP
25+
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="0"}'
26+
winrm set winrm/config '@{MaxTimeoutms="7200000"}'
27+
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
28+
winrm set winrm/config/service '@{MaxConcurrentOperationsPerUser="12000"}'
29+
winrm set winrm/config/service/auth '@{Basic="true"}'
30+
winrm set winrm/config/client/auth '@{Basic="true"}'
31+
32+
# Configure UAC to allow privilege elevation in remote shells
33+
$Key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'
34+
$Setting = 'LocalAccountTokenFilterPolicy'
35+
Set-ItemProperty -Path $Key -Name $Setting -Value 1 -Force
36+
37+
# Configure and restart the WinRM Service; Enable the required firewall exception
38+
Stop-Service -Name WinRM
39+
Set-Service -Name WinRM -StartupType Automatic
40+
netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new action=allow localip=any remoteip=any
41+
Start-Service -Name WinRM
42+
</powershell>
43+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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'))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
choco install -y python3
2+
choco install -y git
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# ---------------------------------------------------------------------------------------------------------------------
2+
# ENVIRONMENT VARIABLES
3+
# Define these secrets as environment variables
4+
# ---------------------------------------------------------------------------------------------------------------------
5+
6+
# AWS_ACCESS_KEY_ID
7+
# AWS_SECRET_ACCESS_KEY
8+
9+
# ---------------------------------------------------------------------------------------------------------------------
10+
# REQUIRED PARAMETERS
11+
# You must provide a value for each of these parameters.
12+
# ---------------------------------------------------------------------------------------------------------------------
13+
14+
variable "region" {
15+
description = "The AWS region in which all resources will be created"
16+
type = string
17+
default = "us-west-2"
18+
}
19+
20+
variable "ami" {
21+
description = "The ID of the AMI to run on the Windows instance."
22+
type = string
23+
}
24+
25+
# ---------------------------------------------------------------------------------------------------------------------
26+
# OPTIONAL PARAMETERS
27+
# These parameters have reasonable defaults.
28+
# ---------------------------------------------------------------------------------------------------------------------
29+
30+
variable "name" {
31+
description = "The name of the Windows instance"
32+
type = string
33+
default = "windows_test_instance"
34+
}
35+
36+
variable "instance_type" {
37+
description = "The instance type to deploy."
38+
type = string
39+
default = "t3.small"
40+
}
41+
42+
variable "root_volume_size" {
43+
description = "The size in GiB of the root volume. Must match the root volume size of the target AMI."
44+
type = number
45+
default = 30
46+
}
47+
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package test
2+
3+
import (
4+
"fmt"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/gruntwork-io/terratest/modules/aws"
9+
"github.com/gruntwork-io/terratest/modules/packer"
10+
"github.com/gruntwork-io/terratest/modules/random"
11+
"github.com/gruntwork-io/terratest/modules/terraform"
12+
test_structure "github.com/gruntwork-io/terratest/modules/test-structure"
13+
)
14+
15+
func TestWindowsInstance(t *testing.T) {
16+
// Uncomment any of the following to skip that section during the test
17+
//os.Setenv("SKIP_setup", "true")
18+
//os.Setenv("SKIP_build_ami", "true")
19+
//os.Setenv("SKIP_deploy", "true")
20+
//os.Setenv("SKIP_validate", "true")
21+
//os.Setenv("SKIP_cleanup", "true")
22+
23+
workingDir := filepath.Join(".", "stages", t.Name())
24+
testBasePath := test_structure.CopyTerraformFolderToTemp(t, "..", "examples/terraform-aws-ec2-windows-example")
25+
26+
test_structure.RunTestStage(t, "setup", func() {
27+
uniqueID := random.UniqueId()
28+
region := aws.GetRandomRegion(t, []string{}, []string{})
29+
roleName := fmt.Sprintf("%s-test-role", uniqueID)
30+
31+
instanceType := aws.GetRecommendedInstanceType(t, region, []string{"t2.micro, t3.micro", "t2.small", "t3.small"})
32+
test_structure.SaveString(t, workingDir, "region", region)
33+
test_structure.SaveString(t, workingDir, "uniqueID", uniqueID)
34+
test_structure.SaveString(t, workingDir, "instanceType", instanceType)
35+
test_structure.SaveString(t, workingDir, "roleName", roleName)
36+
})
37+
38+
test_structure.RunTestStage(t, "build_ami", func() {
39+
region := test_structure.LoadString(t, workingDir, "region")
40+
instanceType := test_structure.LoadString(t, workingDir, "instanceType")
41+
roleName := test_structure.LoadString(t, workingDir, "roleName")
42+
43+
varsMap := make(map[string]string)
44+
45+
varsMap["instance_type"] = instanceType
46+
varsMap["region"] = region
47+
packerOptions := &packer.Options{
48+
Template: filepath.Join(testBasePath, "packer/build.pkr.hcl"),
49+
Vars: varsMap,
50+
}
51+
52+
amiID := packer.BuildArtifact(t, packerOptions)
53+
54+
test_structure.SaveString(t, workingDir, "amiID", amiID)
55+
56+
terratestOptions := &terraform.Options{
57+
TerraformDir: testBasePath,
58+
Vars: make(map[string]interface{}),
59+
}
60+
61+
terratestOptions.Vars["ami"] = amiID
62+
terratestOptions.Vars["region"] = region
63+
terratestOptions.Vars["iam_role_name"] = roleName
64+
test_structure.SaveTerraformOptions(t, workingDir, terratestOptions)
65+
})
66+
67+
defer test_structure.RunTestStage(t, "cleanup", func() {
68+
terratestOptions := test_structure.LoadTerraformOptions(t, workingDir)
69+
terraform.Destroy(t, terratestOptions)
70+
})
71+
72+
test_structure.RunTestStage(t, "deploy", func() {
73+
terratestOptions := test_structure.LoadTerraformOptions(t, workingDir)
74+
terraform.InitAndApply(t, terratestOptions)
75+
})
76+
77+
}

0 commit comments

Comments
 (0)