Terraform Template
Overview
You can now deploy StorageLink using Terraform.
This article covers deploying a single instance of StorageLink version 1.1.0
. The Terraform template is provided as an example, so feel free to further customize it for your business case.
Make sure you are subscribed to Storagelink in the Marketplace before deploying the Terraform template or else you will run into an error while creating the Virtual Machine.
The Marketplace links for each cloud are as listed below:
- AWS: https://aws.amazon.com/marketplace/pp/prodview-nsvqooobiqul4
- Azure: https://azuremarketplace.microsoft.com/en-us/marketplace/apps/thorntechnologiesllc.storage-link
- Google Cloud: https://console.cloud.google.com/marketplace/details/thorn-technologies-public/storagelink
Running the template
This article contains two files:
slink-single-instance-default-vpc.tf
terraform.tfvars
Create these two files on your workstation, using the file contents at the bottom of this page. Make adjustments to the terraform.tfvars
file. Then, run the following commands:
terraform init
terraform plan
When you are ready to deploy the template, run:
terraform apply
How does it work
This article contains a main Terraform template named:
slink-single-instance.tf
- AWS
- Azure
This template provisions the following resources:
EC2 instance
: This server is based on the StorageLink marketplace AMIEC2 Security Group
: Allows TCP80
and443
from anywhere, but locks down admin port22
to a single IPElastic IP
: Static IP that retains the IP after a rebootIAM role
: Grants the EC2 instance access to S3
There's also another file that contains variables:
terraform.tfvars
Since this file is named terraform.tfvars
, it will be automatically used without having to run:
terraform -var-file terraform.tfvars
You can configure the following variables:
key_name
: Specify the name of your EC2 key pairregion
: Specify your current regionadmin_ip
: Get your workstation's public IP fromcheckip.dyndns.org
. Append/32
to specify a single IP rangeopen_s3_permissions
: Set this totrue
for full S3 permissions. Usefalse
to restrict permissions to buckets with the naming conventionslink-i-*
aws_profile
: Optional. Specify an AWS CLI profile if not using the default profile.ec2_instance_size
: Optional. Defaults tot3.medium
. Use this to override the EC2 instance size.disk_volume_size
: Optional. Defaults to32
. Use this to override the EC2 disk size, in GB.
Terraform file contents
slink-single-instance.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.27"
}
}
required_version = ">= 0.14.9"
}
provider "aws" {
region = var.region
profile = var.aws_profile != "" ? var.aws_profile : null
}
data "aws_partition" "current" {}
variable "open_s3_permissions" {
type = bool
description = "Set this to true to allow full S3 access to support multiple buckets. Otherwise, S3 permissions are limited to our default bucket naming convention: slink-<instance-id>."
default = false
}
variable "region" {
type = string
description = "This is the AWS region"
}
variable "ami_map" {
type = map
default = {
us-east-1 = "ami-06790432b06464b5b"
us-gov-east-1 = "ami-0220f569147b9d1b0"
us-gov-west-1 = "ami-04908080bfe92c331"
us-east-2 = "ami-05ac44cba1e3e3333"
us-west-1 = "ami-04f90c9f6e9076bb6"
us-west-2 = "ami-0b1986a1fa1493817"
ca-central-1 = "ami-0a830ea39a1398144"
eu-central-1 = "ami-08fd249dcabbdc425"
eu-west-1 = "ami-0103c3305244ea4c5"
eu-west-2 = "ami-03f215c587367202b"
eu-west-3 = "ami-0b8090f0c9f0b68e4"
eu-north-1 = "ami-06f785c42cf52775e"
eu-south-1 = "ami-094b741290030757a"
ap-east-1 = "ami-06f18f3c229a9867e"
ap-southeast-1 = "ami-0274dafda841b6f92"
ap-southeast-2 = "ami-0bc339b84ea025337"
ap-south-1 = "ami-041c3f708aa74e015"
ap-northeast-1 = "ami-01c08e1a1f57ba35a"
ap-northeast-2 = "ami-0ffb6ddd679d03ace"
me-south-1 = "ami-07df648a8aac3ec35"
}
}
variable "aws_profile" {
type = string
default = "default"
description = "Optional: specify an AWS profile"
}
variable "key_name" {
type = string
description = "Make sure you have access to this EC2 key pair. Otherwise, create a new key pair before proceeding."
}
variable "ec2_instance_size" {
type = string
description = "EC2 instance size. Recommended: t3.medium for testing, m5.large for production."
default = "t3.medium"
}
variable "disk_volume_size" {
type = number
description = "Disk volume size in GB. Must be at least 8."
default = 32
}
variable "admin_ip" {
type = string
description = "Public IP address range for SSH and web access. Use a CIDR range to restrict access. To get your local machine's IP, see http://checkip.dyndns.org/. (Remember to append /32 for a single IP e.g. 12.34.56.78/32) For security reasons, do not use 0.0.0.0/0."
validation {
condition = can(regex("([1-9]\\d{0,2})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/([1-9]\\d{0,1})",var.admin_ip))
error_message = "Must be a valid IP CIDR range in the form of x.x.x.x/x. Do not use 0.0.0.0/0."
}
}
resource "aws_eip" "eip" {
instance = aws_instance.storagelink.id
vpc = true
}
resource "aws_instance" "storagelink" {
ami = lookup(var.ami_map, var.region)
instance_type = var.ec2_instance_size
key_name = var.key_name
security_groups = ["EC2-Security-group"]
iam_instance_profile = aws_iam_instance_profile.ec2_profile.name
root_block_device {
volume_size = var.disk_volume_size
volume_type = "gp2"
encrypted = true
}
tags = {
Name = "storagelink"
}
}
resource "aws_iam_role" "iam_role" {
name = "iam_role"
managed_policy_arns = var.open_s3_permissions ? ["arn:${data.aws_partition.current.partition}:iam::aws:policy/AmazonS3FullAccess"] : []
assume_role_policy = jsonencode({
Version: "2012-10-17",
Statement: [
{
Action: "sts:AssumeRole",
Principal: {
Service: "ec2.amazonaws.com"
},
Effect: "Allow"
}
]
})
}
resource "aws_iam_instance_profile" "ec2_profile" {
name = "ec2_profile"
role = aws_iam_role.iam_role.name
}
resource "aws_iam_role_policy" "ec2_policy" {
name = "ec2_policy"
role = aws_iam_role.iam_role.id
policy = jsonencode({
"Version": "2012-10-17",
"Statement": [{
Action: "s3:*",
Effect: "Allow",
Resource: "arn:${data.aws_partition.current.partition}:s3:::slink-i-*"
}, {
Action: [
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogStreams",
"logs:CreateLogGroup",
"ec2:DescribeAvailabilityZones",
"ec2:DescribeInstances",
"ec2:DescribeTags",
"s3:ListAllMyBuckets",
"cloudformation:DescribeStacks",
"cloudformation:ListStackResources"
],
Effect: "Allow",
Resource: "*"
}]
})
}
resource "aws_security_group" "sg" {
name = "EC2-Security-group"
description = "EC2 Security Group"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [var.admin_ip]
}
egress {
from_port = 0
protocol = "tcp"
to_port = 65535
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
protocol = "udp"
to_port = 65535
cidr_blocks = ["0.0.0.0/0"]
}
}
terraform.tfvars
key_name = "Rob"
region = "us-east-1"
admin_ip = "3.222.237.17/32"
open_s3_permissions = true
This template provisions the following resources:
Virtual Machine
: This server is based on the StorageLink marketplace VMNetwork Security Group
: Allows TCP80
and443
from anywhere, but locks down admin port22
to a single IPPublic IP
: Static IP that retains the IP after a rebootStorage Account
: This contains the Boot Diagnostics of the VM. But it can also be used later on when configuring StorageLink to point to a Storage Account.
There's also another file that contains variables:
terraform.tfvars
Since this file is named terraform.tfvars
, it will be automatically used without having to run:
terraform -var-file terraform.tfvars
You can configure the following variables:
linux_username
: The Username of your Linux admin user.admin_public_key
: Set this to your SSH public key. Alternatively, you can useadmin_password
.admin_password
: Set a password for your admin user. If you do not set this, you must useadmin_public_key
.region
: Specify your current region.admin_ip
: Get your workstation's public IP fromcheckip.dyndns.org
. Append/32
to specify a single IP range.resource_group_name
: Specify the name of the resource group that will be created to house your resources.vm_size
: Optional. Specify the size of your VM. Defaults toStandard_B1ms
.disk_volume_size
: Optional. Specify the size of your disk volume. Defaults to30
GB.
Terraform file contents
slink-single-instance.tf
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
}
}
}
provider "azurerm" {
features {}
}
variable "linux_username" {
type = string
description = "Username of the Linux admin user"
default = "ubuntu"
}
variable "admin_password" {
type = string
description = "Password of the Linux admin user"
default = null
}
variable "admin_public_key" {
type = string
description = "Public SSH key for the Linux admin user"
default = null
}
variable "disk_volume_size" {
type = number
description = "Disk volume size in GB. Must be at least 30."
default = 30
}
variable "resource_group_name" {
type = string
description = "Name of the Resource Group"
}
variable "region" {
type = string
description = "Region in which you deploy resources"
}
variable "admin_ip" {
type = string
description = "Public IP address range for SSH and web access. Use a CIDR range to restrict access. To get your local machine's IP, see http://checkip.dyndns.org/. (Remember to append /32 for a single IP e.g. 12.34.56.78/32) For security reasons, do not use 0.0.0.0/0."
validation {
condition = can(regex("([1-9]\\d{0,2})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/([1-9]\\d{0,1})",var.admin_ip))
error_message = "Must be a valid IP CIDR range in the form of x.x.x.x/x. Do not use 0.0.0.0/0."
}
}
variable "vm_size" {
type = string
description = "VM size. Recommended: Standard_B1ms for testing, Standard_D2_v3 for production."
default = "Standard_B1ms"
}
resource "azurerm_resource_group" "slink-resource-group" {
name = var.resource_group_name
location = var.region
}
resource "azurerm_virtual_network" "slink-vnet" {
name = "slink-vnet"
address_space = ["10.0.0.0/16"]
location = var.region
resource_group_name = azurerm_resource_group.slink-resource-group.name
}
resource "azurerm_subnet" "slink-subnet" {
name = "slink-subnet"
resource_group_name = azurerm_resource_group.slink-resource-group.name
virtual_network_name = azurerm_virtual_network.slink-vnet.name
address_prefixes = ["10.0.2.0/24"]
}
resource "azurerm_public_ip" "slink-ip" {
name = "slink-ip"
location = var.region
resource_group_name = azurerm_resource_group.slink-resource-group.name
allocation_method = "Static"
}
resource "azurerm_network_security_group" "slink-nsg" {
name = "slink-nsg"
location = var.region
resource_group_name = azurerm_resource_group.slink-resource-group.name
}
resource "azurerm_network_security_rule" "slink-ingress-rule-80" {
name = "slink-ingress-rule-80"
priority = 1001
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = "*"
destination_address_prefix = "*"
resource_group_name = azurerm_resource_group.slink-resource-group.name
network_security_group_name = azurerm_network_security_group.slink-nsg.name
}
resource "azurerm_network_security_rule" "slink-ingress-rule-443" {
name = "slink-ingress-rule-443"
priority = 1002
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "*"
destination_address_prefix = "*"
resource_group_name = azurerm_resource_group.slink-resource-group.name
network_security_group_name = azurerm_network_security_group.slink-nsg.name
}
resource "azurerm_network_security_rule" "slink-ingress-rule-22" {
name = "slink-ingress-rule-22"
priority = 1003
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = var.admin_ip
destination_address_prefix = "*"
resource_group_name = azurerm_resource_group.slink-resource-group.name
network_security_group_name = azurerm_network_security_group.slink-nsg.name
}
resource "azurerm_network_interface" "slink-nic" {
name = "slink-nic"
location = var.region
resource_group_name = azurerm_resource_group.slink-resource-group.name
ip_configuration {
name = "NIC-config"
subnet_id = azurerm_subnet.slink-subnet.id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.slink-ip.id
}
}
resource "azurerm_network_interface_security_group_association" "slink-nic-association" {
network_interface_id = azurerm_network_interface.slink-nic.id
network_security_group_id = azurerm_network_security_group.slink-nsg.id
}
resource "random_id" "random_id" {
keepers = {
resource_group = azurerm_resource_group.slink-resource-group.name
}
byte_length = 6
}
resource "azurerm_storage_account" "slink-storage-account" {
name = "slink${random_id.random_id.hex}"
resource_group_name = azurerm_resource_group.slink-resource-group.name
location = var.region
account_replication_type = "LRS"
account_tier = "Standard"
}
resource "azurerm_linux_virtual_machine" "slink-vm" {
name = "slink-vm"
location = var.region
resource_group_name = azurerm_resource_group.slink-resource-group.name
network_interface_ids = [azurerm_network_interface.slink-nic.id]
size = var.vm_size
disable_password_authentication = var.admin_password == null ? true : false
os_disk {
name = "slink-osdisk"
disk_size_gb = var.disk_volume_size
caching = "ReadWrite"
storage_account_type = "Standard_LRS"
}
source_image_reference {
publisher = "thorntechnologiesllc"
offer = "storage-link"
sku = "storagelink"
version = "latest"
}
plan {
name = "storagelink"
publisher = "thorntechnologiesllc"
product = "storage-link"
}
computer_name = "slink-vm"
admin_username = var.linux_username
admin_password = var.admin_password
admin_ssh_key {
username = var.linux_username
public_key = var.admin_public_key
}
boot_diagnostics {
storage_account_uri = azurerm_storage_account.slink-storage-account.primary_blob_endpoint
}
}
output "public_ip_address" {
value = "${azurerm_public_ip.slink-ip.*.ip_address}"
}
terraform.tfvars
admin_public_key = "ssh-rsa AAAAB3NzanNKEbh"
resource_group_name = "terraform-resource-group"
region = "eastus"
admin_ip = "3.222.237.17/32"
This template provisions the following resources:
Virtual Machine
: This server is based on the StorageLink marketplace VMFirewall
: Allows TCP80
and443
from anywhere, but locks down admin port22
to a single IPPublic IP
: Static IP that retains the IP after a reboot
There's also another file that contains variables:
terraform.tfvars
Since this file is named terraform.tfvars
, it will be automatically used without having to run:
terraform -var-file terraform.tfvars
You can configure the following variables:
project
: Specify the project in which to deploy the VMregion
: Specify your current regionzone
: Specify your current zonesource_ranges
: Get your workstation's public IP fromcheckip.dyndns.org
. Append/32
to specify a single IP rangessh-keys
: Set an SSH key to be used to SSH into your VM. Note: the format isubuntu:ssh-rsa AAAAB3NzaC1
, where you specify the username followed by the public key.credentials
: Optional. Specify a json key credentials file to deploy this Terraform template.machine_type
: Optional. Specify the size of your VM. Defaults toe2-medium
.
Terraform file contents
slink-single-instance.tf
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "3.5.0"
}
}
}
provider "google" {
credentials = var.credentials == null ? null : file(var.credentials)
project = var.project
region = var.region
zone = var.zone
}
variable "credentials" {
type = string
description = "Name of your Service Key"
default = null
}
variable "project" {
type = string
description = "Name of your Google Cloud project"
}
variable "region" {
type = string
description = "Name of your Google Cloud region"
default = "us-central1"
}
variable "zone" {
type = string
description = "Name of your region zone"
default = "us-central1-c"
}
variable "machine_type" {
type = string
description = "Machine type of your VM"
default = "e2-medium"
}
variable "ssh-keys" {
type = string
description = "Key used to SSH into your VM"
default = null
}
variable "source_ranges" {
type = string
description = "Source IP range"
}
resource "google_compute_network" "vpc_network" {
name = "terraform-network-${random_id.new.hex}"
}
resource "google_compute_address" "static" {
name = "ipv4-address-${random_id.new.hex}"
}
resource "random_id" "new" {
byte_length = "8"
}
resource "google_compute_instance" "default" {
name = "terraform-instance-${random_id.new.hex}"
machine_type = var.machine_type
zone = var.zone
tags = ["terraform-instance"]
boot_disk {
initialize_params {
image = "thorn-technologies-public/swiftgw-1-1-0-1714428407"
}
}
network_interface {
network = google_compute_network.vpc_network.name
access_config {
nat_ip = google_compute_address.static.address
}
}
metadata = {
ssh-keys = var.ssh-keys
}
}
resource "google_compute_firewall" "default" {
name = "terraform-firewall-${random_id.new.hex}"
network = google_compute_network.vpc_network.name
allow {
protocol = "tcp"
ports = ["22"]
}
target_tags = ["terraform-instance"]
source_ranges = [var.source_ranges]
}
resource "google_compute_firewall" "web-ports" {
name = "terraform-firewall--${random_id.new.hex}"
network = google_compute_network.vpc_network.name
allow {
protocol = "tcp"
ports = ["80", "443"]
}
target_tags = ["terraform-instance"]
source_ranges = ["0.0.0.0/0"]
}
output "public_ip_address" {
value = "${google_compute_address.static.address}"
}
terraform.tfvars
project = "your-google-cloud-project"
region = "us-central1"
zone = "us-central1-c"
ssh-keys = "ubuntu:ssh-rsa AAAAB3NzaC1"
source_ranges = "3.222.237.17/32"