From 9f0d99091f030656a48bebcdfcb2565f9ae109cf Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Mon, 17 Apr 2017 19:07:53 -0500 Subject: [PATCH 01/67] initial commit - 101-vm-from-user-image --- examples/101-vm-from-user-image/.gitignore | 3 +++ examples/101-vm-from-user-image/README.md | 7 +++++++ examples/101-vm-from-user-image/main.tf | 0 examples/101-vm-from-user-image/variables.tf | 0 4 files changed, 10 insertions(+) create mode 100644 examples/101-vm-from-user-image/.gitignore create mode 100644 examples/101-vm-from-user-image/README.md create mode 100644 examples/101-vm-from-user-image/main.tf create mode 100644 examples/101-vm-from-user-image/variables.tf diff --git a/examples/101-vm-from-user-image/.gitignore b/examples/101-vm-from-user-image/.gitignore new file mode 100644 index 000000000000..352c8a2b9b6e --- /dev/null +++ b/examples/101-vm-from-user-image/.gitignore @@ -0,0 +1,3 @@ +terraform.tfstate* +terraform.tfvars* +creds.tf* diff --git a/examples/101-vm-from-user-image/README.md b/examples/101-vm-from-user-image/README.md new file mode 100644 index 000000000000..08f105832396 --- /dev/null +++ b/examples/101-vm-from-user-image/README.md @@ -0,0 +1,7 @@ +# Create a Virtual Machine from a User Image +**Prerequisite - The Storage Account with the User Image VHD should already exist** + +This template allows you to create a Virtual Machines from a User image. This template also deploys a Virtual Network, Public IP addresses and a Network Interface. + +Azure requires that an application is added to Azure Active Directory to generate the client_id, client_secret, and tenant_id needed by Terraform (subscription_id can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this. + diff --git a/examples/101-vm-from-user-image/main.tf b/examples/101-vm-from-user-image/main.tf new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/examples/101-vm-from-user-image/variables.tf b/examples/101-vm-from-user-image/variables.tf new file mode 100644 index 000000000000..e69de29bb2d1 From 6f577a8cad0be9293e47c2670391604db895fa10 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Wed, 19 Apr 2017 17:56:43 -0500 Subject: [PATCH 02/67] changed branch name --- examples/101-vm-from-user-image/.gitignore | 2 +- examples/101-vm-from-user-image/README.md | 2 + examples/101-vm-from-user-image/main.tf | 94 ++++++++++++++++++++ examples/101-vm-from-user-image/variables.tf | 90 +++++++++++++++++++ examples/101-vm-simple-linux/.gitignore | 2 +- 5 files changed, 188 insertions(+), 2 deletions(-) diff --git a/examples/101-vm-from-user-image/.gitignore b/examples/101-vm-from-user-image/.gitignore index 352c8a2b9b6e..a4f6eebd0a98 100644 --- a/examples/101-vm-from-user-image/.gitignore +++ b/examples/101-vm-from-user-image/.gitignore @@ -1,3 +1,3 @@ terraform.tfstate* terraform.tfvars* -creds.tf* +provider.tf* diff --git a/examples/101-vm-from-user-image/README.md b/examples/101-vm-from-user-image/README.md index 08f105832396..c1c5423e5103 100644 --- a/examples/101-vm-from-user-image/README.md +++ b/examples/101-vm-from-user-image/README.md @@ -5,3 +5,5 @@ This template allows you to create a Virtual Machines from a User image. This te Azure requires that an application is added to Azure Active Directory to generate the client_id, client_secret, and tenant_id needed by Terraform (subscription_id can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this. +`image_uri` - Specifies the `image_uri` in the form publisherName:offer:skus:version. `image_uri` can also specify the VHD uri of a custom VM image to clone. +`os_type` - When cloning a custom disk image the `os_type` documented below becomes required. Specifies the operating system Type, valid values are windows, linux. \ No newline at end of file diff --git a/examples/101-vm-from-user-image/main.tf b/examples/101-vm-from-user-image/main.tf index e69de29bb2d1..ce85956cb3e2 100644 --- a/examples/101-vm-from-user-image/main.tf +++ b/examples/101-vm-from-user-image/main.tf @@ -0,0 +1,94 @@ +resource "azurerm_resource_group" "rg" { + name = "${var.resource_group}" + location = "${var.location}" +} + +resource "azurerm_virtual_network" "vnet" { + name = "${var.virtual_network_name}" + location = "${var.location}" + address_space = ["${var.address_space}"] + resource_group_name = "${azurerm_resource_group.rg.name}" +} + +resource "azurerm_subnet" "subnet" { + name = "${var.rg_prefix}subnet" + virtual_network_name = "${azurerm_virtual_network.vnet.name}" + resource_group_name = "${azurerm_resource_group.rg.name}" + address_prefix = "${var.subnet_prefix}" +} + +resource "azurerm_network_interface" "nic" { + name = "${var.rg_prefix}nic" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + + ip_configuration { + name = "${var.rg_prefix}ipconfig" + subnet_id = "${azurerm_subnet.subnet.id}" + private_ip_address_allocation = "Dynamic" + public_ip_address_id = "${azurerm_public_ip.pip.id}" + } +} + +resource "azurerm_public_ip" "pip" { + name = "${var.rg_prefix}-ip" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + public_ip_address_allocation = "dynamic" + domain_name_label = "${var.dns_name}" +} + +resource "azurerm_storage_account" "stor" { + name = "${var.hostname}stor" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + account_type = "${var.storage_account_type}" +} + +resource "azurerm_storage_container" "storc" { + name = "${var.hostname}-vhds" + resource_group_name = "${azurerm_resource_group.rg.name}" + storage_account_name = "${azurerm_storage_account.stor.name}" + container_access_type = "private" +} + +resource "azurerm_virtual_machine" "vm" { + name = "${var.rg_prefix}vm" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + vm_size = "${var.vm_size}" + network_interface_ids = ["${azurerm_network_interface.nic.id}"] + + storage_image_reference { + publisher = "${var.image_publisher}" + offer = "${var.image_offer}" + sku = "${var.image_sku}" + version = "${var.image_version}" + } + + storage_os_disk { + name = "${var.hostname}-osdisk1" + image_uri = "${var.image_uri}" + caching = "ReadWrite" + create_option = "FromImage" + } + + os_profile { + computer_name = "${var.hostname}" + admin_username = "${var.admin_username}" + admin_password = "${var.admin_password}" + } + + boot_diagnostics { + enabled = "true" + storage_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}" + } +} + +output "hostname" { + value = "${var.hostname}" +} + +output "vm_fqdn" { + value = "${azurerm_public_ip.pip.fqdn}" +} \ No newline at end of file diff --git a/examples/101-vm-from-user-image/variables.tf b/examples/101-vm-from-user-image/variables.tf index e69de29bb2d1..f15b77ff0d89 100644 --- a/examples/101-vm-from-user-image/variables.tf +++ b/examples/101-vm-from-user-image/variables.tf @@ -0,0 +1,90 @@ +variable "resource_group" { + description = "The name of the resource group in which to create the virtual network." + default = "myresourcegroup" +} + +variable "image_uri" { + description = "Specifies the image_uri in the form publisherName:offer:skus:version. image_uri can also specify the VHD uri of a custom VM image to clone." + default = "" +} + +variable "os_type" { + description = "Specifies the operating system Type, valid values are windows, linux." + default = "linux" +} + +variable "rg_prefix" { + description = "The shortened abbreviation to represent your resource group that will go on the front of some resources." + default = "rg" +} + +variable "location" { + description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." + default = "southcentralus" +} + +variable "virtual_network_name" { + description = "The name for the virtual network." + default = "vnet" +} + +# UNCERTAIN OF THIS VARIABLE +variable "address_space" { + description = "The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created." + default = "10.0.0.0/16" +} + +# UNCERTAIN OF THIS VARIABLE +variable "subnet_prefix" { + description = "The address prefix to use for the subnet." + default = "10.1.0.0/24" +} + +variable "storage_account_type" { + description = "Specifies the name of the storage account. Changing this forces a new resource to be created. This must be unique across the entire Azure service, not just within the resource group." + default = "Premium_LRS" +} + +variable "vm_size" { + description = "Specifies the name of the virtual machine resource. Changing this forces a new resource to be created." + default = "Standard_DS1_v2" +} + +variable "image_publisher" { + description = "name of the publisher of the image (az vm image list)" + default = "Canonical" +} + +variable "image_offer" { + description = "the name of the offer (az vm image list)" + default = "UbuntuServer" +} + +variable "image_sku" { + description = "image sku to apply (az vm image list)" + default = "12.04.5-LTS" +} + +variable "image_version" { + description = "version of the image to apply (az vm image list)" + default = "latest" +} + +variable "hostname" { + description = "VM name referenced also in storage-related names." + default = "myvm" +} + +variable "dns_name" { + description = " Label for the Domain Name. Will be used to make up the FQDN. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system." +} + +variable "admin_username" { + description = "administrator user name" + default = "vmadmin" +} + +variable "admin_password" { + description = "administrator password (recommended to disable password auth)" + default = "T3rr@f0rmP@ssword" +} diff --git a/examples/101-vm-simple-linux/.gitignore b/examples/101-vm-simple-linux/.gitignore index 352c8a2b9b6e..a4f6eebd0a98 100644 --- a/examples/101-vm-simple-linux/.gitignore +++ b/examples/101-vm-simple-linux/.gitignore @@ -1,3 +1,3 @@ terraform.tfstate* terraform.tfvars* -creds.tf* +provider.tf* From 611d70affe9f89df717c524fbd97d698e74a6cc7 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Fri, 21 Apr 2017 09:03:15 -0500 Subject: [PATCH 03/67] not deploying - storage problems --- examples/101-vm-from-user-image/variables.tf | 90 ------------------- .../.gitignore | 0 .../README.md | 5 +- examples/azure-vm-from-user-image/deploy.sh | 43 +++++++++ .../main.tf | 46 +++------- examples/azure-vm-from-user-image/outputs.tf | 11 +++ .../azure-vm-from-user-image/variables.tf | 54 +++++++++++ 7 files changed, 123 insertions(+), 126 deletions(-) delete mode 100644 examples/101-vm-from-user-image/variables.tf rename examples/{101-vm-from-user-image => azure-vm-from-user-image}/.gitignore (100%) rename examples/{101-vm-from-user-image => azure-vm-from-user-image}/README.md (75%) create mode 100644 examples/azure-vm-from-user-image/deploy.sh rename examples/{101-vm-from-user-image => azure-vm-from-user-image}/main.tf (61%) create mode 100644 examples/azure-vm-from-user-image/outputs.tf create mode 100644 examples/azure-vm-from-user-image/variables.tf diff --git a/examples/101-vm-from-user-image/variables.tf b/examples/101-vm-from-user-image/variables.tf deleted file mode 100644 index f15b77ff0d89..000000000000 --- a/examples/101-vm-from-user-image/variables.tf +++ /dev/null @@ -1,90 +0,0 @@ -variable "resource_group" { - description = "The name of the resource group in which to create the virtual network." - default = "myresourcegroup" -} - -variable "image_uri" { - description = "Specifies the image_uri in the form publisherName:offer:skus:version. image_uri can also specify the VHD uri of a custom VM image to clone." - default = "" -} - -variable "os_type" { - description = "Specifies the operating system Type, valid values are windows, linux." - default = "linux" -} - -variable "rg_prefix" { - description = "The shortened abbreviation to represent your resource group that will go on the front of some resources." - default = "rg" -} - -variable "location" { - description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." - default = "southcentralus" -} - -variable "virtual_network_name" { - description = "The name for the virtual network." - default = "vnet" -} - -# UNCERTAIN OF THIS VARIABLE -variable "address_space" { - description = "The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created." - default = "10.0.0.0/16" -} - -# UNCERTAIN OF THIS VARIABLE -variable "subnet_prefix" { - description = "The address prefix to use for the subnet." - default = "10.1.0.0/24" -} - -variable "storage_account_type" { - description = "Specifies the name of the storage account. Changing this forces a new resource to be created. This must be unique across the entire Azure service, not just within the resource group." - default = "Premium_LRS" -} - -variable "vm_size" { - description = "Specifies the name of the virtual machine resource. Changing this forces a new resource to be created." - default = "Standard_DS1_v2" -} - -variable "image_publisher" { - description = "name of the publisher of the image (az vm image list)" - default = "Canonical" -} - -variable "image_offer" { - description = "the name of the offer (az vm image list)" - default = "UbuntuServer" -} - -variable "image_sku" { - description = "image sku to apply (az vm image list)" - default = "12.04.5-LTS" -} - -variable "image_version" { - description = "version of the image to apply (az vm image list)" - default = "latest" -} - -variable "hostname" { - description = "VM name referenced also in storage-related names." - default = "myvm" -} - -variable "dns_name" { - description = " Label for the Domain Name. Will be used to make up the FQDN. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system." -} - -variable "admin_username" { - description = "administrator user name" - default = "vmadmin" -} - -variable "admin_password" { - description = "administrator password (recommended to disable password auth)" - default = "T3rr@f0rmP@ssword" -} diff --git a/examples/101-vm-from-user-image/.gitignore b/examples/azure-vm-from-user-image/.gitignore similarity index 100% rename from examples/101-vm-from-user-image/.gitignore rename to examples/azure-vm-from-user-image/.gitignore diff --git a/examples/101-vm-from-user-image/README.md b/examples/azure-vm-from-user-image/README.md similarity index 75% rename from examples/101-vm-from-user-image/README.md rename to examples/azure-vm-from-user-image/README.md index c1c5423e5103..b115f2451032 100644 --- a/examples/101-vm-from-user-image/README.md +++ b/examples/azure-vm-from-user-image/README.md @@ -6,4 +6,7 @@ This template allows you to create a Virtual Machines from a User image. This te Azure requires that an application is added to Azure Active Directory to generate the client_id, client_secret, and tenant_id needed by Terraform (subscription_id can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this. `image_uri` - Specifies the `image_uri` in the form publisherName:offer:skus:version. `image_uri` can also specify the VHD uri of a custom VM image to clone. -`os_type` - When cloning a custom disk image the `os_type` documented below becomes required. Specifies the operating system Type, valid values are windows, linux. \ No newline at end of file +`os_type` - When cloning a custom disk image the `os_type` documented below becomes required. Specifies the operating system Type, valid values are windows, linux. + +**Storage Accounts can be imported using the resource id, e.g.** +`terraform import azurerm_storage_account.storageAcc1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myresourcegroup/providers/Microsoft.Storage/storageAccounts/myaccount` \ No newline at end of file diff --git a/examples/azure-vm-from-user-image/deploy.sh b/examples/azure-vm-from-user-image/deploy.sh new file mode 100644 index 000000000000..3160ca88ed43 --- /dev/null +++ b/examples/azure-vm-from-user-image/deploy.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +set -o errexit -o nounset + +# generate a unique string for CI deployment +KEY=$(cat /dev/urandom | tr -cd 'a-z' | head -c 12) +PASSWORD=KEY +PASSWORD+=$(cat /dev/urandom | tr -cd 'A-Z' | head -c 2) +PASSWORD+=$(cat /dev/urandom | tr -cd '0-9' | head -c 2) + +terraform get + +terraform plan \ + -var 'dns_name='$KEY \ + -var 'admin_password='$PASSWORD \ + -var 'admin_username='$KEY \ + -var 'resource_group='$KEY \ + -out=out.tfplan + +terraform apply out.tfplan + + +# TODO: determine external validation, possibly Azure CLI + +# echo "Setting git user name" +# git config user.name $GH_USER_NAME +# +# echo "Setting git user email" +# git config user.email $GH_USER_EMAIL +# +# echo "Adding git upstream remote" +# git remote add upstream "https://$GH_TOKEN@github.com/$GH_REPO.git" +# +# git checkout master + + +# +# NOW=$(TZ=America/Chicago date) +# +# git commit -m "tfstate: $NOW [ci skip]" +# +# echo "Pushing changes to upstream master" +# git push upstream master \ No newline at end of file diff --git a/examples/101-vm-from-user-image/main.tf b/examples/azure-vm-from-user-image/main.tf similarity index 61% rename from examples/101-vm-from-user-image/main.tf rename to examples/azure-vm-from-user-image/main.tf index ce85956cb3e2..44464b5cd55b 100644 --- a/examples/101-vm-from-user-image/main.tf +++ b/examples/azure-vm-from-user-image/main.tf @@ -4,26 +4,26 @@ resource "azurerm_resource_group" "rg" { } resource "azurerm_virtual_network" "vnet" { - name = "${var.virtual_network_name}" + name = "${var.hostname}vnet" location = "${var.location}" address_space = ["${var.address_space}"] resource_group_name = "${azurerm_resource_group.rg.name}" } resource "azurerm_subnet" "subnet" { - name = "${var.rg_prefix}subnet" + name = "${var.hostname}subnet" virtual_network_name = "${azurerm_virtual_network.vnet.name}" resource_group_name = "${azurerm_resource_group.rg.name}" address_prefix = "${var.subnet_prefix}" } resource "azurerm_network_interface" "nic" { - name = "${var.rg_prefix}nic" + name = "${var.hostname}nic" location = "${var.location}" resource_group_name = "${azurerm_resource_group.rg.name}" ip_configuration { - name = "${var.rg_prefix}ipconfig" + name = "${var.hostname}ipconfig" subnet_id = "${azurerm_subnet.subnet.id}" private_ip_address_allocation = "Dynamic" public_ip_address_id = "${azurerm_public_ip.pip.id}" @@ -31,44 +31,32 @@ resource "azurerm_network_interface" "nic" { } resource "azurerm_public_ip" "pip" { - name = "${var.rg_prefix}-ip" + name = "${var.hostname}-ip" location = "${var.location}" resource_group_name = "${azurerm_resource_group.rg.name}" public_ip_address_allocation = "dynamic" - domain_name_label = "${var.dns_name}" + domain_name_label = "${var.hostname}" } resource "azurerm_storage_account" "stor" { - name = "${var.hostname}stor" + name = "${var.storage_account_name}" location = "${var.location}" resource_group_name = "${azurerm_resource_group.rg.name}" account_type = "${var.storage_account_type}" } -resource "azurerm_storage_container" "storc" { - name = "${var.hostname}-vhds" - resource_group_name = "${azurerm_resource_group.rg.name}" - storage_account_name = "${azurerm_storage_account.stor.name}" - container_access_type = "private" -} - resource "azurerm_virtual_machine" "vm" { - name = "${var.rg_prefix}vm" + name = "${var.hostname}" location = "${var.location}" resource_group_name = "${azurerm_resource_group.rg.name}" vm_size = "${var.vm_size}" network_interface_ids = ["${azurerm_network_interface.nic.id}"] - storage_image_reference { - publisher = "${var.image_publisher}" - offer = "${var.image_offer}" - sku = "${var.image_sku}" - version = "${var.image_version}" - } - storage_os_disk { name = "${var.hostname}-osdisk1" - image_uri = "${var.image_uri}" + image_uri = "${var.image_uri}" + vhd_uri = "" + os_type = "${var.os_type}" caching = "ReadWrite" create_option = "FromImage" } @@ -79,16 +67,4 @@ resource "azurerm_virtual_machine" "vm" { admin_password = "${var.admin_password}" } - boot_diagnostics { - enabled = "true" - storage_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}" - } -} - -output "hostname" { - value = "${var.hostname}" } - -output "vm_fqdn" { - value = "${azurerm_public_ip.pip.fqdn}" -} \ No newline at end of file diff --git a/examples/azure-vm-from-user-image/outputs.tf b/examples/azure-vm-from-user-image/outputs.tf new file mode 100644 index 000000000000..125017a6f3f1 --- /dev/null +++ b/examples/azure-vm-from-user-image/outputs.tf @@ -0,0 +1,11 @@ +output "hostname" { + value = "${var.hostname}" +} + +output "vm_fqdn" { + value = "${azurerm_public_ip.pip.fqdn}" +} + +output "sshCommand" { + value = "${concat("ssh ", var.admin_username, "@", azurerm_public_ip.pip.fqdn)}" +} \ No newline at end of file diff --git a/examples/azure-vm-from-user-image/variables.tf b/examples/azure-vm-from-user-image/variables.tf new file mode 100644 index 000000000000..2c10c9401875 --- /dev/null +++ b/examples/azure-vm-from-user-image/variables.tf @@ -0,0 +1,54 @@ +variable "resource_group" { + description = "The name of the resource group in which the image to clone resides." +} + +variable "image_uri" { + description = "Specifies the image_uri in the form publisherName:offer:skus:version. image_uri can also specify the VHD uri of a custom VM image to clone." +} + +variable "os_type" { + description = "Specifies the operating system Type, valid values are windows, linux." + default = "linux" +} + +variable "location" { + description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." + default = "southcentralus" +} + +variable "address_space" { + description = "The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created." + default = "10.0.0.0/16" +} + +variable "subnet_prefix" { + description = "The address prefix to use for the subnet." + default = "10.0.0.0/24" +} + +variable "storage_account_name" { + description = "The name of the storage account in which the image from which you are cloning resides." +} + +variable "storage_account_type" { + description = "Defines the type of storage account to be created. Valid options are Standard_LRS, Standard_ZRS, Standard_GRS, Standard_RAGRS, Premium_LRS. Changing this is sometimes valid - see the Azure documentation for more information on which types of accounts can be converted into other types." + default = "Standard_LRS" +} + +variable "vm_size" { + description = "Specifies the size of the virtual machine. This must be the same as the vm image from which you are copying." +} + +variable "hostname" { + description = "VM name referenced also in storage-related names. This is also used as the label for the Domain Name and to make up the FQDN. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system." +} + +variable "admin_username" { + description = "administrator user name" + default = "vmadmin" +} + +variable "admin_password" { + description = "administrator password (recommended to disable password auth)" + default = "T3rr@f0rmP@ssword" +} From fdff23aeec4aa94ff3481f05ef49430cddcca300 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Fri, 21 Apr 2017 10:34:07 -0500 Subject: [PATCH 04/67] provisions vm but image not properly prepared --- examples/azure-vm-from-user-image/main.tf | 2 +- examples/azure-vm-from-user-image/variables.tf | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/azure-vm-from-user-image/main.tf b/examples/azure-vm-from-user-image/main.tf index 44464b5cd55b..686ea83bac85 100644 --- a/examples/azure-vm-from-user-image/main.tf +++ b/examples/azure-vm-from-user-image/main.tf @@ -55,7 +55,7 @@ resource "azurerm_virtual_machine" "vm" { storage_os_disk { name = "${var.hostname}-osdisk1" image_uri = "${var.image_uri}" - vhd_uri = "" + vhd_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}vhds/myosdisk1.vhd" os_type = "${var.os_type}" caching = "ReadWrite" create_option = "FromImage" diff --git a/examples/azure-vm-from-user-image/variables.tf b/examples/azure-vm-from-user-image/variables.tf index 2c10c9401875..f25a42c66d6e 100644 --- a/examples/azure-vm-from-user-image/variables.tf +++ b/examples/azure-vm-from-user-image/variables.tf @@ -4,6 +4,7 @@ variable "resource_group" { variable "image_uri" { description = "Specifies the image_uri in the form publisherName:offer:skus:version. image_uri can also specify the VHD uri of a custom VM image to clone." + default = "https://myvmstor.blob.core.windows.net/vhds/armvm5yt3s3judzp66osDisk.vhd" } variable "os_type" { From 0dac5ecfa1864844390e1c0418407ce761a10fd4 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Fri, 21 Apr 2017 18:32:12 -0500 Subject: [PATCH 05/67] storage not correct --- examples/azure-vm-from-user-image/README.md | 2 +- examples/azure-vm-from-user-image/main.tf | 2 +- examples/azure-vm-from-user-image/variables.tf | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/azure-vm-from-user-image/README.md b/examples/azure-vm-from-user-image/README.md index b115f2451032..e64165a0455f 100644 --- a/examples/azure-vm-from-user-image/README.md +++ b/examples/azure-vm-from-user-image/README.md @@ -1,4 +1,4 @@ -# Create a Virtual Machine from a User Image +# [Create a Virtual Machine from a User Image](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/cli-deploy-templates#create-a-custom-vm-image) **Prerequisite - The Storage Account with the User Image VHD should already exist** This template allows you to create a Virtual Machines from a User image. This template also deploys a Virtual Network, Public IP addresses and a Network Interface. diff --git a/examples/azure-vm-from-user-image/main.tf b/examples/azure-vm-from-user-image/main.tf index 686ea83bac85..184873130a5f 100644 --- a/examples/azure-vm-from-user-image/main.tf +++ b/examples/azure-vm-from-user-image/main.tf @@ -55,7 +55,7 @@ resource "azurerm_virtual_machine" "vm" { storage_os_disk { name = "${var.hostname}-osdisk1" image_uri = "${var.image_uri}" - vhd_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}vhds/myosdisk1.vhd" + vhd_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}vhds/${var.hostname}osdisk.vhd" os_type = "${var.os_type}" caching = "ReadWrite" create_option = "FromImage" diff --git a/examples/azure-vm-from-user-image/variables.tf b/examples/azure-vm-from-user-image/variables.tf index f25a42c66d6e..57fc4f6fefba 100644 --- a/examples/azure-vm-from-user-image/variables.tf +++ b/examples/azure-vm-from-user-image/variables.tf @@ -4,7 +4,7 @@ variable "resource_group" { variable "image_uri" { description = "Specifies the image_uri in the form publisherName:offer:skus:version. image_uri can also specify the VHD uri of a custom VM image to clone." - default = "https://myvmstor.blob.core.windows.net/vhds/armvm5yt3s3judzp66osDisk.vhd" + default = "https://myrgdisks276.blob.core.windows.net/vhds/originalvm20170421170101.vhd" } variable "os_type" { @@ -29,6 +29,7 @@ variable "subnet_prefix" { variable "storage_account_name" { description = "The name of the storage account in which the image from which you are cloning resides." + default = "myrgsisks276" } variable "storage_account_type" { @@ -38,6 +39,7 @@ variable "storage_account_type" { variable "vm_size" { description = "Specifies the size of the virtual machine. This must be the same as the vm image from which you are copying." + default = "Standard_DS1_v2" } variable "hostname" { From 81e2559ead58b1153ca3253474907db91ec3a0c4 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Sat, 22 Apr 2017 10:26:28 -0500 Subject: [PATCH 06/67] provisions properly --- examples/azure-vm-from-user-image/main.tf | 9 +-------- examples/azure-vm-from-user-image/variables.tf | 5 +++++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/examples/azure-vm-from-user-image/main.tf b/examples/azure-vm-from-user-image/main.tf index 184873130a5f..6bae761c1774 100644 --- a/examples/azure-vm-from-user-image/main.tf +++ b/examples/azure-vm-from-user-image/main.tf @@ -38,13 +38,6 @@ resource "azurerm_public_ip" "pip" { domain_name_label = "${var.hostname}" } -resource "azurerm_storage_account" "stor" { - name = "${var.storage_account_name}" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - account_type = "${var.storage_account_type}" -} - resource "azurerm_virtual_machine" "vm" { name = "${var.hostname}" location = "${var.location}" @@ -55,7 +48,7 @@ resource "azurerm_virtual_machine" "vm" { storage_os_disk { name = "${var.hostname}-osdisk1" image_uri = "${var.image_uri}" - vhd_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}vhds/${var.hostname}osdisk.vhd" + vhd_uri = "${var.primary_blob_endpoint}vhds/${var.hostname}osdisk.vhd" os_type = "${var.os_type}" caching = "ReadWrite" create_option = "FromImage" diff --git a/examples/azure-vm-from-user-image/variables.tf b/examples/azure-vm-from-user-image/variables.tf index 57fc4f6fefba..c09ad5ec15d0 100644 --- a/examples/azure-vm-from-user-image/variables.tf +++ b/examples/azure-vm-from-user-image/variables.tf @@ -7,6 +7,11 @@ variable "image_uri" { default = "https://myrgdisks276.blob.core.windows.net/vhds/originalvm20170421170101.vhd" } +variable "primary_blob_endpoint" { + description = "" + default = "https://myrgdisks276.blob.core.windows.net/" +} + variable "os_type" { description = "Specifies the operating system Type, valid values are windows, linux." default = "linux" From 7dc8150043b6c0c67a3976c6a22222eb44ea9009 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Sat, 22 Apr 2017 11:11:25 -0500 Subject: [PATCH 07/67] changed main.tf to azuredeploy.tf --- examples/azure-vm-from-user-image/README.md | 3 --- examples/azure-vm-from-user-image/{main.tf => azuredeploy.tf} | 0 2 files changed, 3 deletions(-) rename examples/azure-vm-from-user-image/{main.tf => azuredeploy.tf} (100%) diff --git a/examples/azure-vm-from-user-image/README.md b/examples/azure-vm-from-user-image/README.md index e64165a0455f..41d5164c6cbe 100644 --- a/examples/azure-vm-from-user-image/README.md +++ b/examples/azure-vm-from-user-image/README.md @@ -7,6 +7,3 @@ Azure requires that an application is added to Azure Active Directory to generat `image_uri` - Specifies the `image_uri` in the form publisherName:offer:skus:version. `image_uri` can also specify the VHD uri of a custom VM image to clone. `os_type` - When cloning a custom disk image the `os_type` documented below becomes required. Specifies the operating system Type, valid values are windows, linux. - -**Storage Accounts can be imported using the resource id, e.g.** -`terraform import azurerm_storage_account.storageAcc1 /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myresourcegroup/providers/Microsoft.Storage/storageAccounts/myaccount` \ No newline at end of file diff --git a/examples/azure-vm-from-user-image/main.tf b/examples/azure-vm-from-user-image/azuredeploy.tf similarity index 100% rename from examples/azure-vm-from-user-image/main.tf rename to examples/azure-vm-from-user-image/azuredeploy.tf From 5c079fa1d3e73c7c3c830d795c5b6c63e48023a8 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Sat, 22 Apr 2017 12:37:05 -0500 Subject: [PATCH 08/67] added tfvars and info for README --- examples/azure-vm-from-user-image/.gitignore | 4 +- examples/azure-vm-from-user-image/README.md | 93 ++++++++++++++++++- .../azure-vm-from-user-image/azuredeploy.tf | 9 +- examples/azure-vm-from-user-image/outputs.tf | 2 +- .../provider.tf.example | 6 ++ .../terraform.tfvars.example | 13 +++ .../azure-vm-from-user-image/terraform.tvars | 0 .../azure-vm-from-user-image/variables.tf | 15 --- 8 files changed, 114 insertions(+), 28 deletions(-) create mode 100644 examples/azure-vm-from-user-image/provider.tf.example create mode 100644 examples/azure-vm-from-user-image/terraform.tfvars.example create mode 100644 examples/azure-vm-from-user-image/terraform.tvars diff --git a/examples/azure-vm-from-user-image/.gitignore b/examples/azure-vm-from-user-image/.gitignore index a4f6eebd0a98..7773fa9ecc5d 100644 --- a/examples/azure-vm-from-user-image/.gitignore +++ b/examples/azure-vm-from-user-image/.gitignore @@ -1,3 +1,3 @@ terraform.tfstate* -terraform.tfvars* -provider.tf* +terraform.tfvars +provider.tf diff --git a/examples/azure-vm-from-user-image/README.md b/examples/azure-vm-from-user-image/README.md index 41d5164c6cbe..f391b0b8f40f 100644 --- a/examples/azure-vm-from-user-image/README.md +++ b/examples/azure-vm-from-user-image/README.md @@ -1,9 +1,92 @@ # [Create a Virtual Machine from a User Image](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/cli-deploy-templates#create-a-custom-vm-image) -**Prerequisite - The Storage Account with the User Image VHD should already exist** -This template allows you to create a Virtual Machines from a User image. This template also deploys a Virtual Network, Public IP addresses and a Network Interface. + + + + + + -Azure requires that an application is added to Azure Active Directory to generate the client_id, client_secret, and tenant_id needed by Terraform (subscription_id can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this. +> Prerequisite - The generalized image VHD should exist, as well as a Storage Account for boot diagnostics -`image_uri` - Specifies the `image_uri` in the form publisherName:offer:skus:version. `image_uri` can also specify the VHD uri of a custom VM image to clone. -`os_type` - When cloning a custom disk image the `os_type` documented below becomes required. Specifies the operating system Type, valid values are windows, linux. +This template allows you to create a Virtual Machine from an unmanaged User image vhd. This template also deploys a Virtual Network, Public IP addresses and a Network Interface. + +If you are looking to accomplish the above scenario through PowerShell instead of a template, you can use a PowerShell script like below + +##### Variables + ## Global + $rgName = "testrg" + $location = "westus" + + ## Storage + $storageName = "teststore" + $storageType = "Standard_GRS" + + ## Network + $nicname = "testnic" + $subnet1Name = "subnet1" + $vnetName = "testnet" + $vnetAddressPrefix = "10.0.0.0/16" + $vnetSubnetAddressPrefix = "10.0.0.0/24" + + ## Compute + $vmName = "testvm" + $computerName = "testcomputer" + $vmSize = "Standard_A2" + $osDiskName = $vmName + "osDisk" + +##### Resource Group + New-AzureRmResourceGroup -Name $rgName -Location $location + +##### Storage + $storageacc = New-AzureRmStorageAccount -ResourceGroupName $rgName -Name $storageName -Type $storageType -Location $location + +##### Network + $pip = New-AzureRmPublicIpAddress -Name $nicname -ResourceGroupName $rgName -Location $location -AllocationMethod Dynamic + $subnetconfig = New-AzureRmVirtualNetworkSubnetConfig -Name $subnet1Name -AddressPrefix $vnetSubnetAddressPrefix + $vnet = New-AzureRmVirtualNetwork -Name $vnetName -ResourceGroupName $rgName -Location $location -AddressPrefix $vnetAddressPrefix -Subnet $subnetconfig + $nic = New-AzureRmNetworkInterface -Name $nicname -ResourceGroupName $rgName -Location $location -SubnetId $vnet.Subnets[0].Id -PublicIpAddressId $pip.Id + +##### Compute + ## Setup local VM object + $cred = Get-Credential + $vm = New-AzureRmVMConfig -VMName $vmName -VMSize $vmSize + $vm = Set-AzureRmVMOperatingSystem -VM $vm -Windows -ComputerName $computerName -Credential $cred -ProvisionVMAgent -EnableAutoUpdate + + $vm = Add-AzureRmVMNetworkInterface -VM $vm -Id $nic.Id + + $osDiskUri = "http://test.blob.core.windows.net/vmcontainer10798c80-131-1231-a94a-f9d2a712251f/osDisk.10798c80-2919-4100-a94a-f9d2a712251f.vhd" + $imageUri = "http://test.blob.core.windows.net/system/Microsoft.Compute/Images/captured/image-osDisk.8b021d87-913c-4f94-a01a-944ad92d7388.vhd" + $vm = Set-AzureRmVMOSDisk -VM $vm -Name $osDiskName -VhdUri $osDiskUri -CreateOption fromImage -SourceImageUri $imageUri -Windows + + $dataImageUri = "http://test.blob.core.windows.net/system/Microsoft.Compute/Images/captured/image-dataDisk-0.8b021d87-913c-4f94-a01a-944ad92d7388.vhd" + $dataDiskUri = "http://test.blob.core.windows.net/vmcontainer10798c80-sa11-41sa-dsad-f9d2a712251f/dataDisk-0.10798c80-2919-4100-a94a-f9d2a712251f.vhd" + $vm = Add-AzureRmVMDataDisk -VM $vm -Name "dd1" -VhdUri $dataDiskUri -SourceImageUri $dataImageUri -Lun 0 -CreateOption fromImage + + ## Create the VM in Azure + New-AzureRmVM -ResourceGroupName $rgName -Location $location -VM $vm -Verbose + + +## azuredeploy.tf +The `azuredeploy.tf` file contains the actual resources that will be deployed. It also contains the Azure Resource Group definition and any defined variables. + +## outputs.tf +This data is outputted when `terraform apply` is called, and can be queried using the `terraform output` command. + +## provider.tf +Azure requires that an application is added to Azure Active Directory to generate the `client_id`, `client_secret`, and `tenant_id` needed by Terraform (`subscription_id` can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this to populate your `provider.tf` file. + +## terraform.tfvars +If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. + +## variables.tf +The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. + +## .gitignore +If you are committing this template to source control, please insure that the following files are added to your `.gitignore` file. + +``` +terraform.tfstate* +terraform.tfvars* +provider.tf* +``` diff --git a/examples/azure-vm-from-user-image/azuredeploy.tf b/examples/azure-vm-from-user-image/azuredeploy.tf index 6bae761c1774..28edf99204b5 100644 --- a/examples/azure-vm-from-user-image/azuredeploy.tf +++ b/examples/azure-vm-from-user-image/azuredeploy.tf @@ -18,9 +18,9 @@ resource "azurerm_subnet" "subnet" { } resource "azurerm_network_interface" "nic" { - name = "${var.hostname}nic" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" + name = "${var.hostname}nic" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" ip_configuration { name = "${var.hostname}ipconfig" @@ -48,7 +48,7 @@ resource "azurerm_virtual_machine" "vm" { storage_os_disk { name = "${var.hostname}-osdisk1" image_uri = "${var.image_uri}" - vhd_uri = "${var.primary_blob_endpoint}vhds/${var.hostname}osdisk.vhd" + vhd_uri = "https://${var.storage_account_name}.blob.core.windows.net/vhds/${var.hostname}osdisk.vhd" os_type = "${var.os_type}" caching = "ReadWrite" create_option = "FromImage" @@ -59,5 +59,4 @@ resource "azurerm_virtual_machine" "vm" { admin_username = "${var.admin_username}" admin_password = "${var.admin_password}" } - } diff --git a/examples/azure-vm-from-user-image/outputs.tf b/examples/azure-vm-from-user-image/outputs.tf index 125017a6f3f1..e0e255a9e01c 100644 --- a/examples/azure-vm-from-user-image/outputs.tf +++ b/examples/azure-vm-from-user-image/outputs.tf @@ -8,4 +8,4 @@ output "vm_fqdn" { output "sshCommand" { value = "${concat("ssh ", var.admin_username, "@", azurerm_public_ip.pip.fqdn)}" -} \ No newline at end of file +} diff --git a/examples/azure-vm-from-user-image/provider.tf.example b/examples/azure-vm-from-user-image/provider.tf.example new file mode 100644 index 000000000000..a0b5e46772f5 --- /dev/null +++ b/examples/azure-vm-from-user-image/provider.tf.example @@ -0,0 +1,6 @@ +provider "azurerm" { + subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" + client_id = "REPLACE-WITH-YOUR-CLIENT-ID" + client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" + tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" +} diff --git a/examples/azure-vm-from-user-image/terraform.tfvars.example b/examples/azure-vm-from-user-image/terraform.tfvars.example new file mode 100644 index 000000000000..6cc72c9218be --- /dev/null +++ b/examples/azure-vm-from-user-image/terraform.tfvars.example @@ -0,0 +1,13 @@ +resource_group = "myresourcegroup" +image_uri = "https://DISK.blob.core.windows.net/vhds/ORIGINAL-VM.vhd" +primary_blob_endpoint = "https://DISK.blob.core.windows.net/" +location = "southcentralus" +os_type = "linux" +address_space = "10.0.0.0/16" +subnet_prefix = "10.0.0.0/24" +storage_account_name = "STOR-ACCT-NAME" +storage_account_type = "Standard_LRS" +vm_size = "Standard_DS1_v2" +hostname = "HOSTNAME" +admin_username = "vmadmin" +admin_password = "T3rr@f0rmP@ssword" \ No newline at end of file diff --git a/examples/azure-vm-from-user-image/terraform.tvars b/examples/azure-vm-from-user-image/terraform.tvars new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/examples/azure-vm-from-user-image/variables.tf b/examples/azure-vm-from-user-image/variables.tf index c09ad5ec15d0..8bf04524f4fd 100644 --- a/examples/azure-vm-from-user-image/variables.tf +++ b/examples/azure-vm-from-user-image/variables.tf @@ -4,47 +4,34 @@ variable "resource_group" { variable "image_uri" { description = "Specifies the image_uri in the form publisherName:offer:skus:version. image_uri can also specify the VHD uri of a custom VM image to clone." - default = "https://myrgdisks276.blob.core.windows.net/vhds/originalvm20170421170101.vhd" -} - -variable "primary_blob_endpoint" { - description = "" - default = "https://myrgdisks276.blob.core.windows.net/" } variable "os_type" { description = "Specifies the operating system Type, valid values are windows, linux." - default = "linux" } variable "location" { description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." - default = "southcentralus" } variable "address_space" { description = "The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created." - default = "10.0.0.0/16" } variable "subnet_prefix" { description = "The address prefix to use for the subnet." - default = "10.0.0.0/24" } variable "storage_account_name" { description = "The name of the storage account in which the image from which you are cloning resides." - default = "myrgsisks276" } variable "storage_account_type" { description = "Defines the type of storage account to be created. Valid options are Standard_LRS, Standard_ZRS, Standard_GRS, Standard_RAGRS, Premium_LRS. Changing this is sometimes valid - see the Azure documentation for more information on which types of accounts can be converted into other types." - default = "Standard_LRS" } variable "vm_size" { description = "Specifies the size of the virtual machine. This must be the same as the vm image from which you are copying." - default = "Standard_DS1_v2" } variable "hostname" { @@ -53,10 +40,8 @@ variable "hostname" { variable "admin_username" { description = "administrator user name" - default = "vmadmin" } variable "admin_password" { description = "administrator password (recommended to disable password auth)" - default = "T3rr@f0rmP@ssword" } From c86ef6fce79d3dec6ad1614b5fd9c031d5950367 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Sat, 22 Apr 2017 12:43:36 -0500 Subject: [PATCH 09/67] tfvars ignored and corrected file ext --- examples/azure-vm-from-user-image/terraform.tvars | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 examples/azure-vm-from-user-image/terraform.tvars diff --git a/examples/azure-vm-from-user-image/terraform.tvars b/examples/azure-vm-from-user-image/terraform.tvars deleted file mode 100644 index e69de29bb2d1..000000000000 From 534b23634db9774837055825ca23458934f2d3b1 Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Mon, 24 Apr 2017 10:11:10 -0500 Subject: [PATCH 10/67] added CI config; added sane defaults for variables; updated deployment script, added mac specific deployment for local testing --- .travis.yml | 78 +++++++++---------- .../azure-vm-from-user-image/deploy.mac.sh | 17 ++++ examples/azure-vm-from-user-image/deploy.sh | 31 ++++---- .../azure-vm-from-user-image/variables.tf | 7 ++ 4 files changed, 76 insertions(+), 57 deletions(-) create mode 100644 examples/azure-vm-from-user-image/deploy.mac.sh diff --git a/.travis.yml b/.travis.yml index 04cc6f30960b..496f541775a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,42 +1,42 @@ -dist: trusty -sudo: false -language: go -go: -- 1.8 - -# add TF_CONSUL_TEST=1 to run consul tests -# they were causing timouts in travis +sudo: required + +services: + - docker + +language: generic + +# on branches: ignore multiple commits that will queue build jobs, just run latest commit +git: + depth: 1 + +# establish environment variables env: - - CONSUL_VERSION=0.7.5 GOMAXPROCS=4 - -# Fetch consul for the backend and provider tests -before_install: - - curl -sLo consul.zip https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip - - unzip consul.zip - - mkdir ~/bin - - mv consul ~/bin - - export PATH="~/bin:$PATH" - -install: -# This script is used by the Travis build to install a cookie for -# go.googlesource.com so rate limits are higher when using `go get` to fetch -# packages that live there. -# See: https://github.com/golang/go/issues/12933 -- bash scripts/gogetcookie.sh -- go get github.com/kardianos/govendor -script: -- make vet vendor-status test -- GOOS=windows go build + - TEST_DIR=examples/azure-vm-from-user-image + branches: only: - - master -notifications: - irc: - channels: - - irc.freenode.org#terraform-tool - skip_join: true - use_notice: true -matrix: - fast_finish: true - allow_failures: - - go: tip + - /^(?i:topic)-.*$/ + +# install terraform +before_deploy: + - export KEY=$(cat /dev/urandom | tr -cd 'a-z' | head -c 12) + - export PASSWORD=$KEY$(cat /dev/urandom | tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | tr -cd '0-9' | head -c 2) + - export IMAGE_URI='https://DISK.blob.core.windows.net/vhds/ORIGINAL-VM.vhd' + - export PRIMARY_BLOB_ENDPOINT='https://DISK.blob.core.windows.net/' + + +# terraform deploy script +deploy: + - provider: script + skip_cleanup: true + script: cd $TEST_DIR && ./deploy.sh + on: + repo: 10thmagnitude/terraform + branch: topic-101-vm-from-user-image + +# destroy resources with Azure CLI +after_deploy: + - docker run --rm -it \ + azuresdk/azure-cli-python \ + sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ + az group delete -y -n $KEY" diff --git a/examples/azure-vm-from-user-image/deploy.mac.sh b/examples/azure-vm-from-user-image/deploy.mac.sh new file mode 100644 index 000000000000..6bb89bca9249 --- /dev/null +++ b/examples/azure-vm-from-user-image/deploy.mac.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -o errexit -o nounset + +# generate a unique string for CI deployment +export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) +export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) +export IMAGE_URI='https://DISK.blob.core.windows.net/vhds/ORIGINAL-VM.vhd' +export PRIMARY_BLOB_ENDPOINT='https://DISK.blob.core.windows.net/' + + +/bin/sh ./deploy.sh + +# docker run --rm -it \ +# azuresdk/azure-cli-python \ +# sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ +# az group delete -y -n $KEY" diff --git a/examples/azure-vm-from-user-image/deploy.sh b/examples/azure-vm-from-user-image/deploy.sh index 3160ca88ed43..1e8c75d4d77e 100644 --- a/examples/azure-vm-from-user-image/deploy.sh +++ b/examples/azure-vm-from-user-image/deploy.sh @@ -2,23 +2,18 @@ set -o errexit -o nounset -# generate a unique string for CI deployment -KEY=$(cat /dev/urandom | tr -cd 'a-z' | head -c 12) -PASSWORD=KEY -PASSWORD+=$(cat /dev/urandom | tr -cd 'A-Z' | head -c 2) -PASSWORD+=$(cat /dev/urandom | tr -cd '0-9' | head -c 2) - -terraform get - -terraform plan \ - -var 'dns_name='$KEY \ - -var 'admin_password='$PASSWORD \ - -var 'admin_username='$KEY \ - -var 'resource_group='$KEY \ - -out=out.tfplan - -terraform apply out.tfplan - +docker run --rm -it \ + -e ARM_CLIENT_ID \ + -e ARM_CLIENT_SECRET \ + -e ARM_SUBSCRIPTION_ID \ + -e ARM_TENANT_ID \ + -v $(pwd):/data \ + --entrypoint "/bin/sh" \ + hashicorp/terraform:light \ + -c "cd /data; \ + /bin/terraform get; \ + /bin/terraform validate; \ + /bin/terraform plan -out=out.tfplan -var hostname=$KEY -var resource_group=$KEY -var admin_username=$KEY -var admin_password=$PASSWORD -var image_uri=$IMAGE_URI -var primary_blob_endpoint=$PRIMARY_BLOB_ENDPOINT -var storage_account_name=$KEY; /bin/terraform apply out.tfplan" # TODO: determine external validation, possibly Azure CLI @@ -40,4 +35,4 @@ terraform apply out.tfplan # git commit -m "tfstate: $NOW [ci skip]" # # echo "Pushing changes to upstream master" -# git push upstream master \ No newline at end of file +# git push upstream master diff --git a/examples/azure-vm-from-user-image/variables.tf b/examples/azure-vm-from-user-image/variables.tf index 8bf04524f4fd..2f5cfca0f5a7 100644 --- a/examples/azure-vm-from-user-image/variables.tf +++ b/examples/azure-vm-from-user-image/variables.tf @@ -8,18 +8,22 @@ variable "image_uri" { variable "os_type" { description = "Specifies the operating system Type, valid values are windows, linux." + default="linux" } variable "location" { description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." + default="southcentralus" } variable "address_space" { description = "The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created." + default="10.0.0.0/16" } variable "subnet_prefix" { description = "The address prefix to use for the subnet." + default="10.0.0.0/24" } variable "storage_account_name" { @@ -28,10 +32,12 @@ variable "storage_account_name" { variable "storage_account_type" { description = "Defines the type of storage account to be created. Valid options are Standard_LRS, Standard_ZRS, Standard_GRS, Standard_RAGRS, Premium_LRS. Changing this is sometimes valid - see the Azure documentation for more information on which types of accounts can be converted into other types." + default="Standard_LRS" } variable "vm_size" { description = "Specifies the size of the virtual machine. This must be the same as the vm image from which you are copying." + default="Standard_DS1_v2" } variable "hostname" { @@ -40,6 +46,7 @@ variable "hostname" { variable "admin_username" { description = "administrator user name" + default="vmadmin" } variable "admin_password" { From 0cf888b6acbd04fc5e1c599abb77ad8e0027b0b3 Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Mon, 24 Apr 2017 10:16:06 -0500 Subject: [PATCH 11/67] deploy.sh to be executable --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 496f541775a2..bb75e76235a5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ before_deploy: deploy: - provider: script skip_cleanup: true - script: cd $TEST_DIR && ./deploy.sh + script: cd $TEST_DIR && chmod +X ./deploy.sh && ./deploy.sh on: repo: 10thmagnitude/terraform branch: topic-101-vm-from-user-image From 68f00035c9b9e9cd42e3dcba159d09f928f4734c Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Mon, 24 Apr 2017 10:25:46 -0500 Subject: [PATCH 12/67] executable deploy files --- .travis.yml | 2 +- examples/azure-vm-from-user-image/deploy.mac.sh | 0 examples/azure-vm-from-user-image/deploy.sh | 0 3 files changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 examples/azure-vm-from-user-image/deploy.mac.sh mode change 100644 => 100755 examples/azure-vm-from-user-image/deploy.sh diff --git a/.travis.yml b/.travis.yml index bb75e76235a5..496f541775a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ before_deploy: deploy: - provider: script skip_cleanup: true - script: cd $TEST_DIR && chmod +X ./deploy.sh && ./deploy.sh + script: cd $TEST_DIR && ./deploy.sh on: repo: 10thmagnitude/terraform branch: topic-101-vm-from-user-image diff --git a/examples/azure-vm-from-user-image/deploy.mac.sh b/examples/azure-vm-from-user-image/deploy.mac.sh old mode 100644 new mode 100755 diff --git a/examples/azure-vm-from-user-image/deploy.sh b/examples/azure-vm-from-user-image/deploy.sh old mode 100644 new mode 100755 From c898510f2471c59f03e4d5859bd060638455bb6c Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Mon, 24 Apr 2017 19:28:16 -0500 Subject: [PATCH 13/67] added CI files; changed vars --- .travis.yml | 6 +++--- examples/azure-vm-from-user-image/deploy.mac.sh | 6 +++--- examples/azure-vm-from-user-image/deploy.sh | 2 +- .../{azuredeploy.tf => main.tf} | 0 examples/azure-vm-from-user-image/variables.tf | 16 +++++++++------- 5 files changed, 16 insertions(+), 14 deletions(-) rename examples/azure-vm-from-user-image/{azuredeploy.tf => main.tf} (100%) diff --git a/.travis.yml b/.travis.yml index 496f541775a2..b7c7b7c23663 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,9 +21,9 @@ branches: before_deploy: - export KEY=$(cat /dev/urandom | tr -cd 'a-z' | head -c 12) - export PASSWORD=$KEY$(cat /dev/urandom | tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | tr -cd '0-9' | head -c 2) - - export IMAGE_URI='https://DISK.blob.core.windows.net/vhds/ORIGINAL-VM.vhd' - - export PRIMARY_BLOB_ENDPOINT='https://DISK.blob.core.windows.net/' - + - export IMAGE_URI=https://myrgdisks640.blob.core.windows.net/vhds/original-vm20170424164303.vhd + - export STORAGE_ACCOUNT_NAME=myrgdisks640 + - export RG=myrg # terraform deploy script deploy: diff --git a/examples/azure-vm-from-user-image/deploy.mac.sh b/examples/azure-vm-from-user-image/deploy.mac.sh index 6bb89bca9249..d66b85079b19 100755 --- a/examples/azure-vm-from-user-image/deploy.mac.sh +++ b/examples/azure-vm-from-user-image/deploy.mac.sh @@ -5,9 +5,9 @@ set -o errexit -o nounset # generate a unique string for CI deployment export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) -export IMAGE_URI='https://DISK.blob.core.windows.net/vhds/ORIGINAL-VM.vhd' -export PRIMARY_BLOB_ENDPOINT='https://DISK.blob.core.windows.net/' - +export IMAGE_URI=https://myrgdisks640.blob.core.windows.net/vhds/original-vm20170424164303.vhd +export STORAGE_ACCOUNT_NAME=myrgdisks640 +export RG=myrg /bin/sh ./deploy.sh diff --git a/examples/azure-vm-from-user-image/deploy.sh b/examples/azure-vm-from-user-image/deploy.sh index 1e8c75d4d77e..94a5d0a61aae 100755 --- a/examples/azure-vm-from-user-image/deploy.sh +++ b/examples/azure-vm-from-user-image/deploy.sh @@ -13,7 +13,7 @@ docker run --rm -it \ -c "cd /data; \ /bin/terraform get; \ /bin/terraform validate; \ - /bin/terraform plan -out=out.tfplan -var hostname=$KEY -var resource_group=$KEY -var admin_username=$KEY -var admin_password=$PASSWORD -var image_uri=$IMAGE_URI -var primary_blob_endpoint=$PRIMARY_BLOB_ENDPOINT -var storage_account_name=$KEY; /bin/terraform apply out.tfplan" + /bin/terraform plan -out=out.tfplan -var hostname=$KEY -var resource_group=$RG -var admin_username=$KEY -var admin_password=$PASSWORD -var image_uri=$IMAGE_URI -var storage_account_name=$STORAGE_ACCOUNT_NAME; /bin/terraform apply out.tfplan" # TODO: determine external validation, possibly Azure CLI diff --git a/examples/azure-vm-from-user-image/azuredeploy.tf b/examples/azure-vm-from-user-image/main.tf similarity index 100% rename from examples/azure-vm-from-user-image/azuredeploy.tf rename to examples/azure-vm-from-user-image/main.tf diff --git a/examples/azure-vm-from-user-image/variables.tf b/examples/azure-vm-from-user-image/variables.tf index 2f5cfca0f5a7..b3e94928978d 100644 --- a/examples/azure-vm-from-user-image/variables.tf +++ b/examples/azure-vm-from-user-image/variables.tf @@ -1,5 +1,6 @@ variable "resource_group" { description = "The name of the resource group in which the image to clone resides." + default = "myrg" } variable "image_uri" { @@ -8,22 +9,22 @@ variable "image_uri" { variable "os_type" { description = "Specifies the operating system Type, valid values are windows, linux." - default="linux" + default = "linux" } variable "location" { description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." - default="southcentralus" + default = "southcentralus" } variable "address_space" { description = "The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created." - default="10.0.0.0/16" + default = "10.0.0.0/24" } variable "subnet_prefix" { description = "The address prefix to use for the subnet." - default="10.0.0.0/24" + default = "10.0.0.0/24" } variable "storage_account_name" { @@ -32,12 +33,12 @@ variable "storage_account_name" { variable "storage_account_type" { description = "Defines the type of storage account to be created. Valid options are Standard_LRS, Standard_ZRS, Standard_GRS, Standard_RAGRS, Premium_LRS. Changing this is sometimes valid - see the Azure documentation for more information on which types of accounts can be converted into other types." - default="Standard_LRS" + default = "Premium_LRS" } variable "vm_size" { description = "Specifies the size of the virtual machine. This must be the same as the vm image from which you are copying." - default="Standard_DS1_v2" + default = "Standard_DS1_v2" } variable "hostname" { @@ -46,9 +47,10 @@ variable "hostname" { variable "admin_username" { description = "administrator user name" - default="vmadmin" + default = "vmadmin" } variable "admin_password" { description = "administrator password (recommended to disable password auth)" + default = "T3rr@f0rmP@ssword" } From 5c8ecd89384ec00514205e34eb45ca5de6b28f56 Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Tue, 25 Apr 2017 13:07:01 -0500 Subject: [PATCH 14/67] prep for PR --- .travis.yml | 6 +++--- examples/azure-vm-from-user-image/after_deploy.sh | 9 +++++++++ examples/azure-vm-from-user-image/deploy.sh | 3 ++- 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 examples/azure-vm-from-user-image/after_deploy.sh diff --git a/.travis.yml b/.travis.yml index 2300d05045ec..a669ca462c75 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,9 +21,9 @@ branches: before_deploy: - export KEY=$(cat /dev/urandom | tr -cd 'a-z' | head -c 12) - export PASSWORD=$KEY$(cat /dev/urandom | tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | tr -cd '0-9' | head -c 2) - - export IMAGE_URI=https://myrgdisks640.blob.core.windows.net/vhds/original-vm20170424164303.vhd - - export STORAGE_ACCOUNT_NAME=myrgdisks640 - - export RG=myrg + - export EXISTING_IMAGE_URI=https://myrgdisks640.blob.core.windows.net/vhds/original-vm20170424164303.vhd + - export EXISTING_STORAGE_ACCOUNT_NAME=myrgdisks640 + - export EXISTING_RESOURCE_GROUP=myrg # terraform deploy script deploy: diff --git a/examples/azure-vm-from-user-image/after_deploy.sh b/examples/azure-vm-from-user-image/after_deploy.sh new file mode 100644 index 000000000000..245aba38045e --- /dev/null +++ b/examples/azure-vm-from-user-image/after_deploy.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -o errexit -o nounset + +# cleanup deployed azure resources +docker run --rm -it \ + azuresdk/azure-cli-python \ + sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ + az group delete -y -n $KEY" diff --git a/examples/azure-vm-from-user-image/deploy.sh b/examples/azure-vm-from-user-image/deploy.sh index 94a5d0a61aae..3adf165f8700 100755 --- a/examples/azure-vm-from-user-image/deploy.sh +++ b/examples/azure-vm-from-user-image/deploy.sh @@ -13,7 +13,8 @@ docker run --rm -it \ -c "cd /data; \ /bin/terraform get; \ /bin/terraform validate; \ - /bin/terraform plan -out=out.tfplan -var hostname=$KEY -var resource_group=$RG -var admin_username=$KEY -var admin_password=$PASSWORD -var image_uri=$IMAGE_URI -var storage_account_name=$STORAGE_ACCOUNT_NAME; /bin/terraform apply out.tfplan" + /bin/terraform plan -out=out.tfplan -var hostname=$KEY -var resource_group=$EXISTING_RESOURCE_GROUP -var admin_username=$KEY -var admin_password=$PASSWORD -var image_uri=$EXISTING_IMAGE_URI -var storage_account_name=$EXISTING_STORAGE_ACCOUNT_NAME; \ + /bin/terraform apply out.tfplan" # TODO: determine external validation, possibly Azure CLI From 0267fcacf0f75dec5b8e2dfd06735c7e7489df8a Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Tue, 25 Apr 2017 13:42:34 -0500 Subject: [PATCH 15/67] removal of old folder --- examples/101-vm-simple-linux/.gitignore | 3 - examples/101-vm-simple-linux/README.md | 8 -- examples/101-vm-simple-linux/main.tf | 120 ---------------------- examples/101-vm-simple-linux/variables.tf | 78 -------------- 4 files changed, 209 deletions(-) delete mode 100644 examples/101-vm-simple-linux/.gitignore delete mode 100644 examples/101-vm-simple-linux/README.md delete mode 100644 examples/101-vm-simple-linux/main.tf delete mode 100644 examples/101-vm-simple-linux/variables.tf diff --git a/examples/101-vm-simple-linux/.gitignore b/examples/101-vm-simple-linux/.gitignore deleted file mode 100644 index a4f6eebd0a98..000000000000 --- a/examples/101-vm-simple-linux/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -terraform.tfstate* -terraform.tfvars* -provider.tf* diff --git a/examples/101-vm-simple-linux/README.md b/examples/101-vm-simple-linux/README.md deleted file mode 100644 index 92446ee9e970..000000000000 --- a/examples/101-vm-simple-linux/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Deploy a simple Linux VM -**ubuntu** - -This template allows you to deploy a simple Linux VM using a few different options for the Ubuntu version, using the latest patched version. This will deploy a A1 size VM in the resource group location and return the FQDN of the VM. - -This template takes a minimum amount of parameters and deploys a Linux VM, using the latest patched version. - -Azure requires that an application is added to Azure Active Directory to generate the client_id, client_secret, and tenant_id needed by Terraform (subscription_id can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this. \ No newline at end of file diff --git a/examples/101-vm-simple-linux/main.tf b/examples/101-vm-simple-linux/main.tf deleted file mode 100644 index f0ef742412d2..000000000000 --- a/examples/101-vm-simple-linux/main.tf +++ /dev/null @@ -1,120 +0,0 @@ -resource "azurerm_resource_group" "rg" { - name = "${var.resource_group}" - location = "${var.location}" -} - -resource "azurerm_virtual_network" "vnet" { - name = "${var.virtual_network_name}" - location = "${var.location}" - address_space = ["${var.address_space}"] - resource_group_name = "${azurerm_resource_group.rg.name}" -} - -resource "azurerm_subnet" "subnet" { - name = "${var.rg_prefix}subnet" - virtual_network_name = "${azurerm_virtual_network.vnet.name}" - resource_group_name = "${azurerm_resource_group.rg.name}" - address_prefix = "${var.subnet_prefix}" -} - -resource "azurerm_network_interface" "nic" { - name = "${var.rg_prefix}nic" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - - ip_configuration { - name = "${var.rg_prefix}ipconfig" - subnet_id = "${azurerm_subnet.subnet.id}" - private_ip_address_allocation = "Dynamic" - public_ip_address_id = "${azurerm_public_ip.pip.id}" - } -} - -resource "azurerm_public_ip" "pip" { - name = "${var.rg_prefix}-ip" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - public_ip_address_allocation = "dynamic" - domain_name_label = "${var.dns_name}" -} - -resource "azurerm_storage_account" "stor" { - name = "${var.hostname}stor" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - account_type = "${var.storage_account_type}" -} - -resource "azurerm_storage_container" "storc" { - name = "${var.hostname}-vhds" - resource_group_name = "${azurerm_resource_group.rg.name}" - storage_account_name = "${azurerm_storage_account.stor.name}" - container_access_type = "private" -} - -resource "azurerm_managed_disk" "disk1" { - name = "${var.hostname}-osdisk1" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - storage_account_type = "Standard_LRS" - create_option = "Empty" - disk_size_gb = "30" -} - -resource "azurerm_managed_disk" "disk2" { - name = "${var.hostname}-disk2" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - storage_account_type = "Standard_LRS" - create_option = "Empty" - disk_size_gb = "1023" -} - -resource "azurerm_virtual_machine" "vm" { - name = "${var.rg_prefix}vm" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - vm_size = "${var.vm_size}" - network_interface_ids = ["${azurerm_network_interface.nic.id}"] - - storage_image_reference { - publisher = "${var.image_publisher}" - offer = "${var.image_offer}" - sku = "${var.image_sku}" - version = "${var.image_version}" - } - - storage_os_disk { - name = "${var.hostname}-osdisk1" - vhd_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}${azurerm_storage_container.storc.name}/${var.hostname}-osdisk1.vhd" - caching = "ReadWrite" - create_option = "FromImage" - } - - storage_data_disk { - name = "${var.hostname}-disk2" - vhd_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}${azurerm_storage_container.storc.name}/${var.hostname}-disk2.vhd" - disk_size_gb = "1023" - create_option = "Empty" - lun = 0 - } - - os_profile { - computer_name = "${var.hostname}" - admin_username = "${var.admin_username}" - admin_password = "${var.admin_password}" - } - - boot_diagnostics { - enabled = "true" - storage_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}" - } -} - -output "hostname" { - value = "${var.hostname}" -} - -output "vm_fqdn" { - value = "${azurerm_public_ip.pip.fqdn}" -} \ No newline at end of file diff --git a/examples/101-vm-simple-linux/variables.tf b/examples/101-vm-simple-linux/variables.tf deleted file mode 100644 index b2ab327217fa..000000000000 --- a/examples/101-vm-simple-linux/variables.tf +++ /dev/null @@ -1,78 +0,0 @@ -variable "resource_group" { - description = "The name of the resource group in which to create the virtual network." - default = "myresourcegroup" -} - -variable "rg_prefix" { - description = "The shortened abbreviation to represent your resource group that will go on the front of some resources." - default = "rg" -} - -variable "location" { - description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." - default = "southcentralus" -} - -variable "virtual_network_name" { - description = "The name for the virtual network." - default = "vnet" -} - -variable "address_space" { - description = "The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created." - default = "10.0.0.0/16" -} - -variable "subnet_prefix" { - description = "The address prefix to use for the subnet." - default = "10.0.10.0/24" -} - -variable "storage_account_type" { - description = "Specifies the name of the storage account. Changing this forces a new resource to be created. This must be unique across the entire Azure service, not just within the resource group." - default = "Standard_LRS" -} - -variable "vm_size" { - description = "Specifies the name of the virtual machine resource. Changing this forces a new resource to be created." - default = "Standard_A0" -} - -variable "image_publisher" { - description = "name of the publisher of the image (az vm image list)" - default = "Canonical" -} - -variable "image_offer" { - description = "the name of the offer (az vm image list)" - default = "UbuntuServer" -} - -variable "image_sku" { - description = "image sku to apply (az vm image list)" - default = "16.04-LTS" -} - -variable "image_version" { - description = "version of the image to apply (az vm image list)" - default = "latest" -} - -variable "hostname" { - description = "VM name referenced also in storage-related names." - default = "myvm" -} - -variable "dns_name" { - description = " Label for the Domain Name. Will be used to make up the FQDN. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system." -} - -variable "admin_username" { - description = "administrator user name" - default = "vmadmin" -} - -variable "admin_password" { - description = "administrator password (recommended to disable password auth)" - default = "T3rr@f0rmP@ssword" -} From f9bf6baaf3724acc7bbc72cc015a7c40f79db2ab Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Tue, 25 Apr 2017 18:07:09 -0500 Subject: [PATCH 16/67] prep for PR --- examples/azure-vm-from-user-image/after_deploy.sh | 6 ++++-- examples/azure-vm-from-user-image/deploy.mac.sh | 13 ++++--------- 2 files changed, 8 insertions(+), 11 deletions(-) mode change 100644 => 100755 examples/azure-vm-from-user-image/after_deploy.sh diff --git a/examples/azure-vm-from-user-image/after_deploy.sh b/examples/azure-vm-from-user-image/after_deploy.sh old mode 100644 new mode 100755 index 245aba38045e..8a5624eb7681 --- a/examples/azure-vm-from-user-image/after_deploy.sh +++ b/examples/azure-vm-from-user-image/after_deploy.sh @@ -2,8 +2,10 @@ set -o errexit -o nounset -# cleanup deployed azure resources docker run --rm -it \ azuresdk/azure-cli-python \ sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ - az group delete -y -n $KEY" + az vm delete --name $KEY --resource-group permanent -y; \ + az network nic delete --name $KEY'nic' --resource-group permanent; \ + az network vnet delete --name $KEY'vnet' --resource-group permanent; \ + az network public-ip delete --name $KEY'-ip' --resource-group permanent;" diff --git a/examples/azure-vm-from-user-image/deploy.mac.sh b/examples/azure-vm-from-user-image/deploy.mac.sh index d66b85079b19..eb2808ef5502 100755 --- a/examples/azure-vm-from-user-image/deploy.mac.sh +++ b/examples/azure-vm-from-user-image/deploy.mac.sh @@ -5,13 +5,8 @@ set -o errexit -o nounset # generate a unique string for CI deployment export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) -export IMAGE_URI=https://myrgdisks640.blob.core.windows.net/vhds/original-vm20170424164303.vhd -export STORAGE_ACCOUNT_NAME=myrgdisks640 -export RG=myrg +export EXISTING_IMAGE_URI=https://permanentstor.blob.core.windows.net/permanent-vhds/permanent-osdisk1.vhd +export EXISTING_STORAGE_ACCOUNT_NAME=permanentstor +export EXISTING_RESOURCE_GROUP=permanent -/bin/sh ./deploy.sh - -# docker run --rm -it \ -# azuresdk/azure-cli-python \ -# sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ -# az group delete -y -n $KEY" +/bin/sh ./after_deploy.sh From cec06795654b98efca06c0235d42fb481e4f0718 Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Tue, 25 Apr 2017 18:11:07 -0500 Subject: [PATCH 17/67] wrong args for travis --- .travis.yml | 6 +++--- examples/azure-vm-from-user-image/deploy.sh | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index a669ca462c75..b559eea4327c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,9 +21,9 @@ branches: before_deploy: - export KEY=$(cat /dev/urandom | tr -cd 'a-z' | head -c 12) - export PASSWORD=$KEY$(cat /dev/urandom | tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | tr -cd '0-9' | head -c 2) - - export EXISTING_IMAGE_URI=https://myrgdisks640.blob.core.windows.net/vhds/original-vm20170424164303.vhd - - export EXISTING_STORAGE_ACCOUNT_NAME=myrgdisks640 - - export EXISTING_RESOURCE_GROUP=myrg + - export EXISTING_IMAGE_URI=https://permanentstor.blob.core.windows.net/permanent-vhds/permanent-osdisk1.vhd + - export EXISTING_STORAGE_ACCOUNT_NAME=permanentstor + - export EXISTING_RESOURCE_GROUP=permanent # terraform deploy script deploy: diff --git a/examples/azure-vm-from-user-image/deploy.sh b/examples/azure-vm-from-user-image/deploy.sh index 3adf165f8700..2558ebce5ff1 100755 --- a/examples/azure-vm-from-user-image/deploy.sh +++ b/examples/azure-vm-from-user-image/deploy.sh @@ -16,8 +16,6 @@ docker run --rm -it \ /bin/terraform plan -out=out.tfplan -var hostname=$KEY -var resource_group=$EXISTING_RESOURCE_GROUP -var admin_username=$KEY -var admin_password=$PASSWORD -var image_uri=$EXISTING_IMAGE_URI -var storage_account_name=$EXISTING_STORAGE_ACCOUNT_NAME; \ /bin/terraform apply out.tfplan" -# TODO: determine external validation, possibly Azure CLI - # echo "Setting git user name" # git config user.name $GH_USER_NAME # From d78e8bd4bdad292eff2a0dacc92299d5798e15d3 Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Wed, 26 Apr 2017 10:52:16 -0500 Subject: [PATCH 18/67] more PR prep --- examples/azure-vm-from-user-image/.gitignore | 3 --- examples/azure-vm-from-user-image/provider.tf | 6 ++++++ examples/azure-vm-from-user-image/provider.tf.example | 6 ------ .../{terraform.tfvars.example => terraform.tfvars} | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) delete mode 100644 examples/azure-vm-from-user-image/.gitignore create mode 100644 examples/azure-vm-from-user-image/provider.tf delete mode 100644 examples/azure-vm-from-user-image/provider.tf.example rename examples/azure-vm-from-user-image/{terraform.tfvars.example => terraform.tfvars} (92%) diff --git a/examples/azure-vm-from-user-image/.gitignore b/examples/azure-vm-from-user-image/.gitignore deleted file mode 100644 index 7773fa9ecc5d..000000000000 --- a/examples/azure-vm-from-user-image/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -terraform.tfstate* -terraform.tfvars -provider.tf diff --git a/examples/azure-vm-from-user-image/provider.tf b/examples/azure-vm-from-user-image/provider.tf new file mode 100644 index 000000000000..bdf0583f3259 --- /dev/null +++ b/examples/azure-vm-from-user-image/provider.tf @@ -0,0 +1,6 @@ +# provider "azurerm" { +# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" +# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" +# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" +# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" +# } diff --git a/examples/azure-vm-from-user-image/provider.tf.example b/examples/azure-vm-from-user-image/provider.tf.example deleted file mode 100644 index a0b5e46772f5..000000000000 --- a/examples/azure-vm-from-user-image/provider.tf.example +++ /dev/null @@ -1,6 +0,0 @@ -provider "azurerm" { - subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" - client_id = "REPLACE-WITH-YOUR-CLIENT-ID" - client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" - tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" -} diff --git a/examples/azure-vm-from-user-image/terraform.tfvars.example b/examples/azure-vm-from-user-image/terraform.tfvars similarity index 92% rename from examples/azure-vm-from-user-image/terraform.tfvars.example rename to examples/azure-vm-from-user-image/terraform.tfvars index 6cc72c9218be..d217db2f7a0a 100644 --- a/examples/azure-vm-from-user-image/terraform.tfvars.example +++ b/examples/azure-vm-from-user-image/terraform.tfvars @@ -10,4 +10,4 @@ storage_account_type = "Standard_LRS" vm_size = "Standard_DS1_v2" hostname = "HOSTNAME" admin_username = "vmadmin" -admin_password = "T3rr@f0rmP@ssword" \ No newline at end of file +admin_password = "YOURPASSWORDHERE" From 6bf2df2d6608eddf3c8707c848dab418927393f5 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Wed, 26 Apr 2017 10:54:22 -0500 Subject: [PATCH 19/67] updated README --- examples/azure-vm-from-user-image/README.md | 78 ++------------------- 1 file changed, 7 insertions(+), 71 deletions(-) diff --git a/examples/azure-vm-from-user-image/README.md b/examples/azure-vm-from-user-image/README.md index f391b0b8f40f..455b0bcb1a5f 100644 --- a/examples/azure-vm-from-user-image/README.md +++ b/examples/azure-vm-from-user-image/README.md @@ -1,8 +1,7 @@ # [Create a Virtual Machine from a User Image](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/cli-deploy-templates#create-a-custom-vm-image) - - - +This Terraform template was based on [this](https://github.com/Azure/azure-quickstart-templates/tree/master/101-vm-from-user-image) Azure Quickstart Template. Changes to the ARM template may have occured since the creation of this example may not be reflected here. + @@ -11,64 +10,8 @@ This template allows you to create a Virtual Machine from an unmanaged User image vhd. This template also deploys a Virtual Network, Public IP addresses and a Network Interface. -If you are looking to accomplish the above scenario through PowerShell instead of a template, you can use a PowerShell script like below - -##### Variables - ## Global - $rgName = "testrg" - $location = "westus" - - ## Storage - $storageName = "teststore" - $storageType = "Standard_GRS" - - ## Network - $nicname = "testnic" - $subnet1Name = "subnet1" - $vnetName = "testnet" - $vnetAddressPrefix = "10.0.0.0/16" - $vnetSubnetAddressPrefix = "10.0.0.0/24" - - ## Compute - $vmName = "testvm" - $computerName = "testcomputer" - $vmSize = "Standard_A2" - $osDiskName = $vmName + "osDisk" - -##### Resource Group - New-AzureRmResourceGroup -Name $rgName -Location $location - -##### Storage - $storageacc = New-AzureRmStorageAccount -ResourceGroupName $rgName -Name $storageName -Type $storageType -Location $location - -##### Network - $pip = New-AzureRmPublicIpAddress -Name $nicname -ResourceGroupName $rgName -Location $location -AllocationMethod Dynamic - $subnetconfig = New-AzureRmVirtualNetworkSubnetConfig -Name $subnet1Name -AddressPrefix $vnetSubnetAddressPrefix - $vnet = New-AzureRmVirtualNetwork -Name $vnetName -ResourceGroupName $rgName -Location $location -AddressPrefix $vnetAddressPrefix -Subnet $subnetconfig - $nic = New-AzureRmNetworkInterface -Name $nicname -ResourceGroupName $rgName -Location $location -SubnetId $vnet.Subnets[0].Id -PublicIpAddressId $pip.Id - -##### Compute - ## Setup local VM object - $cred = Get-Credential - $vm = New-AzureRmVMConfig -VMName $vmName -VMSize $vmSize - $vm = Set-AzureRmVMOperatingSystem -VM $vm -Windows -ComputerName $computerName -Credential $cred -ProvisionVMAgent -EnableAutoUpdate - - $vm = Add-AzureRmVMNetworkInterface -VM $vm -Id $nic.Id - - $osDiskUri = "http://test.blob.core.windows.net/vmcontainer10798c80-131-1231-a94a-f9d2a712251f/osDisk.10798c80-2919-4100-a94a-f9d2a712251f.vhd" - $imageUri = "http://test.blob.core.windows.net/system/Microsoft.Compute/Images/captured/image-osDisk.8b021d87-913c-4f94-a01a-944ad92d7388.vhd" - $vm = Set-AzureRmVMOSDisk -VM $vm -Name $osDiskName -VhdUri $osDiskUri -CreateOption fromImage -SourceImageUri $imageUri -Windows - - $dataImageUri = "http://test.blob.core.windows.net/system/Microsoft.Compute/Images/captured/image-dataDisk-0.8b021d87-913c-4f94-a01a-944ad92d7388.vhd" - $dataDiskUri = "http://test.blob.core.windows.net/vmcontainer10798c80-sa11-41sa-dsad-f9d2a712251f/dataDisk-0.10798c80-2919-4100-a94a-f9d2a712251f.vhd" - $vm = Add-AzureRmVMDataDisk -VM $vm -Name "dd1" -VhdUri $dataDiskUri -SourceImageUri $dataImageUri -Lun 0 -CreateOption fromImage - - ## Create the VM in Azure - New-AzureRmVM -ResourceGroupName $rgName -Location $location -VM $vm -Verbose - - -## azuredeploy.tf -The `azuredeploy.tf` file contains the actual resources that will be deployed. It also contains the Azure Resource Group definition and any defined variables. +## main.tf +The `main.tf` file contains the actual resources that will be deployed. It also contains the Azure Resource Group definition and any defined variables. ## outputs.tf This data is outputted when `terraform apply` is called, and can be queried using the `terraform output` command. @@ -77,16 +20,9 @@ This data is outputted when `terraform apply` is called, and can be queried usin Azure requires that an application is added to Azure Active Directory to generate the `client_id`, `client_secret`, and `tenant_id` needed by Terraform (`subscription_id` can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this to populate your `provider.tf` file. ## terraform.tfvars -If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. +If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. + +If you are committing this template to source control, please insure that you add this file to your `.gitignore` file. ## variables.tf The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. - -## .gitignore -If you are committing this template to source control, please insure that the following files are added to your `.gitignore` file. - -``` -terraform.tfstate* -terraform.tfvars* -provider.tf* -``` From b22cff0d170f72e8f098f85d77daf1ac129f7df3 Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Wed, 26 Apr 2017 10:54:32 -0500 Subject: [PATCH 20/67] commented out variables in terraform.tfvars --- .../azure-vm-from-user-image/terraform.tfvars | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/azure-vm-from-user-image/terraform.tfvars b/examples/azure-vm-from-user-image/terraform.tfvars index d217db2f7a0a..15da2a9c6ca7 100644 --- a/examples/azure-vm-from-user-image/terraform.tfvars +++ b/examples/azure-vm-from-user-image/terraform.tfvars @@ -1,13 +1,13 @@ -resource_group = "myresourcegroup" -image_uri = "https://DISK.blob.core.windows.net/vhds/ORIGINAL-VM.vhd" -primary_blob_endpoint = "https://DISK.blob.core.windows.net/" -location = "southcentralus" -os_type = "linux" -address_space = "10.0.0.0/16" -subnet_prefix = "10.0.0.0/24" -storage_account_name = "STOR-ACCT-NAME" -storage_account_type = "Standard_LRS" -vm_size = "Standard_DS1_v2" -hostname = "HOSTNAME" -admin_username = "vmadmin" -admin_password = "YOURPASSWORDHERE" +# resource_group = "myresourcegroup" +# image_uri = "https://DISK.blob.core.windows.net/vhds/ORIGINAL-VM.vhd" +# primary_blob_endpoint = "https://DISK.blob.core.windows.net/" +# location = "southcentralus" +# os_type = "linux" +# address_space = "10.0.0.0/16" +# subnet_prefix = "10.0.0.0/24" +# storage_account_name = "STOR-ACCT-NAME" +# storage_account_type = "Standard_LRS" +# vm_size = "Standard_DS1_v2" +# hostname = "HOSTNAME" +# admin_username = "vmadmin" +# admin_password = "YOURPASSWORDHERE" From 0843f451307b4c2a9740c5a9dcba0c0adda69f7c Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Wed, 26 Apr 2017 16:14:34 -0500 Subject: [PATCH 21/67] Topic 101 vm from user image (#2) * initial commit - 101-vm-from-user-image * added tfvars and info for README * added CI config; added sane defaults for variables; updated deployment script, added mac specific deployment for local testing * prep for PR --- .travis.yml | 15 +++-- examples/azure-vm-from-user-image/README.md | 28 +++++++++ .../azure-vm-from-user-image/after_deploy.sh | 11 ++++ .../azure-vm-from-user-image/deploy.mac.sh | 12 ++++ examples/azure-vm-from-user-image/deploy.sh | 37 +++++++++++ examples/azure-vm-from-user-image/main.tf | 62 +++++++++++++++++++ examples/azure-vm-from-user-image/outputs.tf | 11 ++++ examples/azure-vm-from-user-image/provider.tf | 6 ++ .../azure-vm-from-user-image/terraform.tfvars | 13 ++++ .../azure-vm-from-user-image/variables.tf | 56 +++++++++++++++++ 10 files changed, 247 insertions(+), 4 deletions(-) create mode 100644 examples/azure-vm-from-user-image/README.md create mode 100755 examples/azure-vm-from-user-image/after_deploy.sh create mode 100755 examples/azure-vm-from-user-image/deploy.mac.sh create mode 100755 examples/azure-vm-from-user-image/deploy.sh create mode 100644 examples/azure-vm-from-user-image/main.tf create mode 100644 examples/azure-vm-from-user-image/outputs.tf create mode 100644 examples/azure-vm-from-user-image/provider.tf create mode 100644 examples/azure-vm-from-user-image/terraform.tfvars create mode 100644 examples/azure-vm-from-user-image/variables.tf diff --git a/.travis.yml b/.travis.yml index 688b223f7a98..b559eea4327c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,18 +5,25 @@ services: language: generic +# on branches: ignore multiple commits that will queue build jobs, just run latest commit +git: + depth: 1 + # establish environment variables env: - - TEST_DIR=examples/azure-vm-simple-linux + - TEST_DIR=examples/azure-vm-from-user-image branches: only: - - master + - /^(?i:topic)-.*$/ # install terraform before_deploy: - export KEY=$(cat /dev/urandom | tr -cd 'a-z' | head -c 12) - export PASSWORD=$KEY$(cat /dev/urandom | tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | tr -cd '0-9' | head -c 2) + - export EXISTING_IMAGE_URI=https://permanentstor.blob.core.windows.net/permanent-vhds/permanent-osdisk1.vhd + - export EXISTING_STORAGE_ACCOUNT_NAME=permanentstor + - export EXISTING_RESOURCE_GROUP=permanent # terraform deploy script deploy: @@ -25,7 +32,7 @@ deploy: script: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./deploy.sh on: repo: 10thmagnitude/terraform - branch: master + branch: topic-101-vm-from-user-image -# destroy resources with Azure CLI +# cleanup after_deploy: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./after_deploy.sh diff --git a/examples/azure-vm-from-user-image/README.md b/examples/azure-vm-from-user-image/README.md new file mode 100644 index 000000000000..455b0bcb1a5f --- /dev/null +++ b/examples/azure-vm-from-user-image/README.md @@ -0,0 +1,28 @@ +# [Create a Virtual Machine from a User Image](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/cli-deploy-templates#create-a-custom-vm-image) + +This Terraform template was based on [this](https://github.com/Azure/azure-quickstart-templates/tree/master/101-vm-from-user-image) Azure Quickstart Template. Changes to the ARM template may have occured since the creation of this example may not be reflected here. + + + + + +> Prerequisite - The generalized image VHD should exist, as well as a Storage Account for boot diagnostics + +This template allows you to create a Virtual Machine from an unmanaged User image vhd. This template also deploys a Virtual Network, Public IP addresses and a Network Interface. + +## main.tf +The `main.tf` file contains the actual resources that will be deployed. It also contains the Azure Resource Group definition and any defined variables. + +## outputs.tf +This data is outputted when `terraform apply` is called, and can be queried using the `terraform output` command. + +## provider.tf +Azure requires that an application is added to Azure Active Directory to generate the `client_id`, `client_secret`, and `tenant_id` needed by Terraform (`subscription_id` can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this to populate your `provider.tf` file. + +## terraform.tfvars +If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. + +If you are committing this template to source control, please insure that you add this file to your `.gitignore` file. + +## variables.tf +The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. diff --git a/examples/azure-vm-from-user-image/after_deploy.sh b/examples/azure-vm-from-user-image/after_deploy.sh new file mode 100755 index 000000000000..8a5624eb7681 --- /dev/null +++ b/examples/azure-vm-from-user-image/after_deploy.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -o errexit -o nounset + +docker run --rm -it \ + azuresdk/azure-cli-python \ + sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ + az vm delete --name $KEY --resource-group permanent -y; \ + az network nic delete --name $KEY'nic' --resource-group permanent; \ + az network vnet delete --name $KEY'vnet' --resource-group permanent; \ + az network public-ip delete --name $KEY'-ip' --resource-group permanent;" diff --git a/examples/azure-vm-from-user-image/deploy.mac.sh b/examples/azure-vm-from-user-image/deploy.mac.sh new file mode 100755 index 000000000000..eb2808ef5502 --- /dev/null +++ b/examples/azure-vm-from-user-image/deploy.mac.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -o errexit -o nounset + +# generate a unique string for CI deployment +export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) +export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) +export EXISTING_IMAGE_URI=https://permanentstor.blob.core.windows.net/permanent-vhds/permanent-osdisk1.vhd +export EXISTING_STORAGE_ACCOUNT_NAME=permanentstor +export EXISTING_RESOURCE_GROUP=permanent + +/bin/sh ./after_deploy.sh diff --git a/examples/azure-vm-from-user-image/deploy.sh b/examples/azure-vm-from-user-image/deploy.sh new file mode 100755 index 000000000000..2558ebce5ff1 --- /dev/null +++ b/examples/azure-vm-from-user-image/deploy.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +set -o errexit -o nounset + +docker run --rm -it \ + -e ARM_CLIENT_ID \ + -e ARM_CLIENT_SECRET \ + -e ARM_SUBSCRIPTION_ID \ + -e ARM_TENANT_ID \ + -v $(pwd):/data \ + --entrypoint "/bin/sh" \ + hashicorp/terraform:light \ + -c "cd /data; \ + /bin/terraform get; \ + /bin/terraform validate; \ + /bin/terraform plan -out=out.tfplan -var hostname=$KEY -var resource_group=$EXISTING_RESOURCE_GROUP -var admin_username=$KEY -var admin_password=$PASSWORD -var image_uri=$EXISTING_IMAGE_URI -var storage_account_name=$EXISTING_STORAGE_ACCOUNT_NAME; \ + /bin/terraform apply out.tfplan" + +# echo "Setting git user name" +# git config user.name $GH_USER_NAME +# +# echo "Setting git user email" +# git config user.email $GH_USER_EMAIL +# +# echo "Adding git upstream remote" +# git remote add upstream "https://$GH_TOKEN@github.com/$GH_REPO.git" +# +# git checkout master + + +# +# NOW=$(TZ=America/Chicago date) +# +# git commit -m "tfstate: $NOW [ci skip]" +# +# echo "Pushing changes to upstream master" +# git push upstream master diff --git a/examples/azure-vm-from-user-image/main.tf b/examples/azure-vm-from-user-image/main.tf new file mode 100644 index 000000000000..28edf99204b5 --- /dev/null +++ b/examples/azure-vm-from-user-image/main.tf @@ -0,0 +1,62 @@ +resource "azurerm_resource_group" "rg" { + name = "${var.resource_group}" + location = "${var.location}" +} + +resource "azurerm_virtual_network" "vnet" { + name = "${var.hostname}vnet" + location = "${var.location}" + address_space = ["${var.address_space}"] + resource_group_name = "${azurerm_resource_group.rg.name}" +} + +resource "azurerm_subnet" "subnet" { + name = "${var.hostname}subnet" + virtual_network_name = "${azurerm_virtual_network.vnet.name}" + resource_group_name = "${azurerm_resource_group.rg.name}" + address_prefix = "${var.subnet_prefix}" +} + +resource "azurerm_network_interface" "nic" { + name = "${var.hostname}nic" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + + ip_configuration { + name = "${var.hostname}ipconfig" + subnet_id = "${azurerm_subnet.subnet.id}" + private_ip_address_allocation = "Dynamic" + public_ip_address_id = "${azurerm_public_ip.pip.id}" + } +} + +resource "azurerm_public_ip" "pip" { + name = "${var.hostname}-ip" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + public_ip_address_allocation = "dynamic" + domain_name_label = "${var.hostname}" +} + +resource "azurerm_virtual_machine" "vm" { + name = "${var.hostname}" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + vm_size = "${var.vm_size}" + network_interface_ids = ["${azurerm_network_interface.nic.id}"] + + storage_os_disk { + name = "${var.hostname}-osdisk1" + image_uri = "${var.image_uri}" + vhd_uri = "https://${var.storage_account_name}.blob.core.windows.net/vhds/${var.hostname}osdisk.vhd" + os_type = "${var.os_type}" + caching = "ReadWrite" + create_option = "FromImage" + } + + os_profile { + computer_name = "${var.hostname}" + admin_username = "${var.admin_username}" + admin_password = "${var.admin_password}" + } +} diff --git a/examples/azure-vm-from-user-image/outputs.tf b/examples/azure-vm-from-user-image/outputs.tf new file mode 100644 index 000000000000..e0e255a9e01c --- /dev/null +++ b/examples/azure-vm-from-user-image/outputs.tf @@ -0,0 +1,11 @@ +output "hostname" { + value = "${var.hostname}" +} + +output "vm_fqdn" { + value = "${azurerm_public_ip.pip.fqdn}" +} + +output "sshCommand" { + value = "${concat("ssh ", var.admin_username, "@", azurerm_public_ip.pip.fqdn)}" +} diff --git a/examples/azure-vm-from-user-image/provider.tf b/examples/azure-vm-from-user-image/provider.tf new file mode 100644 index 000000000000..bdf0583f3259 --- /dev/null +++ b/examples/azure-vm-from-user-image/provider.tf @@ -0,0 +1,6 @@ +# provider "azurerm" { +# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" +# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" +# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" +# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" +# } diff --git a/examples/azure-vm-from-user-image/terraform.tfvars b/examples/azure-vm-from-user-image/terraform.tfvars new file mode 100644 index 000000000000..15da2a9c6ca7 --- /dev/null +++ b/examples/azure-vm-from-user-image/terraform.tfvars @@ -0,0 +1,13 @@ +# resource_group = "myresourcegroup" +# image_uri = "https://DISK.blob.core.windows.net/vhds/ORIGINAL-VM.vhd" +# primary_blob_endpoint = "https://DISK.blob.core.windows.net/" +# location = "southcentralus" +# os_type = "linux" +# address_space = "10.0.0.0/16" +# subnet_prefix = "10.0.0.0/24" +# storage_account_name = "STOR-ACCT-NAME" +# storage_account_type = "Standard_LRS" +# vm_size = "Standard_DS1_v2" +# hostname = "HOSTNAME" +# admin_username = "vmadmin" +# admin_password = "YOURPASSWORDHERE" diff --git a/examples/azure-vm-from-user-image/variables.tf b/examples/azure-vm-from-user-image/variables.tf new file mode 100644 index 000000000000..b3e94928978d --- /dev/null +++ b/examples/azure-vm-from-user-image/variables.tf @@ -0,0 +1,56 @@ +variable "resource_group" { + description = "The name of the resource group in which the image to clone resides." + default = "myrg" +} + +variable "image_uri" { + description = "Specifies the image_uri in the form publisherName:offer:skus:version. image_uri can also specify the VHD uri of a custom VM image to clone." +} + +variable "os_type" { + description = "Specifies the operating system Type, valid values are windows, linux." + default = "linux" +} + +variable "location" { + description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." + default = "southcentralus" +} + +variable "address_space" { + description = "The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created." + default = "10.0.0.0/24" +} + +variable "subnet_prefix" { + description = "The address prefix to use for the subnet." + default = "10.0.0.0/24" +} + +variable "storage_account_name" { + description = "The name of the storage account in which the image from which you are cloning resides." +} + +variable "storage_account_type" { + description = "Defines the type of storage account to be created. Valid options are Standard_LRS, Standard_ZRS, Standard_GRS, Standard_RAGRS, Premium_LRS. Changing this is sometimes valid - see the Azure documentation for more information on which types of accounts can be converted into other types." + default = "Premium_LRS" +} + +variable "vm_size" { + description = "Specifies the size of the virtual machine. This must be the same as the vm image from which you are copying." + default = "Standard_DS1_v2" +} + +variable "hostname" { + description = "VM name referenced also in storage-related names. This is also used as the label for the Domain Name and to make up the FQDN. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system." +} + +variable "admin_username" { + description = "administrator user name" + default = "vmadmin" +} + +variable "admin_password" { + description = "administrator password (recommended to disable password auth)" + default = "T3rr@f0rmP@ssword" +} From 49caea553c384dc44f0d367c21bf7a2b1274df2c Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Wed, 26 Apr 2017 16:29:11 -0500 Subject: [PATCH 22/67] added new template --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b559eea4327c..9412e69721ff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ git: # establish environment variables env: + - TEST_DIR=examples/azure-vm-simple-linux - TEST_DIR=examples/azure-vm-from-user-image branches: @@ -32,7 +33,7 @@ deploy: script: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./deploy.sh on: repo: 10thmagnitude/terraform - branch: topic-101-vm-from-user-image + branch: master # cleanup after_deploy: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./after_deploy.sh From 2236828c1f0d3231cb4d86a49213ba141f769bab Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Wed, 26 Apr 2017 16:37:46 -0500 Subject: [PATCH 23/67] oops, left off master --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 9412e69721ff..af6a783e4aa3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ env: branches: only: + - master - /^(?i:topic)-.*$/ # install terraform From 7f8fe25583bd941ec3ce1a0fa79eb557ed97a4bf Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Wed, 26 Apr 2017 19:09:46 -0500 Subject: [PATCH 24/67] prep for PR --- examples/azure-vm-simple-linux/.gitignore | 3 - examples/azure-vm-simple-linux/README.md | 36 ------ .../azure-vm-simple-linux/after_deploy.sh | 9 -- examples/azure-vm-simple-linux/deploy.mac.sh | 14 --- examples/azure-vm-simple-linux/deploy.sh | 17 --- examples/azure-vm-simple-linux/main.tf | 112 ------------------ examples/azure-vm-simple-linux/outputs.tf | 11 -- .../azure-vm-simple-linux/provider.tf.example | 6 - .../terraform.tfvars.example | 6 - examples/azure-vm-simple-linux/variables.tf | 75 ------------ 10 files changed, 289 deletions(-) delete mode 100644 examples/azure-vm-simple-linux/.gitignore delete mode 100644 examples/azure-vm-simple-linux/README.md delete mode 100755 examples/azure-vm-simple-linux/after_deploy.sh delete mode 100755 examples/azure-vm-simple-linux/deploy.mac.sh delete mode 100755 examples/azure-vm-simple-linux/deploy.sh delete mode 100644 examples/azure-vm-simple-linux/main.tf delete mode 100644 examples/azure-vm-simple-linux/outputs.tf delete mode 100644 examples/azure-vm-simple-linux/provider.tf.example delete mode 100644 examples/azure-vm-simple-linux/terraform.tfvars.example delete mode 100644 examples/azure-vm-simple-linux/variables.tf diff --git a/examples/azure-vm-simple-linux/.gitignore b/examples/azure-vm-simple-linux/.gitignore deleted file mode 100644 index 7773fa9ecc5d..000000000000 --- a/examples/azure-vm-simple-linux/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -terraform.tfstate* -terraform.tfvars -provider.tf diff --git a/examples/azure-vm-simple-linux/README.md b/examples/azure-vm-simple-linux/README.md deleted file mode 100644 index a0ab5f63451e..000000000000 --- a/examples/azure-vm-simple-linux/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Very simple deployment of a Linux VM - - - - - - - - -This template allows you to deploy a simple Linux VM using a few different options for the Ubuntu version, using the latest patched version. This will deploy a A1 size VM in the resource group location and return the FQDN of the VM. - -This template takes a minimum amount of parameters and deploys a Linux VM, using the latest patched version. - -## main.tf -The `main.tf` file contains the actual resources that will be deployed. It also contains the Azure Resource Group definition and any defined variables. - -## outputs.tf -This data is outputted when `terraform apply` is called, and can be queried using the `terraform output` command. - -## provider.tf -Azure requires that an application is added to Azure Active Directory to generate the `client_id`, `client_secret`, and `tenant_id` needed by Terraform (`subscription_id` can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this to populate your `provider.tf` file. - -## terraform.tfvars -If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. - -## variables.tf -The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. - -## .gitignore -If you are committing this template to source control, please insure that the following files are added to your `.gitignore` file. - -``` -terraform.tfstate* -terraform.tfvars -provider.tf* -``` \ No newline at end of file diff --git a/examples/azure-vm-simple-linux/after_deploy.sh b/examples/azure-vm-simple-linux/after_deploy.sh deleted file mode 100755 index 245aba38045e..000000000000 --- a/examples/azure-vm-simple-linux/after_deploy.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -set -o errexit -o nounset - -# cleanup deployed azure resources -docker run --rm -it \ - azuresdk/azure-cli-python \ - sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ - az group delete -y -n $KEY" diff --git a/examples/azure-vm-simple-linux/deploy.mac.sh b/examples/azure-vm-simple-linux/deploy.mac.sh deleted file mode 100755 index f0bb60f8bb8b..000000000000 --- a/examples/azure-vm-simple-linux/deploy.mac.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -set -o errexit -o nounset - -# generate a unique string for CI deployment -export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) -export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) - -/bin/sh ./deploy.sh - -# docker run --rm -it \ -# azuresdk/azure-cli-python \ -# sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ -# az group delete -y -n $KEY" diff --git a/examples/azure-vm-simple-linux/deploy.sh b/examples/azure-vm-simple-linux/deploy.sh deleted file mode 100755 index 5347f3a838c6..000000000000 --- a/examples/azure-vm-simple-linux/deploy.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -set -o errexit -o nounset - -docker run --rm -it \ - -e ARM_CLIENT_ID \ - -e ARM_CLIENT_SECRET \ - -e ARM_SUBSCRIPTION_ID \ - -e ARM_TENANT_ID \ - -v $(pwd):/data \ - --entrypoint "/bin/sh" \ - hashicorp/terraform:light \ - -c "cd /data; \ - /bin/terraform get; \ - /bin/terraform validate; \ - /bin/terraform plan -out=out.tfplan -var dns_name=$KEY -var hostname=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD; \ - /bin/terraform apply out.tfplan" diff --git a/examples/azure-vm-simple-linux/main.tf b/examples/azure-vm-simple-linux/main.tf deleted file mode 100644 index cf931d0f9e4e..000000000000 --- a/examples/azure-vm-simple-linux/main.tf +++ /dev/null @@ -1,112 +0,0 @@ -resource "azurerm_resource_group" "rg" { - name = "${var.resource_group}" - location = "${var.location}" -} - -resource "azurerm_virtual_network" "vnet" { - name = "${var.virtual_network_name}" - location = "${var.location}" - address_space = ["${var.address_space}"] - resource_group_name = "${azurerm_resource_group.rg.name}" -} - -resource "azurerm_subnet" "subnet" { - name = "${var.rg_prefix}subnet" - virtual_network_name = "${azurerm_virtual_network.vnet.name}" - resource_group_name = "${azurerm_resource_group.rg.name}" - address_prefix = "${var.subnet_prefix}" -} - -resource "azurerm_network_interface" "nic" { - name = "${var.rg_prefix}nic" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - - ip_configuration { - name = "${var.rg_prefix}ipconfig" - subnet_id = "${azurerm_subnet.subnet.id}" - private_ip_address_allocation = "Dynamic" - public_ip_address_id = "${azurerm_public_ip.pip.id}" - } -} - -resource "azurerm_public_ip" "pip" { - name = "${var.rg_prefix}-ip" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - public_ip_address_allocation = "dynamic" - domain_name_label = "${var.dns_name}" -} - -resource "azurerm_storage_account" "stor" { - name = "${var.dns_name}stor" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - account_type = "${var.storage_account_type}" -} - -resource "azurerm_storage_container" "storc" { - name = "${var.hostname}-vhds" - resource_group_name = "${azurerm_resource_group.rg.name}" - storage_account_name = "${azurerm_storage_account.stor.name}" - container_access_type = "private" -} - -resource "azurerm_managed_disk" "disk1" { - name = "${var.hostname}-osdisk1" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - storage_account_type = "Standard_LRS" - create_option = "Empty" - disk_size_gb = "30" -} - -resource "azurerm_managed_disk" "disk2" { - name = "${var.hostname}-disk2" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - storage_account_type = "Standard_LRS" - create_option = "Empty" - disk_size_gb = "1023" -} - -resource "azurerm_virtual_machine" "vm" { - name = "${var.rg_prefix}vm" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - vm_size = "${var.vm_size}" - network_interface_ids = ["${azurerm_network_interface.nic.id}"] - - storage_image_reference { - publisher = "${var.image_publisher}" - offer = "${var.image_offer}" - sku = "${var.image_sku}" - version = "${var.image_version}" - } - - storage_os_disk { - name = "${var.hostname}-osdisk1" - vhd_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}${azurerm_storage_container.storc.name}/${var.hostname}-osdisk1.vhd" - caching = "ReadWrite" - create_option = "FromImage" - } - - storage_data_disk { - name = "${var.hostname}-disk2" - vhd_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}${azurerm_storage_container.storc.name}/${var.hostname}-disk2.vhd" - disk_size_gb = "1023" - create_option = "Empty" - lun = 0 - } - - os_profile { - computer_name = "${var.hostname}" - admin_username = "${var.admin_username}" - admin_password = "${var.admin_password}" - } - - boot_diagnostics { - enabled = "true" - storage_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}" - } -} diff --git a/examples/azure-vm-simple-linux/outputs.tf b/examples/azure-vm-simple-linux/outputs.tf deleted file mode 100644 index 9e3c2f0712bc..000000000000 --- a/examples/azure-vm-simple-linux/outputs.tf +++ /dev/null @@ -1,11 +0,0 @@ -output "hostname" { - value = "${var.hostname}" -} - -output "vm_fqdn" { - value = "${azurerm_public_ip.pip.fqdn}" -} - -output "sshCommand" { - value = "ssh ${var.admin_username}@${azurerm_public_ip.pip.fqdn}" -} diff --git a/examples/azure-vm-simple-linux/provider.tf.example b/examples/azure-vm-simple-linux/provider.tf.example deleted file mode 100644 index 327ceb55eefa..000000000000 --- a/examples/azure-vm-simple-linux/provider.tf.example +++ /dev/null @@ -1,6 +0,0 @@ -provider "azurerm" { - subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" - client_id = "REPLACE-WITH-YOUR-CLIENT-ID" - client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" - tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" -} \ No newline at end of file diff --git a/examples/azure-vm-simple-linux/terraform.tfvars.example b/examples/azure-vm-simple-linux/terraform.tfvars.example deleted file mode 100644 index 262093ba52f9..000000000000 --- a/examples/azure-vm-simple-linux/terraform.tfvars.example +++ /dev/null @@ -1,6 +0,0 @@ -resource_group = "myresourcegroup" -rg_prefix = "rg" -hostname = "myvm" -dns_name = "mydnsname" -location = "southcentralus" -admin_password = "T3rr@f0rmP@ssword" diff --git a/examples/azure-vm-simple-linux/variables.tf b/examples/azure-vm-simple-linux/variables.tf deleted file mode 100644 index 6d65a0277060..000000000000 --- a/examples/azure-vm-simple-linux/variables.tf +++ /dev/null @@ -1,75 +0,0 @@ -variable "resource_group" { - description = "The name of the resource group in which to create the virtual network." -} - -variable "rg_prefix" { - description = "The shortened abbreviation to represent your resource group that will go on the front of some resources." - default = "rg" -} - -variable "hostname" { - description = "VM name referenced also in storage-related names." -} - -variable "dns_name" { - description = " Label for the Domain Name. Will be used to make up the FQDN. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system." -} - -variable "location" { - description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." - default = "southcentralus" -} - -variable "virtual_network_name" { - description = "The name for the virtual network." - default = "vnet" -} - -variable "address_space" { - description = "The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created." - default = "10.0.0.0/16" -} - -variable "subnet_prefix" { - description = "The address prefix to use for the subnet." - default = "10.0.10.0/24" -} - -variable "storage_account_type" { - description = "Specifies the name of the storage account. Changing this forces a new resource to be created. This must be unique across the entire Azure service, not just within the resource group." - default = "Standard_LRS" -} - -variable "vm_size" { - description = "Specifies the name of the virtual machine resource. Changing this forces a new resource to be created." - default = "Standard_A0" -} - -variable "image_publisher" { - description = "name of the publisher of the image (az vm image list)" - default = "Canonical" -} - -variable "image_offer" { - description = "the name of the offer (az vm image list)" - default = "UbuntuServer" -} - -variable "image_sku" { - description = "image sku to apply (az vm image list)" - default = "16.04-LTS" -} - -variable "image_version" { - description = "version of the image to apply (az vm image list)" - default = "latest" -} - -variable "admin_username" { - description = "administrator user name" - default = "vmadmin" -} - -variable "admin_password" { - description = "administrator password (recommended to disable password auth)" -} From eecb2d2bd2ccd793fecfac7b4628388febf7e035 Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Wed, 26 Apr 2017 20:50:48 -0500 Subject: [PATCH 25/67] correct repository for destination --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 875765d83b66..544b49bb33f5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ deploy: skip_cleanup: true script: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./deploy.sh on: - repo: 10thmagnitude/terraform + repo: harijayms/terraform branch: master # cleanup From 92e341d38bca428ada134091b262ad6937ff4d2f Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Wed, 26 Apr 2017 21:38:23 -0500 Subject: [PATCH 26/67] renamed scripts to be more intuitive; added check for docker --- .travis.yml | 4 ++-- .../{after_deploy.sh => after_deploy.ci.sh} | 0 .../{deploy.sh => deploy.ci.sh} | 4 ++-- .../azure-vm-from-user-image/deploy.mac.sh | 22 ++++++++++++------- 4 files changed, 18 insertions(+), 12 deletions(-) rename examples/azure-vm-from-user-image/{after_deploy.sh => after_deploy.ci.sh} (100%) rename examples/azure-vm-from-user-image/{deploy.sh => deploy.ci.sh} (95%) diff --git a/.travis.yml b/.travis.yml index 544b49bb33f5..2e9151fa24c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,10 +31,10 @@ before_deploy: deploy: - provider: script skip_cleanup: true - script: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./deploy.sh + script: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./deploy.ci.sh on: repo: harijayms/terraform branch: master # cleanup -after_deploy: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./after_deploy.sh +after_deploy: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./after_deploy.ci.sh diff --git a/examples/azure-vm-from-user-image/after_deploy.sh b/examples/azure-vm-from-user-image/after_deploy.ci.sh similarity index 100% rename from examples/azure-vm-from-user-image/after_deploy.sh rename to examples/azure-vm-from-user-image/after_deploy.ci.sh diff --git a/examples/azure-vm-from-user-image/deploy.sh b/examples/azure-vm-from-user-image/deploy.ci.sh similarity index 95% rename from examples/azure-vm-from-user-image/deploy.sh rename to examples/azure-vm-from-user-image/deploy.ci.sh index 2558ebce5ff1..f45c5bb2a6cd 100755 --- a/examples/azure-vm-from-user-image/deploy.sh +++ b/examples/azure-vm-from-user-image/deploy.ci.sh @@ -8,10 +8,10 @@ docker run --rm -it \ -e ARM_SUBSCRIPTION_ID \ -e ARM_TENANT_ID \ -v $(pwd):/data \ + --workdir=/data \ --entrypoint "/bin/sh" \ hashicorp/terraform:light \ - -c "cd /data; \ - /bin/terraform get; \ + -c "/bin/terraform get; \ /bin/terraform validate; \ /bin/terraform plan -out=out.tfplan -var hostname=$KEY -var resource_group=$EXISTING_RESOURCE_GROUP -var admin_username=$KEY -var admin_password=$PASSWORD -var image_uri=$EXISTING_IMAGE_URI -var storage_account_name=$EXISTING_STORAGE_ACCOUNT_NAME; \ /bin/terraform apply out.tfplan" diff --git a/examples/azure-vm-from-user-image/deploy.mac.sh b/examples/azure-vm-from-user-image/deploy.mac.sh index eb2808ef5502..acd4ca7028fc 100755 --- a/examples/azure-vm-from-user-image/deploy.mac.sh +++ b/examples/azure-vm-from-user-image/deploy.mac.sh @@ -2,11 +2,17 @@ set -o errexit -o nounset -# generate a unique string for CI deployment -export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) -export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) -export EXISTING_IMAGE_URI=https://permanentstor.blob.core.windows.net/permanent-vhds/permanent-osdisk1.vhd -export EXISTING_STORAGE_ACCOUNT_NAME=permanentstor -export EXISTING_RESOURCE_GROUP=permanent - -/bin/sh ./after_deploy.sh +if docker -v; then + + # generate a unique string for CI deployment + export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) + export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) + export EXISTING_IMAGE_URI=https://permanentstor.blob.core.windows.net/permanent-vhds/permanent-osdisk1.vhd + export EXISTING_STORAGE_ACCOUNT_NAME=permanentstor + export EXISTING_RESOURCE_GROUP=permanent + + /bin/sh ./deploy.ci.sh + +else + echo "Docker is used to run terraform commands, please install before run: https://docs.docker.com/docker-for-mac/install/" +fi From 23627c80a8acab7b32ec66a3267f00edf08972f4 Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Wed, 26 Apr 2017 22:15:44 -0500 Subject: [PATCH 27/67] merge vm simple; vm from image --- .travis.yml | 2 +- .../README.md | 24 ++++ .../after_deploy.ci.sh | 9 ++ .../deploy.ci.sh | 17 +++ .../deploy.mac.sh | 15 +++ .../main.tf | 104 ++++++++++++++++++ .../outputs.tf | 11 ++ .../provider.tf | 7 ++ .../terraform.tfvars | 8 ++ .../variables.tf | 75 +++++++++++++ 10 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 examples/azure-vm-simple-linux-managed-disk/README.md create mode 100755 examples/azure-vm-simple-linux-managed-disk/after_deploy.ci.sh create mode 100755 examples/azure-vm-simple-linux-managed-disk/deploy.ci.sh create mode 100755 examples/azure-vm-simple-linux-managed-disk/deploy.mac.sh create mode 100644 examples/azure-vm-simple-linux-managed-disk/main.tf create mode 100644 examples/azure-vm-simple-linux-managed-disk/outputs.tf create mode 100644 examples/azure-vm-simple-linux-managed-disk/provider.tf create mode 100644 examples/azure-vm-simple-linux-managed-disk/terraform.tfvars create mode 100644 examples/azure-vm-simple-linux-managed-disk/variables.tf diff --git a/.travis.yml b/.travis.yml index 2e9151fa24c4..d54a0592b1e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,7 +33,7 @@ deploy: skip_cleanup: true script: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./deploy.ci.sh on: - repo: harijayms/terraform + repo: 10thmagnitude/terraform branch: master # cleanup diff --git a/examples/azure-vm-simple-linux-managed-disk/README.md b/examples/azure-vm-simple-linux-managed-disk/README.md new file mode 100644 index 000000000000..0492e3edccb9 --- /dev/null +++ b/examples/azure-vm-simple-linux-managed-disk/README.md @@ -0,0 +1,24 @@ +# Very simple deployment of a Linux VM + + + + + +This template allows you to deploy a simple Linux VM using a few different options for the Ubuntu version, using the latest patched version. This will deploy a A1 size VM in the resource group location and return the FQDN of the VM. + +This template takes a minimum amount of parameters and deploys a Linux VM, using the latest patched version. + +## main.tf +The `main.tf` file contains the actual resources that will be deployed. It also contains the Azure Resource Group definition and any defined variables. + +## outputs.tf +This data is outputted when `terraform apply` is called, and can be queried using the `terraform output` command. + +## provider.tf +Azure requires that an application is added to Azure Active Directory to generate the `client_id`, `client_secret`, and `tenant_id` needed by Terraform (`subscription_id` can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this to populate your `provider.tf` file. + +## terraform.tfvars +If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. + +## variables.tf +The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. diff --git a/examples/azure-vm-simple-linux-managed-disk/after_deploy.ci.sh b/examples/azure-vm-simple-linux-managed-disk/after_deploy.ci.sh new file mode 100755 index 000000000000..245aba38045e --- /dev/null +++ b/examples/azure-vm-simple-linux-managed-disk/after_deploy.ci.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -o errexit -o nounset + +# cleanup deployed azure resources +docker run --rm -it \ + azuresdk/azure-cli-python \ + sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ + az group delete -y -n $KEY" diff --git a/examples/azure-vm-simple-linux-managed-disk/deploy.ci.sh b/examples/azure-vm-simple-linux-managed-disk/deploy.ci.sh new file mode 100755 index 000000000000..8fa08573faad --- /dev/null +++ b/examples/azure-vm-simple-linux-managed-disk/deploy.ci.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -o errexit -o nounset + +docker run --rm -it \ + -e ARM_CLIENT_ID \ + -e ARM_CLIENT_SECRET \ + -e ARM_SUBSCRIPTION_ID \ + -e ARM_TENANT_ID \ + -v $(pwd):/data \ + --workdir=/data \ + --entrypoint "/bin/sh" \ + hashicorp/terraform:light \ + -c "/bin/terraform get; \ + /bin/terraform validate; \ + /bin/terraform plan -out=out.tfplan -var dns_name=$KEY -var hostname=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD; \ + /bin/terraform apply out.tfplan" diff --git a/examples/azure-vm-simple-linux-managed-disk/deploy.mac.sh b/examples/azure-vm-simple-linux-managed-disk/deploy.mac.sh new file mode 100755 index 000000000000..9c6563f07d71 --- /dev/null +++ b/examples/azure-vm-simple-linux-managed-disk/deploy.mac.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -o errexit -o nounset + +if docker -v; then + + # generate a unique string for CI deployment + export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) + export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) + + /bin/sh ./deploy.ci.sh + +else + echo "Docker is used to run terraform commands, please install before run: https://docs.docker.com/docker-for-mac/install/" +fi diff --git a/examples/azure-vm-simple-linux-managed-disk/main.tf b/examples/azure-vm-simple-linux-managed-disk/main.tf new file mode 100644 index 000000000000..e9c0877d5a39 --- /dev/null +++ b/examples/azure-vm-simple-linux-managed-disk/main.tf @@ -0,0 +1,104 @@ +resource "azurerm_resource_group" "rg" { + name = "${var.resource_group}" + location = "${var.location}" +} + +resource "azurerm_virtual_network" "vnet" { + name = "${var.virtual_network_name}" + location = "${var.location}" + address_space = ["${var.address_space}"] + resource_group_name = "${azurerm_resource_group.rg.name}" +} + +resource "azurerm_subnet" "subnet" { + name = "${var.rg_prefix}subnet" + virtual_network_name = "${azurerm_virtual_network.vnet.name}" + resource_group_name = "${azurerm_resource_group.rg.name}" + address_prefix = "${var.subnet_prefix}" +} + +resource "azurerm_network_interface" "nic" { + name = "${var.rg_prefix}nic" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + + ip_configuration { + name = "${var.rg_prefix}ipconfig" + subnet_id = "${azurerm_subnet.subnet.id}" + private_ip_address_allocation = "Dynamic" + public_ip_address_id = "${azurerm_public_ip.pip.id}" + } +} + +resource "azurerm_public_ip" "pip" { + name = "${var.rg_prefix}-ip" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + public_ip_address_allocation = "dynamic" + domain_name_label = "${var.dns_name}" +} + +resource "azurerm_storage_account" "stor" { + name = "${var.dns_name}stor" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + account_type = "${var.storage_account_type}" +} + +resource "azurerm_storage_container" "storc" { + name = "${var.hostname}-vhds" + resource_group_name = "${azurerm_resource_group.rg.name}" + storage_account_name = "${azurerm_storage_account.stor.name}" + container_access_type = "private" +} + +resource "azurerm_managed_disk" "datadisk" { + name = "${var.hostname}-datadisk" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + storage_account_type = "Standard_LRS" + create_option = "Empty" + disk_size_gb = "1023" +} + +resource "azurerm_virtual_machine" "vm" { + name = "${var.rg_prefix}vm" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + vm_size = "${var.vm_size}" + network_interface_ids = ["${azurerm_network_interface.nic.id}"] + + storage_image_reference { + publisher = "${var.image_publisher}" + offer = "${var.image_offer}" + sku = "${var.image_sku}" + version = "${var.image_version}" + } + + storage_os_disk { + name = "${var.hostname}-osdisk" + managed_disk_type = "Standard_LRS" + caching = "ReadWrite" + create_option = "FromImage" + } + + storage_data_disk { + name = "${var.hostname}-datadisk" + managed_disk_id = "${azurerm_managed_disk.datadisk.id}" + managed_disk_type = "Standard_LRS" + disk_size_gb = "1023" + create_option = "Attach" + lun = 0 + } + + os_profile { + computer_name = "${var.hostname}" + admin_username = "${var.admin_username}" + admin_password = "${var.admin_password}" + } + + boot_diagnostics { + enabled = "true" + storage_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}" + } +} diff --git a/examples/azure-vm-simple-linux-managed-disk/outputs.tf b/examples/azure-vm-simple-linux-managed-disk/outputs.tf new file mode 100644 index 000000000000..9e3c2f0712bc --- /dev/null +++ b/examples/azure-vm-simple-linux-managed-disk/outputs.tf @@ -0,0 +1,11 @@ +output "hostname" { + value = "${var.hostname}" +} + +output "vm_fqdn" { + value = "${azurerm_public_ip.pip.fqdn}" +} + +output "sshCommand" { + value = "ssh ${var.admin_username}@${azurerm_public_ip.pip.fqdn}" +} diff --git a/examples/azure-vm-simple-linux-managed-disk/provider.tf b/examples/azure-vm-simple-linux-managed-disk/provider.tf new file mode 100644 index 000000000000..79291f7ca895 --- /dev/null +++ b/examples/azure-vm-simple-linux-managed-disk/provider.tf @@ -0,0 +1,7 @@ +# provider "azurerm" { +# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" +# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" +# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" +# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" +# } + diff --git a/examples/azure-vm-simple-linux-managed-disk/terraform.tfvars b/examples/azure-vm-simple-linux-managed-disk/terraform.tfvars new file mode 100644 index 000000000000..bee98e4e11bc --- /dev/null +++ b/examples/azure-vm-simple-linux-managed-disk/terraform.tfvars @@ -0,0 +1,8 @@ +# Replace with relevant values + +# resource_group = "myresourcegroup" +# rg_prefix = "rg" +# hostname = "myvm" +# dns_name = "mydnsname" +# location = "southcentralus" +# admin_password = "T3rr@f0rmP@ssword" diff --git a/examples/azure-vm-simple-linux-managed-disk/variables.tf b/examples/azure-vm-simple-linux-managed-disk/variables.tf new file mode 100644 index 000000000000..6d65a0277060 --- /dev/null +++ b/examples/azure-vm-simple-linux-managed-disk/variables.tf @@ -0,0 +1,75 @@ +variable "resource_group" { + description = "The name of the resource group in which to create the virtual network." +} + +variable "rg_prefix" { + description = "The shortened abbreviation to represent your resource group that will go on the front of some resources." + default = "rg" +} + +variable "hostname" { + description = "VM name referenced also in storage-related names." +} + +variable "dns_name" { + description = " Label for the Domain Name. Will be used to make up the FQDN. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system." +} + +variable "location" { + description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." + default = "southcentralus" +} + +variable "virtual_network_name" { + description = "The name for the virtual network." + default = "vnet" +} + +variable "address_space" { + description = "The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created." + default = "10.0.0.0/16" +} + +variable "subnet_prefix" { + description = "The address prefix to use for the subnet." + default = "10.0.10.0/24" +} + +variable "storage_account_type" { + description = "Specifies the name of the storage account. Changing this forces a new resource to be created. This must be unique across the entire Azure service, not just within the resource group." + default = "Standard_LRS" +} + +variable "vm_size" { + description = "Specifies the name of the virtual machine resource. Changing this forces a new resource to be created." + default = "Standard_A0" +} + +variable "image_publisher" { + description = "name of the publisher of the image (az vm image list)" + default = "Canonical" +} + +variable "image_offer" { + description = "the name of the offer (az vm image list)" + default = "UbuntuServer" +} + +variable "image_sku" { + description = "image sku to apply (az vm image list)" + default = "16.04-LTS" +} + +variable "image_version" { + description = "version of the image to apply (az vm image list)" + default = "latest" +} + +variable "admin_username" { + description = "administrator user name" + default = "vmadmin" +} + +variable "admin_password" { + description = "administrator password (recommended to disable password auth)" +} From ac2e5c25debc793e3c4affda1c624819ef83fd95 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Thu, 27 Apr 2017 14:28:16 -0500 Subject: [PATCH 28/67] initial commit --- .../azure-cdn-with-storage-account/README.md | 32 +++++++++++++++++ .../after_deploy.ci.sh | 9 +++++ .../deploy.ci.sh | 17 ++++++++++ .../deploy.mac.sh | 15 ++++++++ .../azure-cdn-with-storage-account/main.tf | 34 +++++++++++++++++++ .../provider.tf.example | 7 ++++ .../terraform.tfvars | 8 +++++ .../variables.tf | 27 +++++++++++++++ 8 files changed, 149 insertions(+) create mode 100644 examples/azure-cdn-with-storage-account/README.md create mode 100755 examples/azure-cdn-with-storage-account/after_deploy.ci.sh create mode 100755 examples/azure-cdn-with-storage-account/deploy.ci.sh create mode 100755 examples/azure-cdn-with-storage-account/deploy.mac.sh create mode 100644 examples/azure-cdn-with-storage-account/main.tf create mode 100644 examples/azure-cdn-with-storage-account/provider.tf.example create mode 100644 examples/azure-cdn-with-storage-account/terraform.tfvars create mode 100644 examples/azure-cdn-with-storage-account/variables.tf diff --git a/examples/azure-cdn-with-storage-account/README.md b/examples/azure-cdn-with-storage-account/README.md new file mode 100644 index 000000000000..287a09b4e7ea --- /dev/null +++ b/examples/azure-cdn-with-storage-account/README.md @@ -0,0 +1,32 @@ +# Create a CDN Profile, a CDN Endpoint with a Storage Account as origin + +This Terraform template was based on [this](https://github.com/Azure/azure-quickstart-templates/tree/master/201-cdn-with-storage-account) Azure Quickstart Template. Changes to the ARM template may have occurred since the creation of this example may not be reflected here. + + + + + +This template creates a [CDN Profile](https://docs.microsoft.com/en-us/azure/cdn/cdn-overview) and a CDN Endpoint with origin as a Storage Account. Note that user needs to create a public container in the Storage Account in order for CDN Endpoint to serve content from the Storage Account. + +# Important + +The endpoint will not immediately be available for use, as it takes time for the registration to propagate through the CDN. For Azure CDN from Akamai profiles, propagation will usually complete within one minute. For Azure CDN from Verizon profiles, propagation will usually complete within 90 minutes, but in some cases can take longer. + +Users who try to use the CDN domain name before the endpoint configuration has propagated to the POPs will receive HTTP 404 response codes. If it's been several hours since you created your endpoint and you're still receiving 404 responses, please see [Troubleshooting CDN endpoints returning 404 statuses](https://docs.microsoft.com/en-us/azure/cdn/cdn-troubleshoot-endpoint). + +## main.tf +The `main.tf` file contains the actual resources that will be deployed. It also contains the Azure Resource Group definition and any defined variables. + +## outputs.tf +This data is outputted when `terraform apply` is called, and can be queried using the `terraform output` command. + +## provider.tf +Azure requires that an application is added to Azure Active Directory to generate the `client_id`, `client_secret`, and `tenant_id` needed by Terraform (`subscription_id` can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this to populate your `provider.tf` file. + +## terraform.tfvars +If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. + +If you are committing this template to source control, please insure that you add this file to your `.gitignore` file. + +## variables.tf +The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. diff --git a/examples/azure-cdn-with-storage-account/after_deploy.ci.sh b/examples/azure-cdn-with-storage-account/after_deploy.ci.sh new file mode 100755 index 000000000000..245aba38045e --- /dev/null +++ b/examples/azure-cdn-with-storage-account/after_deploy.ci.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -o errexit -o nounset + +# cleanup deployed azure resources +docker run --rm -it \ + azuresdk/azure-cli-python \ + sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ + az group delete -y -n $KEY" diff --git a/examples/azure-cdn-with-storage-account/deploy.ci.sh b/examples/azure-cdn-with-storage-account/deploy.ci.sh new file mode 100755 index 000000000000..8fa08573faad --- /dev/null +++ b/examples/azure-cdn-with-storage-account/deploy.ci.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -o errexit -o nounset + +docker run --rm -it \ + -e ARM_CLIENT_ID \ + -e ARM_CLIENT_SECRET \ + -e ARM_SUBSCRIPTION_ID \ + -e ARM_TENANT_ID \ + -v $(pwd):/data \ + --workdir=/data \ + --entrypoint "/bin/sh" \ + hashicorp/terraform:light \ + -c "/bin/terraform get; \ + /bin/terraform validate; \ + /bin/terraform plan -out=out.tfplan -var dns_name=$KEY -var hostname=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD; \ + /bin/terraform apply out.tfplan" diff --git a/examples/azure-cdn-with-storage-account/deploy.mac.sh b/examples/azure-cdn-with-storage-account/deploy.mac.sh new file mode 100755 index 000000000000..9c6563f07d71 --- /dev/null +++ b/examples/azure-cdn-with-storage-account/deploy.mac.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -o errexit -o nounset + +if docker -v; then + + # generate a unique string for CI deployment + export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) + export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) + + /bin/sh ./deploy.ci.sh + +else + echo "Docker is used to run terraform commands, please install before run: https://docs.docker.com/docker-for-mac/install/" +fi diff --git a/examples/azure-cdn-with-storage-account/main.tf b/examples/azure-cdn-with-storage-account/main.tf new file mode 100644 index 000000000000..6073b2710690 --- /dev/null +++ b/examples/azure-cdn-with-storage-account/main.tf @@ -0,0 +1,34 @@ +resource "azurerm_resource_group" "rg" { + name = "${var.resource_group}" + location = "${var.location}" +} + +resource "azurerm_storage_account" "stor" { + name = "${var.hostname}stor" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + account_type = "${var.storage_account_type}" +} + +resource "azurerm_cdn_profile" "cdn" { + name = "${var.hostname}CdnProfile1" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + sku = "Standard_Akamai" +} + +resource "azurerm_cdn_endpoint" "cdnendpt" { + name = "${var.hostname}CdnEndpoint1" + profile_name = "${azurerm_cdn_profile.cdn.name}" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + # content_types_to_compress = ["text/plain", "text/html", "text/css", "application/x-javascript", "text/javascript"] + # is_compression_enabled = true + # is_https_allowed = false + + origin { + name = "${var.hostname}Origin1" + host_name = "vmforcdn.southcentralus.cloudapp.azure.com" + http_port = 80 + } +} diff --git a/examples/azure-cdn-with-storage-account/provider.tf.example b/examples/azure-cdn-with-storage-account/provider.tf.example new file mode 100644 index 000000000000..79291f7ca895 --- /dev/null +++ b/examples/azure-cdn-with-storage-account/provider.tf.example @@ -0,0 +1,7 @@ +# provider "azurerm" { +# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" +# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" +# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" +# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" +# } + diff --git a/examples/azure-cdn-with-storage-account/terraform.tfvars b/examples/azure-cdn-with-storage-account/terraform.tfvars new file mode 100644 index 000000000000..bee98e4e11bc --- /dev/null +++ b/examples/azure-cdn-with-storage-account/terraform.tfvars @@ -0,0 +1,8 @@ +# Replace with relevant values + +# resource_group = "myresourcegroup" +# rg_prefix = "rg" +# hostname = "myvm" +# dns_name = "mydnsname" +# location = "southcentralus" +# admin_password = "T3rr@f0rmP@ssword" diff --git a/examples/azure-cdn-with-storage-account/variables.tf b/examples/azure-cdn-with-storage-account/variables.tf new file mode 100644 index 000000000000..1793b6b24654 --- /dev/null +++ b/examples/azure-cdn-with-storage-account/variables.tf @@ -0,0 +1,27 @@ +variable "resource_group" { + description = "The name of the resource group in which to create the virtual network." +} + +variable "hostname" { + description = "VM name referenced also in storage-related names." +} + +variable "location" { + description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." + default = "southcentralus" +} + +variable "storage_account_type" { + description = "Specifies the name of the storage account. Changing this forces a new resource to be created. This must be unique across the entire Azure service, not just within the resource group." + default = "Standard_LRS" +} + +variable "admin_username" { + description = "administrator user name" + default = "vmadmin" +} + +variable "admin_password" { + description = "administrator password (recommended to disable password auth)" + default = "T3rraform!!!" +} From 935c19a25904d7ca3ff79f7e9c803698765c778a Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Thu, 27 Apr 2017 16:16:23 -0500 Subject: [PATCH 29/67] deploys locally --- .travis.yml | 8 ++------ .../azure-cdn-with-storage-account/deploy.ci.sh | 2 +- examples/azure-cdn-with-storage-account/main.tf | 15 +++++++-------- .../azure-cdn-with-storage-account/variables.tf | 17 ++++------------- 4 files changed, 14 insertions(+), 28 deletions(-) diff --git a/.travis.yml b/.travis.yml index d54a0592b1e7..5f63df3aebad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,7 @@ git: # establish environment variables env: - - TEST_DIR=examples/azure-vm-simple-linux-managed-disk - - TEST_DIR=examples/azure-vm-from-user-image + - TEST_DIR=examples/azure-cdn-with-storage-account branches: only: @@ -23,9 +22,6 @@ branches: before_deploy: - export KEY=$(cat /dev/urandom | tr -cd 'a-z' | head -c 12) - export PASSWORD=$KEY$(cat /dev/urandom | tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | tr -cd '0-9' | head -c 2) - - export EXISTING_IMAGE_URI=https://permanentstor.blob.core.windows.net/permanent-vhds/permanent-osdisk1.vhd - - export EXISTING_STORAGE_ACCOUNT_NAME=permanentstor - - export EXISTING_RESOURCE_GROUP=permanent # terraform deploy script deploy: @@ -34,7 +30,7 @@ deploy: script: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./deploy.ci.sh on: repo: 10thmagnitude/terraform - branch: master + branch: topic-201-cdn-with-storage-account # cleanup after_deploy: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./after_deploy.ci.sh diff --git a/examples/azure-cdn-with-storage-account/deploy.ci.sh b/examples/azure-cdn-with-storage-account/deploy.ci.sh index 8fa08573faad..3abf813936c6 100755 --- a/examples/azure-cdn-with-storage-account/deploy.ci.sh +++ b/examples/azure-cdn-with-storage-account/deploy.ci.sh @@ -13,5 +13,5 @@ docker run --rm -it \ hashicorp/terraform:light \ -c "/bin/terraform get; \ /bin/terraform validate; \ - /bin/terraform plan -out=out.tfplan -var dns_name=$KEY -var hostname=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD; \ + /bin/terraform plan -out=out.tfplan -var resource_group=$KEY; \ /bin/terraform apply out.tfplan" diff --git a/examples/azure-cdn-with-storage-account/main.tf b/examples/azure-cdn-with-storage-account/main.tf index 6073b2710690..d5b69558b0a7 100644 --- a/examples/azure-cdn-with-storage-account/main.tf +++ b/examples/azure-cdn-with-storage-account/main.tf @@ -4,31 +4,30 @@ resource "azurerm_resource_group" "rg" { } resource "azurerm_storage_account" "stor" { - name = "${var.hostname}stor" + name = "${var.resource_group}stor" location = "${var.location}" resource_group_name = "${azurerm_resource_group.rg.name}" account_type = "${var.storage_account_type}" } resource "azurerm_cdn_profile" "cdn" { - name = "${var.hostname}CdnProfile1" + name = "${var.resource_group}CdnProfile1" location = "${var.location}" resource_group_name = "${azurerm_resource_group.rg.name}" sku = "Standard_Akamai" } resource "azurerm_cdn_endpoint" "cdnendpt" { - name = "${var.hostname}CdnEndpoint1" + name = "${var.resource_group}CdnEndpoint1" profile_name = "${azurerm_cdn_profile.cdn.name}" location = "${var.location}" resource_group_name = "${azurerm_resource_group.rg.name}" - # content_types_to_compress = ["text/plain", "text/html", "text/css", "application/x-javascript", "text/javascript"] - # is_compression_enabled = true - # is_https_allowed = false + is_https_allowed = false origin { - name = "${var.hostname}Origin1" - host_name = "vmforcdn.southcentralus.cloudapp.azure.com" + name = "${var.resource_group}Origin1" + host_name = "${var.host_name}" http_port = 80 + https_port = 443 } } diff --git a/examples/azure-cdn-with-storage-account/variables.tf b/examples/azure-cdn-with-storage-account/variables.tf index 1793b6b24654..9b69787ef788 100644 --- a/examples/azure-cdn-with-storage-account/variables.tf +++ b/examples/azure-cdn-with-storage-account/variables.tf @@ -2,10 +2,6 @@ variable "resource_group" { description = "The name of the resource group in which to create the virtual network." } -variable "hostname" { - description = "VM name referenced also in storage-related names." -} - variable "location" { description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." default = "southcentralus" @@ -16,12 +12,7 @@ variable "storage_account_type" { default = "Standard_LRS" } -variable "admin_username" { - description = "administrator user name" - default = "vmadmin" -} - -variable "admin_password" { - description = "administrator password (recommended to disable password auth)" - default = "T3rraform!!!" -} +variable "host_name" { + description = "A string that determines the hostname/IP address of the origin server. This string could be a domain name, IPv4 address or IPv6 address." + default = "www.example.com" +} \ No newline at end of file From 66b74413034b49764f8aa6f6f620f79a6c7e8b9c Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Thu, 27 Apr 2017 16:27:21 -0500 Subject: [PATCH 30/67] updated deploy --- .../after_deploy.ci.sh | 9 --------- .../deploy.ci.sh | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 9 deletions(-) delete mode 100755 examples/azure-cdn-with-storage-account/after_deploy.ci.sh diff --git a/examples/azure-cdn-with-storage-account/after_deploy.ci.sh b/examples/azure-cdn-with-storage-account/after_deploy.ci.sh deleted file mode 100755 index 245aba38045e..000000000000 --- a/examples/azure-cdn-with-storage-account/after_deploy.ci.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -set -o errexit -o nounset - -# cleanup deployed azure resources -docker run --rm -it \ - azuresdk/azure-cli-python \ - sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ - az group delete -y -n $KEY" diff --git a/examples/azure-cdn-with-storage-account/deploy.ci.sh b/examples/azure-cdn-with-storage-account/deploy.ci.sh index 3abf813936c6..d4a8bf244087 100755 --- a/examples/azure-cdn-with-storage-account/deploy.ci.sh +++ b/examples/azure-cdn-with-storage-account/deploy.ci.sh @@ -15,3 +15,22 @@ docker run --rm -it \ /bin/terraform validate; \ /bin/terraform plan -out=out.tfplan -var resource_group=$KEY; \ /bin/terraform apply out.tfplan" + +# cleanup deployed azure resources via terraform +docker run --rm -it \ + -e ARM_CLIENT_ID \ + -e ARM_CLIENT_SECRET \ + -e ARM_SUBSCRIPTION_ID \ + -e ARM_TENANT_ID \ + -v $(pwd):/data \ + --workdir=/data \ + --entrypoint "/bin/sh" \ + hashicorp/terraform:light \ + -c "/bin/terraform destroy -force -var resource_group=$KEY;" + + +# cleanup deployed azure resources via azure-cli +# docker run --rm -it \ +# azuresdk/azure-cli-python \ +# sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ +# az group delete -y -n $KEY" \ No newline at end of file From c98b1d11780976d1bba35b3537831102f5ef475d Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Thu, 27 Apr 2017 16:29:02 -0500 Subject: [PATCH 31/67] consolidated deploy and after_deploy into a single script; simplified ci process; added os_profile_linux_config --- .../after_deploy.ci.sh | 11 ------ .../azure-vm-from-user-image/deploy.ci.sh | 36 ++++++++++--------- .../azure-vm-from-user-image/deploy.mac.sh | 2 +- examples/azure-vm-from-user-image/main.tf | 4 +++ 4 files changed, 24 insertions(+), 29 deletions(-) delete mode 100755 examples/azure-vm-from-user-image/after_deploy.ci.sh diff --git a/examples/azure-vm-from-user-image/after_deploy.ci.sh b/examples/azure-vm-from-user-image/after_deploy.ci.sh deleted file mode 100755 index 8a5624eb7681..000000000000 --- a/examples/azure-vm-from-user-image/after_deploy.ci.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -set -o errexit -o nounset - -docker run --rm -it \ - azuresdk/azure-cli-python \ - sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ - az vm delete --name $KEY --resource-group permanent -y; \ - az network nic delete --name $KEY'nic' --resource-group permanent; \ - az network vnet delete --name $KEY'vnet' --resource-group permanent; \ - az network public-ip delete --name $KEY'-ip' --resource-group permanent;" diff --git a/examples/azure-vm-from-user-image/deploy.ci.sh b/examples/azure-vm-from-user-image/deploy.ci.sh index f45c5bb2a6cd..6c315809aadf 100755 --- a/examples/azure-vm-from-user-image/deploy.ci.sh +++ b/examples/azure-vm-from-user-image/deploy.ci.sh @@ -16,22 +16,24 @@ docker run --rm -it \ /bin/terraform plan -out=out.tfplan -var hostname=$KEY -var resource_group=$EXISTING_RESOURCE_GROUP -var admin_username=$KEY -var admin_password=$PASSWORD -var image_uri=$EXISTING_IMAGE_URI -var storage_account_name=$EXISTING_STORAGE_ACCOUNT_NAME; \ /bin/terraform apply out.tfplan" -# echo "Setting git user name" -# git config user.name $GH_USER_NAME -# -# echo "Setting git user email" -# git config user.email $GH_USER_EMAIL -# -# echo "Adding git upstream remote" -# git remote add upstream "https://$GH_TOKEN@github.com/$GH_REPO.git" -# -# git checkout master +# cleanup deployed azure resources via terraform +docker run --rm -it \ + -e ARM_CLIENT_ID \ + -e ARM_CLIENT_SECRET \ + -e ARM_SUBSCRIPTION_ID \ + -e ARM_TENANT_ID \ + -v $(pwd):/data \ + --workdir=/data \ + --entrypoint "/bin/sh" \ + hashicorp/terraform:light \ + -c "/bin/terraform destroy -force -var hostname=$KEY -var resource_group=$EXISTING_RESOURCE_GROUP -var admin_username=$KEY -var admin_password=$PASSWORD -var image_uri=$EXISTING_IMAGE_URI -var storage_account_name=$EXISTING_STORAGE_ACCOUNT_NAME -target=azurerm_virtual_machine.vm -target=azurerm_network_interface.nic -target=azurerm_virtual_network.vnet -target=azurerm_public_ip.pip;" -# -# NOW=$(TZ=America/Chicago date) -# -# git commit -m "tfstate: $NOW [ci skip]" -# -# echo "Pushing changes to upstream master" -# git push upstream master +## cleanup deployed azure resources via azure-cli +# docker run --rm -it \ +# azuresdk/azure-cli-python \ +# sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ +# az vm delete --name $KEY --resource-group permanent -y; \ +# az network nic delete --name $KEY'nic' --resource-group permanent; \ +# az network vnet delete --name $KEY'vnet' --resource-group permanent; \ +# az network public-ip delete --name $KEY'-ip' --resource-group permanent;" diff --git a/examples/azure-vm-from-user-image/deploy.mac.sh b/examples/azure-vm-from-user-image/deploy.mac.sh index acd4ca7028fc..079c7f1bbe60 100755 --- a/examples/azure-vm-from-user-image/deploy.mac.sh +++ b/examples/azure-vm-from-user-image/deploy.mac.sh @@ -5,7 +5,7 @@ set -o errexit -o nounset if docker -v; then # generate a unique string for CI deployment - export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) + export KEY=jcnkmmscfkrw export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) export EXISTING_IMAGE_URI=https://permanentstor.blob.core.windows.net/permanent-vhds/permanent-osdisk1.vhd export EXISTING_STORAGE_ACCOUNT_NAME=permanentstor diff --git a/examples/azure-vm-from-user-image/main.tf b/examples/azure-vm-from-user-image/main.tf index 28edf99204b5..929a87fa7134 100644 --- a/examples/azure-vm-from-user-image/main.tf +++ b/examples/azure-vm-from-user-image/main.tf @@ -59,4 +59,8 @@ resource "azurerm_virtual_machine" "vm" { admin_username = "${var.admin_username}" admin_password = "${var.admin_password}" } + + os_profile_linux_config { + disable_password_authentication = false + } } From 0ef07bcbe131ef32663d11c36cd9f325029c7cb0 Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Thu, 27 Apr 2017 17:22:22 -0500 Subject: [PATCH 32/67] added terraform show --- examples/azure-vm-from-user-image/deploy.ci.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/azure-vm-from-user-image/deploy.ci.sh b/examples/azure-vm-from-user-image/deploy.ci.sh index 6c315809aadf..faa3086987ba 100755 --- a/examples/azure-vm-from-user-image/deploy.ci.sh +++ b/examples/azure-vm-from-user-image/deploy.ci.sh @@ -14,7 +14,8 @@ docker run --rm -it \ -c "/bin/terraform get; \ /bin/terraform validate; \ /bin/terraform plan -out=out.tfplan -var hostname=$KEY -var resource_group=$EXISTING_RESOURCE_GROUP -var admin_username=$KEY -var admin_password=$PASSWORD -var image_uri=$EXISTING_IMAGE_URI -var storage_account_name=$EXISTING_STORAGE_ACCOUNT_NAME; \ - /bin/terraform apply out.tfplan" + /bin/terraform apply out.tfplan \ + /bin/terraform show;" # cleanup deployed azure resources via terraform docker run --rm -it \ From 50e8d8a5f6777581c85d89055b5d79005a6c07f8 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Thu, 27 Apr 2017 18:12:45 -0500 Subject: [PATCH 33/67] changed to allow http & https (like ARM tmplt) --- examples/azure-cdn-with-storage-account/main.tf | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/azure-cdn-with-storage-account/main.tf b/examples/azure-cdn-with-storage-account/main.tf index d5b69558b0a7..1f9c9ecbf843 100644 --- a/examples/azure-cdn-with-storage-account/main.tf +++ b/examples/azure-cdn-with-storage-account/main.tf @@ -22,7 +22,6 @@ resource "azurerm_cdn_endpoint" "cdnendpt" { profile_name = "${azurerm_cdn_profile.cdn.name}" location = "${var.location}" resource_group_name = "${azurerm_resource_group.rg.name}" - is_https_allowed = false origin { name = "${var.resource_group}Origin1" From ace9b0bd91019122d098ba02d53c561365c4b613 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Thu, 27 Apr 2017 18:39:39 -0500 Subject: [PATCH 34/67] changed host_name & host_name variable desc --- examples/azure-cdn-with-storage-account/outputs.tf | 3 +++ examples/azure-cdn-with-storage-account/variables.tf | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 examples/azure-cdn-with-storage-account/outputs.tf diff --git a/examples/azure-cdn-with-storage-account/outputs.tf b/examples/azure-cdn-with-storage-account/outputs.tf new file mode 100644 index 000000000000..8f7c1e5c8ad1 --- /dev/null +++ b/examples/azure-cdn-with-storage-account/outputs.tf @@ -0,0 +1,3 @@ +output "CDN Endpoint ID" { + value = "${azurerm_cdn_endpoint.cdnendpt.name}.azureedge.net" +} diff --git a/examples/azure-cdn-with-storage-account/variables.tf b/examples/azure-cdn-with-storage-account/variables.tf index 9b69787ef788..be6fd57487a6 100644 --- a/examples/azure-cdn-with-storage-account/variables.tf +++ b/examples/azure-cdn-with-storage-account/variables.tf @@ -13,6 +13,6 @@ variable "storage_account_type" { } variable "host_name" { - description = "A string that determines the hostname/IP address of the origin server. This string could be a domain name, IPv4 address or IPv6 address." - default = "www.example.com" + description = "Storage account endpoint. This template requires that the user creates a public container in the Storage Account in order for CDN Endpoint to serve content from the Storage Account." + default = "https://example.blob.core.windows.net/" } \ No newline at end of file From 5e99f8166ea6961a69eddc33bcd7b16eeb4ceb1e Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Thu, 27 Apr 2017 19:09:55 -0500 Subject: [PATCH 35/67] added az cli check --- examples/azure-vm-from-user-image/deploy.ci.sh | 17 ++++++----------- examples/azure-vm-from-user-image/deploy.mac.sh | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/examples/azure-vm-from-user-image/deploy.ci.sh b/examples/azure-vm-from-user-image/deploy.ci.sh index faa3086987ba..a9f19bd7ed3e 100755 --- a/examples/azure-vm-from-user-image/deploy.ci.sh +++ b/examples/azure-vm-from-user-image/deploy.ci.sh @@ -14,9 +14,14 @@ docker run --rm -it \ -c "/bin/terraform get; \ /bin/terraform validate; \ /bin/terraform plan -out=out.tfplan -var hostname=$KEY -var resource_group=$EXISTING_RESOURCE_GROUP -var admin_username=$KEY -var admin_password=$PASSWORD -var image_uri=$EXISTING_IMAGE_URI -var storage_account_name=$EXISTING_STORAGE_ACCOUNT_NAME; \ - /bin/terraform apply out.tfplan \ + /bin/terraform apply out.tfplan; \ /bin/terraform show;" +docker run --rm -it \ + azuresdk/azure-cli-python \ + sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ + az vm show --name $KEY --resource-group permanent" + # cleanup deployed azure resources via terraform docker run --rm -it \ -e ARM_CLIENT_ID \ @@ -28,13 +33,3 @@ docker run --rm -it \ --entrypoint "/bin/sh" \ hashicorp/terraform:light \ -c "/bin/terraform destroy -force -var hostname=$KEY -var resource_group=$EXISTING_RESOURCE_GROUP -var admin_username=$KEY -var admin_password=$PASSWORD -var image_uri=$EXISTING_IMAGE_URI -var storage_account_name=$EXISTING_STORAGE_ACCOUNT_NAME -target=azurerm_virtual_machine.vm -target=azurerm_network_interface.nic -target=azurerm_virtual_network.vnet -target=azurerm_public_ip.pip;" - - -## cleanup deployed azure resources via azure-cli -# docker run --rm -it \ -# azuresdk/azure-cli-python \ -# sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ -# az vm delete --name $KEY --resource-group permanent -y; \ -# az network nic delete --name $KEY'nic' --resource-group permanent; \ -# az network vnet delete --name $KEY'vnet' --resource-group permanent; \ -# az network public-ip delete --name $KEY'-ip' --resource-group permanent;" diff --git a/examples/azure-vm-from-user-image/deploy.mac.sh b/examples/azure-vm-from-user-image/deploy.mac.sh index 079c7f1bbe60..acd4ca7028fc 100755 --- a/examples/azure-vm-from-user-image/deploy.mac.sh +++ b/examples/azure-vm-from-user-image/deploy.mac.sh @@ -5,7 +5,7 @@ set -o errexit -o nounset if docker -v; then # generate a unique string for CI deployment - export KEY=jcnkmmscfkrw + export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) export EXISTING_IMAGE_URI=https://permanentstor.blob.core.windows.net/permanent-vhds/permanent-osdisk1.vhd export EXISTING_STORAGE_ACCOUNT_NAME=permanentstor From 65826c136151f4822f72b3cad3dd60cadee1c4ac Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Thu, 27 Apr 2017 20:28:33 -0500 Subject: [PATCH 36/67] on this branch, only build test_dir; master will aggregate all the examples --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f17d0ca9930d..d7f21a323fff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,6 @@ git: # establish environment variables env: - - TEST_DIR=examples/azure-vm-simple-linux-managed-disk - TEST_DIR=examples/azure-vm-from-user-image branches: From 2675c55d5c22abb71bdd859a830f789e5f3b0eb9 Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Fri, 28 Apr 2017 10:16:01 -0500 Subject: [PATCH 37/67] merge master --- .travis.yml | 9 ++-- examples/azure-vnet-two-subnets/.gitignore | 3 ++ examples/azure-vnet-two-subnets/README.md | 22 ++++++++++ examples/azure-vnet-two-subnets/deploy.mac.sh | 14 ++++++ examples/azure-vnet-two-subnets/deploy.sh | 43 +++++++++++++++++++ examples/azure-vnet-two-subnets/main.tf | 25 +++++++++++ .../provider.tf.example | 6 +++ examples/azure-vnet-two-subnets/variables.tf | 9 ++++ 8 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 examples/azure-vnet-two-subnets/.gitignore create mode 100644 examples/azure-vnet-two-subnets/README.md create mode 100644 examples/azure-vnet-two-subnets/deploy.mac.sh create mode 100755 examples/azure-vnet-two-subnets/deploy.sh create mode 100644 examples/azure-vnet-two-subnets/main.tf create mode 100644 examples/azure-vnet-two-subnets/provider.tf.example create mode 100644 examples/azure-vnet-two-subnets/variables.tf diff --git a/.travis.yml b/.travis.yml index dc362203e9b2..33cdfdab3d17 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,13 +9,16 @@ services: language: generic +# on branches: ignore multiple commits that will queue build jobs, just run latest commit +git: + depth: 1 + # establish environment variables env: - - TEST_DIR=examples/azure-vm-simple-linux-managed-disk + - TEST_DIR=examples/azure-vnet-two-subnets branches: only: - - master - /^(?i:topic)-.*$/ # install terraform @@ -30,4 +33,4 @@ deploy: script: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./deploy.ci.sh on: repo: harijayms/terraform - branch: master + branch: topic-101-vnet-two-subnets diff --git a/examples/azure-vnet-two-subnets/.gitignore b/examples/azure-vnet-two-subnets/.gitignore new file mode 100644 index 000000000000..4893d38ff8fc --- /dev/null +++ b/examples/azure-vnet-two-subnets/.gitignore @@ -0,0 +1,3 @@ +terraform.tfstate* +provider.tf +out.tfplan diff --git a/examples/azure-vnet-two-subnets/README.md b/examples/azure-vnet-two-subnets/README.md new file mode 100644 index 000000000000..dc1c50277301 --- /dev/null +++ b/examples/azure-vnet-two-subnets/README.md @@ -0,0 +1,22 @@ +# Virtual Network with two Subnets + + + + + +This template allows you to create a Virtual Network with two subnets. + +## main.tf +The `main.tf` file contains the actual resources that will be deployed. It also contains the Azure Resource Group definition and any defined variables. + +## outputs.tf +This data is outputted when `terraform apply` is called, and can be queried using the `terraform output` command. + +## provider.tf +Azure requires that an application is added to Azure Active Directory to generate the `client_id`, `client_secret`, and `tenant_id` needed by Terraform (`subscription_id` can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this to populate your `provider.tf` file. + +## terraform.tfvars +If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. + +## variables.tf +The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. \ No newline at end of file diff --git a/examples/azure-vnet-two-subnets/deploy.mac.sh b/examples/azure-vnet-two-subnets/deploy.mac.sh new file mode 100644 index 000000000000..e712aa6aa8f1 --- /dev/null +++ b/examples/azure-vnet-two-subnets/deploy.mac.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -o errexit -o nounset + +# generate a unique string for CI deployment +export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) +export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) + +/bin/sh ./deploy.sh + +# docker run --rm -it \ +# azuresdk/azure-cli-python \ +# sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ +# az group delete -y -n $KEY" \ No newline at end of file diff --git a/examples/azure-vnet-two-subnets/deploy.sh b/examples/azure-vnet-two-subnets/deploy.sh new file mode 100755 index 000000000000..c267eaea7986 --- /dev/null +++ b/examples/azure-vnet-two-subnets/deploy.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +set -o errexit -o nounset + +# generate a unique string for CI deployment +# KEY=$(cat /dev/urandom | tr -cd 'a-z' | head -c 12) +# PASSWORD=$KEY$(cat /dev/urandom | tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | tr -cd '0-9' | head -c 2) + +docker run --rm -it \ + -e ARM_CLIENT_ID \ + -e ARM_CLIENT_SECRET \ + -e ARM_SUBSCRIPTION_ID \ + -e ARM_TENANT_ID \ + -v $(pwd):/data \ + --entrypoint "/bin/sh" \ + hashicorp/terraform:light \ + -c "cd /data; \ + /bin/terraform get; \ + /bin/terraform validate; \ + /bin/terraform plan -out=out.tfplan; \ + /bin/terraform apply out.tfplan" + +# TODO: determine external validation, possibly Azure CLI + +# echo "Setting git user name" +# git config user.name $GH_USER_NAME +# +# echo "Setting git user email" +# git config user.email $GH_USER_EMAIL +# +# echo "Adding git upstream remote" +# git remote add upstream "https://$GH_TOKEN@github.com/$GH_REPO.git" +# +# git checkout master + + +# +# NOW=$(TZ=America/Chicago date) +# +# git commit -m "tfstate: $NOW [ci skip]" +# +# echo "Pushing changes to upstream master" +# git push upstream master \ No newline at end of file diff --git a/examples/azure-vnet-two-subnets/main.tf b/examples/azure-vnet-two-subnets/main.tf new file mode 100644 index 000000000000..eb83a4bf35b3 --- /dev/null +++ b/examples/azure-vnet-two-subnets/main.tf @@ -0,0 +1,25 @@ +resource "azurerm_resource_group" "rg" { + name = "${var.resource_group}" + location = "${var.location}" +} + +resource "azurerm_virtual_network" "vnet" { + name = "${var.resource_group}vnet" + location = "${var.location}" + address_space = ["10.0.0.0/16"] + resource_group_name = "${azurerm_resource_group.rg.name}" +} + +resource "azurerm_subnet" "subnet1" { + name = "${var.resource_group}subnet1" + virtual_network_name = "${azurerm_virtual_network.vnet.name}" + resource_group_name = "${azurerm_resource_group.rg.name}" + address_prefix = "10.0.0.0/24" +} + +resource "azurerm_subnet" "subnet2" { + name = "${var.resource_group}subnet2" + virtual_network_name = "${azurerm_virtual_network.vnet.name}" + resource_group_name = "${azurerm_resource_group.rg.name}" + address_prefix = "10.0.1.0/24" +} diff --git a/examples/azure-vnet-two-subnets/provider.tf.example b/examples/azure-vnet-two-subnets/provider.tf.example new file mode 100644 index 000000000000..327ceb55eefa --- /dev/null +++ b/examples/azure-vnet-two-subnets/provider.tf.example @@ -0,0 +1,6 @@ +provider "azurerm" { + subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" + client_id = "REPLACE-WITH-YOUR-CLIENT-ID" + client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" + tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" +} \ No newline at end of file diff --git a/examples/azure-vnet-two-subnets/variables.tf b/examples/azure-vnet-two-subnets/variables.tf new file mode 100644 index 000000000000..ee64ffbc28e2 --- /dev/null +++ b/examples/azure-vnet-two-subnets/variables.tf @@ -0,0 +1,9 @@ +variable "resource_group" { + description = "The name of the resource group in which to create the virtual network." + default = "rg2" +} + +variable "location" { + description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." + default = "southcentralus" +} From 072a51a9845b72754142436bde091409676885b7 Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Fri, 28 Apr 2017 10:44:07 -0500 Subject: [PATCH 38/67] added new constructs/naming for deploy scripts, etc. --- examples/azure-vnet-two-subnets/deploy.ci.sh | 41 ++++++++++++++++++ examples/azure-vnet-two-subnets/deploy.mac.sh | 17 ++++---- examples/azure-vnet-two-subnets/deploy.sh | 43 ------------------- examples/azure-vnet-two-subnets/main.tf | 4 +- examples/azure-vnet-two-subnets/variables.tf | 1 - 5 files changed, 52 insertions(+), 54 deletions(-) create mode 100755 examples/azure-vnet-two-subnets/deploy.ci.sh mode change 100644 => 100755 examples/azure-vnet-two-subnets/deploy.mac.sh delete mode 100755 examples/azure-vnet-two-subnets/deploy.sh diff --git a/examples/azure-vnet-two-subnets/deploy.ci.sh b/examples/azure-vnet-two-subnets/deploy.ci.sh new file mode 100755 index 000000000000..b77b4135cf3a --- /dev/null +++ b/examples/azure-vnet-two-subnets/deploy.ci.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +set -o errexit -o nounset + +# generate a unique string for CI deployment +# KEY=$(cat /dev/urandom | tr -cd 'a-z' | head -c 12) +# PASSWORD=$KEY$(cat /dev/urandom | tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | tr -cd '0-9' | head -c 2) + +docker run --rm -it \ + -e ARM_CLIENT_ID \ + -e ARM_CLIENT_SECRET \ + -e ARM_SUBSCRIPTION_ID \ + -e ARM_TENANT_ID \ + -v $(pwd):/data \ + --workdir=/data \ + --entrypoint "/bin/sh" \ + hashicorp/terraform:light \ + -c "/bin/terraform get; \ + /bin/terraform validate; \ + /bin/terraform plan -out=out.tfplan -var resource_group=$KEY; \ + /bin/terraform apply out.tfplan; \ + /bin/terraform show;" + +# check that resources exist via azure cli +docker run --rm -it \ + azuresdk/azure-cli-python \ + sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ + az network vnet subnet show -n subnet1 -g $KEY --vnet-name '$KEY'vnet; \ + az network vnet subnet show -n subnet2 -g $KEY --vnet-name '$KEY'vnet;" + +# cleanup deployed azure resources via terraform +docker run --rm -it \ + -e ARM_CLIENT_ID \ + -e ARM_CLIENT_SECRET \ + -e ARM_SUBSCRIPTION_ID \ + -e ARM_TENANT_ID \ + -v $(pwd):/data \ + --workdir=/data \ + --entrypoint "/bin/sh" \ + hashicorp/terraform:light \ + -c "/bin/terraform destroy -force -var resource_group=$KEY;" diff --git a/examples/azure-vnet-two-subnets/deploy.mac.sh b/examples/azure-vnet-two-subnets/deploy.mac.sh old mode 100644 new mode 100755 index e712aa6aa8f1..9c6563f07d71 --- a/examples/azure-vnet-two-subnets/deploy.mac.sh +++ b/examples/azure-vnet-two-subnets/deploy.mac.sh @@ -2,13 +2,14 @@ set -o errexit -o nounset -# generate a unique string for CI deployment -export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) -export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) +if docker -v; then -/bin/sh ./deploy.sh + # generate a unique string for CI deployment + export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) + export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) -# docker run --rm -it \ -# azuresdk/azure-cli-python \ -# sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ -# az group delete -y -n $KEY" \ No newline at end of file + /bin/sh ./deploy.ci.sh + +else + echo "Docker is used to run terraform commands, please install before run: https://docs.docker.com/docker-for-mac/install/" +fi diff --git a/examples/azure-vnet-two-subnets/deploy.sh b/examples/azure-vnet-two-subnets/deploy.sh deleted file mode 100755 index c267eaea7986..000000000000 --- a/examples/azure-vnet-two-subnets/deploy.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -set -o errexit -o nounset - -# generate a unique string for CI deployment -# KEY=$(cat /dev/urandom | tr -cd 'a-z' | head -c 12) -# PASSWORD=$KEY$(cat /dev/urandom | tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | tr -cd '0-9' | head -c 2) - -docker run --rm -it \ - -e ARM_CLIENT_ID \ - -e ARM_CLIENT_SECRET \ - -e ARM_SUBSCRIPTION_ID \ - -e ARM_TENANT_ID \ - -v $(pwd):/data \ - --entrypoint "/bin/sh" \ - hashicorp/terraform:light \ - -c "cd /data; \ - /bin/terraform get; \ - /bin/terraform validate; \ - /bin/terraform plan -out=out.tfplan; \ - /bin/terraform apply out.tfplan" - -# TODO: determine external validation, possibly Azure CLI - -# echo "Setting git user name" -# git config user.name $GH_USER_NAME -# -# echo "Setting git user email" -# git config user.email $GH_USER_EMAIL -# -# echo "Adding git upstream remote" -# git remote add upstream "https://$GH_TOKEN@github.com/$GH_REPO.git" -# -# git checkout master - - -# -# NOW=$(TZ=America/Chicago date) -# -# git commit -m "tfstate: $NOW [ci skip]" -# -# echo "Pushing changes to upstream master" -# git push upstream master \ No newline at end of file diff --git a/examples/azure-vnet-two-subnets/main.tf b/examples/azure-vnet-two-subnets/main.tf index eb83a4bf35b3..ef74357ce256 100644 --- a/examples/azure-vnet-two-subnets/main.tf +++ b/examples/azure-vnet-two-subnets/main.tf @@ -11,14 +11,14 @@ resource "azurerm_virtual_network" "vnet" { } resource "azurerm_subnet" "subnet1" { - name = "${var.resource_group}subnet1" + name = "subnet1" virtual_network_name = "${azurerm_virtual_network.vnet.name}" resource_group_name = "${azurerm_resource_group.rg.name}" address_prefix = "10.0.0.0/24" } resource "azurerm_subnet" "subnet2" { - name = "${var.resource_group}subnet2" + name = "subnet2" virtual_network_name = "${azurerm_virtual_network.vnet.name}" resource_group_name = "${azurerm_resource_group.rg.name}" address_prefix = "10.0.1.0/24" diff --git a/examples/azure-vnet-two-subnets/variables.tf b/examples/azure-vnet-two-subnets/variables.tf index ee64ffbc28e2..8d5dd4131636 100644 --- a/examples/azure-vnet-two-subnets/variables.tf +++ b/examples/azure-vnet-two-subnets/variables.tf @@ -1,6 +1,5 @@ variable "resource_group" { description = "The name of the resource group in which to create the virtual network." - default = "rg2" } variable "location" { From 06f1a7f11258b912c56006a69bdf2fcb99397dc8 Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Fri, 28 Apr 2017 11:59:31 -0500 Subject: [PATCH 39/67] suppress az login output --- examples/azure-vnet-two-subnets/deploy.ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/azure-vnet-two-subnets/deploy.ci.sh b/examples/azure-vnet-two-subnets/deploy.ci.sh index b77b4135cf3a..ee119ea4269f 100755 --- a/examples/azure-vnet-two-subnets/deploy.ci.sh +++ b/examples/azure-vnet-two-subnets/deploy.ci.sh @@ -24,7 +24,7 @@ docker run --rm -it \ # check that resources exist via azure cli docker run --rm -it \ azuresdk/azure-cli-python \ - sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ + sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID > /dev/null; \ az network vnet subnet show -n subnet1 -g $KEY --vnet-name '$KEY'vnet; \ az network vnet subnet show -n subnet2 -g $KEY --vnet-name '$KEY'vnet;" From 3523ed3305ff908e242388946298c823ec694863 Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Fri, 28 Apr 2017 12:40:25 -0500 Subject: [PATCH 40/67] suppress az login output --- examples/azure-vm-from-user-image/deploy.ci.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/examples/azure-vm-from-user-image/deploy.ci.sh b/examples/azure-vm-from-user-image/deploy.ci.sh index a9f19bd7ed3e..cf4a9c814511 100755 --- a/examples/azure-vm-from-user-image/deploy.ci.sh +++ b/examples/azure-vm-from-user-image/deploy.ci.sh @@ -19,7 +19,7 @@ docker run --rm -it \ docker run --rm -it \ azuresdk/azure-cli-python \ - sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ + sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID > /dev/null; \ az vm show --name $KEY --resource-group permanent" # cleanup deployed azure resources via terraform @@ -32,4 +32,14 @@ docker run --rm -it \ --workdir=/data \ --entrypoint "/bin/sh" \ hashicorp/terraform:light \ - -c "/bin/terraform destroy -force -var hostname=$KEY -var resource_group=$EXISTING_RESOURCE_GROUP -var admin_username=$KEY -var admin_password=$PASSWORD -var image_uri=$EXISTING_IMAGE_URI -var storage_account_name=$EXISTING_STORAGE_ACCOUNT_NAME -target=azurerm_virtual_machine.vm -target=azurerm_network_interface.nic -target=azurerm_virtual_network.vnet -target=azurerm_public_ip.pip;" + -c "/bin/terraform destroy -force \ + -var hostname=$KEY + -var resource_group=$EXISTING_RESOURCE_GROUP + -var admin_username=$KEY + -var admin_password=$PASSWORD + -var image_uri=$EXISTING_IMAGE_URI + -var storage_account_name=$EXISTING_STORAGE_ACCOUNT_NAME + -target=azurerm_virtual_machine.vm + -target=azurerm_network_interface.nic + -target=azurerm_virtual_network.vnet + -target=azurerm_public_ip.pip;" From 21837a95bc02adca7b4d9dfb357a65499b8d24d5 Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Fri, 28 Apr 2017 12:47:10 -0500 Subject: [PATCH 41/67] forgot about line breaks --- examples/azure-vm-from-user-image/deploy.ci.sh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/azure-vm-from-user-image/deploy.ci.sh b/examples/azure-vm-from-user-image/deploy.ci.sh index cf4a9c814511..37578ed7db12 100755 --- a/examples/azure-vm-from-user-image/deploy.ci.sh +++ b/examples/azure-vm-from-user-image/deploy.ci.sh @@ -33,13 +33,13 @@ docker run --rm -it \ --entrypoint "/bin/sh" \ hashicorp/terraform:light \ -c "/bin/terraform destroy -force \ - -var hostname=$KEY - -var resource_group=$EXISTING_RESOURCE_GROUP - -var admin_username=$KEY - -var admin_password=$PASSWORD - -var image_uri=$EXISTING_IMAGE_URI - -var storage_account_name=$EXISTING_STORAGE_ACCOUNT_NAME - -target=azurerm_virtual_machine.vm - -target=azurerm_network_interface.nic - -target=azurerm_virtual_network.vnet + -var hostname=$KEY \ + -var resource_group=$EXISTING_RESOURCE_GROUP \ + -var admin_username=$KEY \ + -var admin_password=$PASSWORD \ + -var image_uri=$EXISTING_IMAGE_URI \ + -var storage_account_name=$EXISTING_STORAGE_ACCOUNT_NAME \ + -target=azurerm_virtual_machine.vm \ + -target=azurerm_network_interface.nic \ + -target=azurerm_virtual_network.vnet \ -target=azurerm_public_ip.pip;" From e1e3aa2cdfd18ece7f3e7edb292785470d85ad7d Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Fri, 28 Apr 2017 13:34:56 -0500 Subject: [PATCH 42/67] breaking build as an example --- examples/azure-vm-from-user-image/deploy.ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/azure-vm-from-user-image/deploy.ci.sh b/examples/azure-vm-from-user-image/deploy.ci.sh index 37578ed7db12..1f0c4782d210 100755 --- a/examples/azure-vm-from-user-image/deploy.ci.sh +++ b/examples/azure-vm-from-user-image/deploy.ci.sh @@ -19,7 +19,7 @@ docker run --rm -it \ docker run --rm -it \ azuresdk/azure-cli-python \ - sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID > /dev/null; \ + sh -c "az login --service-principal -u $ARM_CLIENT_ID -p WRONG --tenant $ARM_TENANT_ID > /dev/null; \ az vm show --name $KEY --resource-group permanent" # cleanup deployed azure resources via terraform From a0a7643e081dcab1a93433fbc86e822a1aacfebf Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Fri, 28 Apr 2017 13:40:41 -0500 Subject: [PATCH 43/67] fixing broken build example --- examples/azure-vm-from-user-image/deploy.ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/azure-vm-from-user-image/deploy.ci.sh b/examples/azure-vm-from-user-image/deploy.ci.sh index 1f0c4782d210..37578ed7db12 100755 --- a/examples/azure-vm-from-user-image/deploy.ci.sh +++ b/examples/azure-vm-from-user-image/deploy.ci.sh @@ -19,7 +19,7 @@ docker run --rm -it \ docker run --rm -it \ azuresdk/azure-cli-python \ - sh -c "az login --service-principal -u $ARM_CLIENT_ID -p WRONG --tenant $ARM_TENANT_ID > /dev/null; \ + sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID > /dev/null; \ az vm show --name $KEY --resource-group permanent" # cleanup deployed azure resources via terraform From e0f9c39ff5bf1d2966d5355bfbc97f57d7879bb4 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Mon, 1 May 2017 10:23:34 -0500 Subject: [PATCH 44/67] work in progress --- .travis.yml | 5 +- examples/azure-traffic-manager-vm/README.md | 31 ++++ .../azure-traffic-manager-vm/deploy.ci.sh | 36 ++++ .../azure-traffic-manager-vm/deploy.mac.sh | 15 ++ examples/azure-traffic-manager-vm/main.tf | 160 ++++++++++++++++++ examples/azure-traffic-manager-vm/outputs.tf | 11 ++ .../provider.tf.example | 7 + .../azure-traffic-manager-vm/terraform.tfvars | 8 + .../azure-traffic-manager-vm/variables.tf | 94 ++++++++++ 9 files changed, 364 insertions(+), 3 deletions(-) create mode 100644 examples/azure-traffic-manager-vm/README.md create mode 100755 examples/azure-traffic-manager-vm/deploy.ci.sh create mode 100755 examples/azure-traffic-manager-vm/deploy.mac.sh create mode 100644 examples/azure-traffic-manager-vm/main.tf create mode 100644 examples/azure-traffic-manager-vm/outputs.tf create mode 100644 examples/azure-traffic-manager-vm/provider.tf.example create mode 100644 examples/azure-traffic-manager-vm/terraform.tfvars create mode 100644 examples/azure-traffic-manager-vm/variables.tf diff --git a/.travis.yml b/.travis.yml index dc362203e9b2..b611c7f098af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,11 +11,10 @@ language: generic # establish environment variables env: - - TEST_DIR=examples/azure-vm-simple-linux-managed-disk + - TEST_DIR=examples/azure-traffic-manager-vm branches: only: - - master - /^(?i:topic)-.*$/ # install terraform @@ -30,4 +29,4 @@ deploy: script: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./deploy.ci.sh on: repo: harijayms/terraform - branch: master + branch: topic-201-traffic-manager-vm diff --git a/examples/azure-traffic-manager-vm/README.md b/examples/azure-traffic-manager-vm/README.md new file mode 100644 index 000000000000..865d947c568f --- /dev/null +++ b/examples/azure-traffic-manager-vm/README.md @@ -0,0 +1,31 @@ +# Azure Traffic Manager with virtual machines + +This Terraform template was based on [this](https://github.com/Azure/azure-quickstart-templates/tree/master/201-traffic-manager-vm) Azure Quickstart Template. Changes to the ARM template may have occurred since the creation of this example may not be reflected here. + + + + + +This template shows how to create an Azure Traffic Manager profile to load-balance across a couple of Azure virtual machines. Each endpoint has an equal weight but different weights can be specified to distribute load non-uniformly. + +See also: + +- Traffic Manager routing methods for details of the different routing methods available. +- Create or update a Traffic Manager profile for details of the JSON elements relating to a Traffic Manager profile. + +## main.tf +The `main.tf` file contains the actual resources that will be deployed. It also contains the Azure Resource Group definition and any defined variables. + +## outputs.tf +This data is outputted when `terraform apply` is called, and can be queried using the `terraform output` command. + +## provider.tf +Azure requires that an application is added to Azure Active Directory to generate the `client_id`, `client_secret`, and `tenant_id` needed by Terraform (`subscription_id` can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this to populate your `provider.tf` file. + +## terraform.tfvars +If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. + +If you are committing this template to source control, please insure that you add this file to your `.gitignore` file. + +## variables.tf +The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. diff --git a/examples/azure-traffic-manager-vm/deploy.ci.sh b/examples/azure-traffic-manager-vm/deploy.ci.sh new file mode 100755 index 000000000000..9dc30a379b89 --- /dev/null +++ b/examples/azure-traffic-manager-vm/deploy.ci.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -o errexit -o nounset + +docker run --rm -it \ + -e ARM_CLIENT_ID \ + -e ARM_CLIENT_SECRET \ + -e ARM_SUBSCRIPTION_ID \ + -e ARM_TENANT_ID \ + -v $(pwd):/data \ + --workdir=/data \ + --entrypoint "/bin/sh" \ + hashicorp/terraform:light \ + -c "/bin/terraform get; \ + /bin/terraform validate; \ + /bin/terraform plan -out=out.tfplan -var unique_dns_name=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD; \ + /bin/terraform apply out.tfplan; \ + /bin/terraform show;" + +# cleanup deployed azure resources via azure-cli +docker run --rm -it \ + azuresdk/azure-cli-python \ + sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID > /dev/null; \ + az vm show -g $KEY -n rgvm" + +# cleanup deployed azure resources via terraform +docker run --rm -it \ + -e ARM_CLIENT_ID \ + -e ARM_CLIENT_SECRET \ + -e ARM_SUBSCRIPTION_ID \ + -e ARM_TENANT_ID \ + -v $(pwd):/data \ + --workdir=/data \ + --entrypoint "/bin/sh" \ + hashicorp/terraform:light \ + -c "/bin/terraform destroy -force -var unique_dns_name=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD;" diff --git a/examples/azure-traffic-manager-vm/deploy.mac.sh b/examples/azure-traffic-manager-vm/deploy.mac.sh new file mode 100755 index 000000000000..9c6563f07d71 --- /dev/null +++ b/examples/azure-traffic-manager-vm/deploy.mac.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -o errexit -o nounset + +if docker -v; then + + # generate a unique string for CI deployment + export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) + export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) + + /bin/sh ./deploy.ci.sh + +else + echo "Docker is used to run terraform commands, please install before run: https://docs.docker.com/docker-for-mac/install/" +fi diff --git a/examples/azure-traffic-manager-vm/main.tf b/examples/azure-traffic-manager-vm/main.tf new file mode 100644 index 000000000000..8317980877f7 --- /dev/null +++ b/examples/azure-traffic-manager-vm/main.tf @@ -0,0 +1,160 @@ +resource "azurerm_resource_group" "rg" { + name = "${var.resource_group}" + location = "${var.location}" +} + +resource "azurerm_public_ip" "pip" { + name = "ip${count.index}" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + public_ip_address_allocation = "dynamic" + domain_name_label = "${var.dns_name}${count.index}" + count = "${var.num_vms}" +} + +resource "azurerm_virtual_network" "vnet" { + name = "${var.virtual_network_name}" + location = "${var.location}" + address_space = ["${var.address_space}"] + resource_group_name = "${azurerm_resource_group.rg.name}" +} + +resource "azurerm_subnet" "subnet" { + name = "${var.rg_prefix}subnet" + virtual_network_name = "${azurerm_virtual_network.vnet.name}" + resource_group_name = "${azurerm_resource_group.rg.name}" + address_prefix = "${var.subnet_prefix}" +} + +resource "azurerm_network_interface" "nic" { + name = "nic${count.index}" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + count = "${var.num_vms}" + + ip_configuration { + name = "ipconfig${count.index}" + subnet_id = "${azurerm_subnet.subnet.id}" + private_ip_address_allocation = "Dynamic" + public_ip_address_id = ["${element(azurerm_public_ip.pip.*.id, count.index)}"] + } +} + +resource "azurerm_storage_account" "stor" { + name = "${var.dns_name}stor" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + account_type = "${var.storage_account_type}" +} + +resource "azurerm_virtual_machine" "vm" { + name = "vm${count.index}" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + vm_size = "${var.vm_size}" + network_interface_ids = ["${element(azurerm_network_interface.nic.*.id, count.index)}"] + + storage_image_reference { + publisher = "${var.image_publisher}" + offer = "${var.image_offer}" + sku = "${var.image_sku}" + version = "${var.image_version}" + } + + storage_os_disk { + name = "osdisk${count.index}" + create_option = "FromImage" + } + + os_profile { + computer_name = "${var.hostname}" + admin_username = "${var.admin_username}" + admin_password = "${var.admin_password}" + } + + os_profile_linux_config { + disable_password_authentication = false + } + + boot_diagnostics { + enabled = "true" + storage_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}" + } +} + + "type": "Microsoft.Compute/virtualMachines/extensions", + "name": "[concat(variables('vmName'), copyIndex(), '/installcustomscript')]", + "copy": { + "name": "extLoop", + "count": "[variables('numVMs')]" + }, + "apiVersion": "[variables('apiVersion')]", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[concat('Microsoft.Compute/virtualMachines/', variables('vmName'), copyIndex())]" + ], + "properties": { + "publisher": "Microsoft.Azure.Extensions", + "type": "CustomScript", + "typeHandlerVersion": "2.0", + "autoUpgradeMinorVersion": true, + "settings": { + "commandToExecute": "sudo bash -c 'apt-get update && apt-get -y install apache2' " + } + } + }, + { + "apiVersion": "[variables('tmApiVersion')]", + "type": "Microsoft.Network/trafficManagerProfiles", + "name": "VMEndpointExample", + "location": "global", + "dependsOn": [ + "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'), '0')]", + "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'), '1')]", + "[concat('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'), '2')]" + ], + "properties": { + "profileStatus": "Enabled", + "trafficRoutingMethod": "Weighted", + "dnsConfig": { + "relativeName": "[parameters('uniqueDnsName')]", + "ttl": 30 + }, + "monitorConfig": { + "protocol": "http", + "port": 80, + "path": "/" + }, + "endpoints": [ + { + "name": "endpoint0", + "type": "Microsoft.Network/trafficManagerProfiles/azureEndpoints", + "properties": { + "targetResourceId": "[resourceId('Microsoft.Network/publicIPAddresses',concat(variables('publicIPAddressName'), 0))]", + "endpointStatus": "Enabled", + "weight": 1 + } + }, + { + "name": "endpoint1", + "type": "Microsoft.Network/trafficManagerProfiles/azureEndpoints", + "properties": { + "targetResourceId": "[resourceId('Microsoft.Network/publicIPAddresses',concat(variables('publicIPAddressName'), 1))]", + "endpointStatus": "Enabled", + "weight": 1 + } + }, + { + "name": "endpoint2", + "type": "Microsoft.Network/trafficManagerProfiles/azureEndpoints", + "properties": { + "targetResourceId": "[resourceId('Microsoft.Network/publicIPAddresses',concat(variables('publicIPAddressName'), 2))]", + "endpointStatus": "Enabled", + "weight": 1 + } + } + ] + } + } + ] +} \ No newline at end of file diff --git a/examples/azure-traffic-manager-vm/outputs.tf b/examples/azure-traffic-manager-vm/outputs.tf new file mode 100644 index 000000000000..9e3c2f0712bc --- /dev/null +++ b/examples/azure-traffic-manager-vm/outputs.tf @@ -0,0 +1,11 @@ +output "hostname" { + value = "${var.hostname}" +} + +output "vm_fqdn" { + value = "${azurerm_public_ip.pip.fqdn}" +} + +output "sshCommand" { + value = "ssh ${var.admin_username}@${azurerm_public_ip.pip.fqdn}" +} diff --git a/examples/azure-traffic-manager-vm/provider.tf.example b/examples/azure-traffic-manager-vm/provider.tf.example new file mode 100644 index 000000000000..79291f7ca895 --- /dev/null +++ b/examples/azure-traffic-manager-vm/provider.tf.example @@ -0,0 +1,7 @@ +# provider "azurerm" { +# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" +# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" +# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" +# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" +# } + diff --git a/examples/azure-traffic-manager-vm/terraform.tfvars b/examples/azure-traffic-manager-vm/terraform.tfvars new file mode 100644 index 000000000000..bee98e4e11bc --- /dev/null +++ b/examples/azure-traffic-manager-vm/terraform.tfvars @@ -0,0 +1,8 @@ +# Replace with relevant values + +# resource_group = "myresourcegroup" +# rg_prefix = "rg" +# hostname = "myvm" +# dns_name = "mydnsname" +# location = "southcentralus" +# admin_password = "T3rr@f0rmP@ssword" diff --git a/examples/azure-traffic-manager-vm/variables.tf b/examples/azure-traffic-manager-vm/variables.tf new file mode 100644 index 000000000000..92085e343055 --- /dev/null +++ b/examples/azure-traffic-manager-vm/variables.tf @@ -0,0 +1,94 @@ +variable "resource_group" { + description = "The name of the resource group in which to create the virtual network" +} + +variable "location" { + description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." + default = "southcentralus" +} + +variable "unique_dns_name" { + description = "Relative DNS name for the traffic manager profile, resulting FQDN will be .trafficmanager.net, must be globally unique." +} + +variable "vnet" { + description = "The name of virtual network" + default = "vnet" +} + +variable "num_vms" { + description = "The number of virtual machines you will provision. This variable is also used for NICs and PIPs in this Terraform script." + default = "3" +} +variable "address_space" { + description = "The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created." + default = "10.0.0.0/16" +} + +variable "subnet_name" { + description = "The name of the subnet" + default = "subnet" +} + +variable "subnet_prefix" { + description = "The address prefix to use for the subnet" + default = "10.0.0.0/24" +} + +variable "public_ip_name" { + description = "The name of the public ip address" + default = "pip" +} + +variable "public_ip_type" { + description = "Specifies whether the public ip address is dynamic or static" + default = "dynamic" +} + +variable "nic_name" { + description = "The name of the nic" + default = "nic" +} + +variable "storage_account_type" { + description = "Defines the type of storage account to be created. Valid options are Standard_LRS, Standard_ZRS, Standard_GRS, Standard_RAGRS, Premium_LRS. Changing this is sometimes valid - see the Azure documentation for more information on which types of accounts can be converted into other types." + default = "Standard_LRS" +} + +variable "vm_name" { + description = "The name of the virtual machine" + default = "MyUbuntuVm" +} + +variable "vm_size" { + description = "The size of the virtual machine" + default = "Standard_D1" +} + +variable "image_publisher" { + description = "The name of the publisher of the image (az vm image list)" + default = "Canonical" +} + +variable "image_offer" { + description = "The name of the offer (az vm image list)" + default = "UbuntuServer" +} + +variable "image_sku" { + description = "The Ubuntu version for the VM. This will pick a fully patched image of this given Ubuntu version. Allowed values: 12.04.5-LTS, 14.04.2-LTS, 15.10." + default = "14.04.2-LTS" +} + +variable "image_version" { + description = "the version of the image to apply (az vm image list)" + default = "latest" +} + +variable "admin_username" { + description = "Username for virtual machines" +} + +variable "admin_password" { + description = "Password for virtual machines" +} From 2c02115a92c4e25648a91882457ce7b3165f585c Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Mon, 1 May 2017 10:35:36 -0500 Subject: [PATCH 45/67] merge of CI config --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 33cdfdab3d17..86488119349f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,10 +15,12 @@ git: # establish environment variables env: + - TEST_DIR=examples/azure-vm-simple-linux-managed-disk - TEST_DIR=examples/azure-vnet-two-subnets branches: only: + - master - /^(?i:topic)-.*$/ # install terraform @@ -33,4 +35,4 @@ deploy: script: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./deploy.ci.sh on: repo: harijayms/terraform - branch: topic-101-vnet-two-subnets + branch: master From 487d9cd1eeeeb4a03d0dcaa08fd07d21580b8b7b Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Mon, 1 May 2017 15:58:45 -0500 Subject: [PATCH 46/67] fixed grammar in readme --- examples/azure-vm-from-user-image/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/azure-vm-from-user-image/README.md b/examples/azure-vm-from-user-image/README.md index 455b0bcb1a5f..7f575a7926de 100644 --- a/examples/azure-vm-from-user-image/README.md +++ b/examples/azure-vm-from-user-image/README.md @@ -1,6 +1,6 @@ # [Create a Virtual Machine from a User Image](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/cli-deploy-templates#create-a-custom-vm-image) -This Terraform template was based on [this](https://github.com/Azure/azure-quickstart-templates/tree/master/101-vm-from-user-image) Azure Quickstart Template. Changes to the ARM template may have occured since the creation of this example may not be reflected here. +This Terraform template was based on [this](https://github.com/Azure/azure-quickstart-templates/tree/master/101-vm-from-user-image) Azure Quickstart Template. Changes to the ARM template that may have occured since the creation of this example may not be reflected here. @@ -11,7 +11,7 @@ This Terraform template was based on [this](https://github.com/Azure/azure-quick This template allows you to create a Virtual Machine from an unmanaged User image vhd. This template also deploys a Virtual Network, Public IP addresses and a Network Interface. ## main.tf -The `main.tf` file contains the actual resources that will be deployed. It also contains the Azure Resource Group definition and any defined variables. +The `main.tf` file contains the actual resources that will be deployed. It also contains the Azure Resource Group definition and any defined variables. ## outputs.tf This data is outputted when `terraform apply` is called, and can be queried using the `terraform output` command. @@ -20,7 +20,7 @@ This data is outputted when `terraform apply` is called, and can be queried usin Azure requires that an application is added to Azure Active Directory to generate the `client_id`, `client_secret`, and `tenant_id` needed by Terraform (`subscription_id` can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this to populate your `provider.tf` file. ## terraform.tfvars -If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. +If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. If you are committing this template to source control, please insure that you add this file to your `.gitignore` file. From d8ef7b121ce959e8363937085896d40367542d92 Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Mon, 1 May 2017 18:14:53 -0500 Subject: [PATCH 47/67] prep for PR --- .travis.yml | 1 - examples/azure-cdn-with-storage-account/README.md | 2 +- examples/azure-cdn-with-storage-account/deploy.ci.sh | 6 ++++-- examples/azure-cdn-with-storage-account/deploy.mac.sh | 1 - 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index f9dec25566ff..42e207da6b9c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,6 @@ branches: before_deploy: - export KEY=$(cat /dev/urandom | tr -cd 'a-z' | head -c 12) - export PASSWORD=$KEY$(cat /dev/urandom | tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | tr -cd '0-9' | head -c 2) - - export VM_HOST_NAME=anniecdn.southcentralus.cloudapp.azure.com # terraform deploy + script deploy: diff --git a/examples/azure-cdn-with-storage-account/README.md b/examples/azure-cdn-with-storage-account/README.md index 287a09b4e7ea..6536f729385c 100644 --- a/examples/azure-cdn-with-storage-account/README.md +++ b/examples/azure-cdn-with-storage-account/README.md @@ -1,6 +1,6 @@ # Create a CDN Profile, a CDN Endpoint with a Storage Account as origin -This Terraform template was based on [this](https://github.com/Azure/azure-quickstart-templates/tree/master/201-cdn-with-storage-account) Azure Quickstart Template. Changes to the ARM template may have occurred since the creation of this example may not be reflected here. +This Terraform template was based on [this](https://github.com/Azure/azure-quickstart-templates/tree/master/201-cdn-with-storage-account) Azure Quickstart Template. Changes to the ARM template that may have occurred since the creation of this example may not be reflected here. diff --git a/examples/azure-cdn-with-storage-account/deploy.ci.sh b/examples/azure-cdn-with-storage-account/deploy.ci.sh index 4f8f967ab289..dcb4de86e3b9 100755 --- a/examples/azure-cdn-with-storage-account/deploy.ci.sh +++ b/examples/azure-cdn-with-storage-account/deploy.ci.sh @@ -13,9 +13,11 @@ docker run --rm -it \ hashicorp/terraform:light \ -c "/bin/terraform get; \ /bin/terraform validate; \ - /bin/terraform plan -out=out.tfplan -var resource_group=$KEY -var host_name=$VM_HOST_NAME; \ + /bin/terraform plan -out=out.tfplan -var resource_group=$KEY -var host_name=$KEY; \ /bin/terraform apply out.tfplan" +#TODO: how do we validate? + # cleanup deployed azure resources via terraform docker run --rm -it \ -e ARM_CLIENT_ID \ @@ -26,4 +28,4 @@ docker run --rm -it \ --workdir=/data \ --entrypoint "/bin/sh" \ hashicorp/terraform:light \ - -c "/bin/terraform destroy -force -var resource_group=$KEY -var host_name=$VM_HOST_NAME;" + -c "/bin/terraform destroy -force -var resource_group=$KEY -var host_name=$KEY;" diff --git a/examples/azure-cdn-with-storage-account/deploy.mac.sh b/examples/azure-cdn-with-storage-account/deploy.mac.sh index 11ec6e0967b3..9c6563f07d71 100755 --- a/examples/azure-cdn-with-storage-account/deploy.mac.sh +++ b/examples/azure-cdn-with-storage-account/deploy.mac.sh @@ -7,7 +7,6 @@ if docker -v; then # generate a unique string for CI deployment export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) - export VM_HOST_NAME=anniecdn.southcentralus.cloudapp.azure.com /bin/sh ./deploy.ci.sh From 90a6522b66c7ad324e7ceb75245feb1f5069a88b Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Mon, 1 May 2017 19:03:42 -0500 Subject: [PATCH 48/67] deploys locally --- .../azure-traffic-manager-vm/deploy.ci.sh | 2 +- examples/azure-traffic-manager-vm/main.tf | 142 ++++++------------ examples/azure-traffic-manager-vm/outputs.tf | 18 ++- .../azure-traffic-manager-vm/variables.tf | 29 +--- 4 files changed, 64 insertions(+), 127 deletions(-) diff --git a/examples/azure-traffic-manager-vm/deploy.ci.sh b/examples/azure-traffic-manager-vm/deploy.ci.sh index 9dc30a379b89..32e9c33786d2 100755 --- a/examples/azure-traffic-manager-vm/deploy.ci.sh +++ b/examples/azure-traffic-manager-vm/deploy.ci.sh @@ -13,7 +13,7 @@ docker run --rm -it \ hashicorp/terraform:light \ -c "/bin/terraform get; \ /bin/terraform validate; \ - /bin/terraform plan -out=out.tfplan -var unique_dns_name=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD; \ + /bin/terraform plan -out=out.tfplan -var dns_name=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD; \ /bin/terraform apply out.tfplan; \ /bin/terraform show;" diff --git a/examples/azure-traffic-manager-vm/main.tf b/examples/azure-traffic-manager-vm/main.tf index 8317980877f7..753cdb4de540 100644 --- a/examples/azure-traffic-manager-vm/main.tf +++ b/examples/azure-traffic-manager-vm/main.tf @@ -13,14 +13,14 @@ resource "azurerm_public_ip" "pip" { } resource "azurerm_virtual_network" "vnet" { - name = "${var.virtual_network_name}" + name = "${var.vnet}" location = "${var.location}" address_space = ["${var.address_space}"] resource_group_name = "${azurerm_resource_group.rg.name}" } resource "azurerm_subnet" "subnet" { - name = "${var.rg_prefix}subnet" + name = "${var.subnet_name}" virtual_network_name = "${azurerm_virtual_network.vnet.name}" resource_group_name = "${azurerm_resource_group.rg.name}" address_prefix = "${var.subnet_prefix}" @@ -36,22 +36,16 @@ resource "azurerm_network_interface" "nic" { name = "ipconfig${count.index}" subnet_id = "${azurerm_subnet.subnet.id}" private_ip_address_allocation = "Dynamic" - public_ip_address_id = ["${element(azurerm_public_ip.pip.*.id, count.index)}"] + public_ip_address_id = "${element(azurerm_public_ip.pip.*.id, count.index)}" } } -resource "azurerm_storage_account" "stor" { - name = "${var.dns_name}stor" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - account_type = "${var.storage_account_type}" -} - resource "azurerm_virtual_machine" "vm" { name = "vm${count.index}" location = "${var.location}" resource_group_name = "${azurerm_resource_group.rg.name}" vm_size = "${var.vm_size}" + count = "${var.num_vms}" network_interface_ids = ["${element(azurerm_network_interface.nic.*.id, count.index)}"] storage_image_reference { @@ -62,12 +56,12 @@ resource "azurerm_virtual_machine" "vm" { } storage_os_disk { - name = "osdisk${count.index}" - create_option = "FromImage" + name = "osdisk${count.index}" + create_option = "FromImage" } os_profile { - computer_name = "${var.hostname}" + computer_name = "vm${count.index}" admin_username = "${var.admin_username}" admin_password = "${var.admin_password}" } @@ -75,86 +69,50 @@ resource "azurerm_virtual_machine" "vm" { os_profile_linux_config { disable_password_authentication = false } - - boot_diagnostics { - enabled = "true" - storage_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}" - } } - "type": "Microsoft.Compute/virtualMachines/extensions", - "name": "[concat(variables('vmName'), copyIndex(), '/installcustomscript')]", - "copy": { - "name": "extLoop", - "count": "[variables('numVMs')]" - }, - "apiVersion": "[variables('apiVersion')]", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[concat('Microsoft.Compute/virtualMachines/', variables('vmName'), copyIndex())]" - ], - "properties": { - "publisher": "Microsoft.Azure.Extensions", - "type": "CustomScript", - "typeHandlerVersion": "2.0", - "autoUpgradeMinorVersion": true, - "settings": { - "commandToExecute": "sudo bash -c 'apt-get update && apt-get -y install apache2' " - } - } - }, +resource "azurerm_virtual_machine_extension" "ext" { + depends_on = ["azurerm_virtual_machine.vm"] + name = "CustomScript" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + virtual_machine_name = "vm${count.index}" + publisher = "Microsoft.Azure.Extensions" + type = "CustomScript" + type_handler_version = "2.0" + count = "${var.num_vms}" + auto_upgrade_minor_version = true + + settings = < Date: Tue, 2 May 2017 09:14:01 -0500 Subject: [PATCH 49/67] deploys locally --- examples/azure-traffic-manager-vm/README.md | 4 +- .../azure-traffic-manager-vm/deploy.ci.sh | 4 +- examples/azure-traffic-manager-vm/main.tf | 142 ++++++------------ examples/azure-traffic-manager-vm/outputs.tf | 12 +- .../azure-traffic-manager-vm/variables.tf | 29 +--- 5 files changed, 59 insertions(+), 132 deletions(-) diff --git a/examples/azure-traffic-manager-vm/README.md b/examples/azure-traffic-manager-vm/README.md index 865d947c568f..0556bf066cf7 100644 --- a/examples/azure-traffic-manager-vm/README.md +++ b/examples/azure-traffic-manager-vm/README.md @@ -1,12 +1,12 @@ # Azure Traffic Manager with virtual machines -This Terraform template was based on [this](https://github.com/Azure/azure-quickstart-templates/tree/master/201-traffic-manager-vm) Azure Quickstart Template. Changes to the ARM template may have occurred since the creation of this example may not be reflected here. +This Terraform template was based on [this](https://github.com/Azure/azure-quickstart-templates/tree/master/201-traffic-manager-vm) Azure Quickstart Template. Changes to the ARM template that may have occurred since the creation of this example may not be reflected here. -This template shows how to create an Azure Traffic Manager profile to load-balance across a couple of Azure virtual machines. Each endpoint has an equal weight but different weights can be specified to distribute load non-uniformly. +This template shows how to create an Azure Traffic Manager profile to load-balance across a couple of Azure virtual machines. Each endpoint has an equal weight but different weights can be specified to distribute load non-uniformly. See also: diff --git a/examples/azure-traffic-manager-vm/deploy.ci.sh b/examples/azure-traffic-manager-vm/deploy.ci.sh index 9dc30a379b89..6bd4a3a19f73 100755 --- a/examples/azure-traffic-manager-vm/deploy.ci.sh +++ b/examples/azure-traffic-manager-vm/deploy.ci.sh @@ -13,7 +13,7 @@ docker run --rm -it \ hashicorp/terraform:light \ -c "/bin/terraform get; \ /bin/terraform validate; \ - /bin/terraform plan -out=out.tfplan -var unique_dns_name=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD; \ + /bin/terraform plan -out=out.tfplan -var dns_name=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD; \ /bin/terraform apply out.tfplan; \ /bin/terraform show;" @@ -33,4 +33,4 @@ docker run --rm -it \ --workdir=/data \ --entrypoint "/bin/sh" \ hashicorp/terraform:light \ - -c "/bin/terraform destroy -force -var unique_dns_name=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD;" + -c "/bin/terraform destroy -force -var dns_name=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD;" diff --git a/examples/azure-traffic-manager-vm/main.tf b/examples/azure-traffic-manager-vm/main.tf index 8317980877f7..753cdb4de540 100644 --- a/examples/azure-traffic-manager-vm/main.tf +++ b/examples/azure-traffic-manager-vm/main.tf @@ -13,14 +13,14 @@ resource "azurerm_public_ip" "pip" { } resource "azurerm_virtual_network" "vnet" { - name = "${var.virtual_network_name}" + name = "${var.vnet}" location = "${var.location}" address_space = ["${var.address_space}"] resource_group_name = "${azurerm_resource_group.rg.name}" } resource "azurerm_subnet" "subnet" { - name = "${var.rg_prefix}subnet" + name = "${var.subnet_name}" virtual_network_name = "${azurerm_virtual_network.vnet.name}" resource_group_name = "${azurerm_resource_group.rg.name}" address_prefix = "${var.subnet_prefix}" @@ -36,22 +36,16 @@ resource "azurerm_network_interface" "nic" { name = "ipconfig${count.index}" subnet_id = "${azurerm_subnet.subnet.id}" private_ip_address_allocation = "Dynamic" - public_ip_address_id = ["${element(azurerm_public_ip.pip.*.id, count.index)}"] + public_ip_address_id = "${element(azurerm_public_ip.pip.*.id, count.index)}" } } -resource "azurerm_storage_account" "stor" { - name = "${var.dns_name}stor" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - account_type = "${var.storage_account_type}" -} - resource "azurerm_virtual_machine" "vm" { name = "vm${count.index}" location = "${var.location}" resource_group_name = "${azurerm_resource_group.rg.name}" vm_size = "${var.vm_size}" + count = "${var.num_vms}" network_interface_ids = ["${element(azurerm_network_interface.nic.*.id, count.index)}"] storage_image_reference { @@ -62,12 +56,12 @@ resource "azurerm_virtual_machine" "vm" { } storage_os_disk { - name = "osdisk${count.index}" - create_option = "FromImage" + name = "osdisk${count.index}" + create_option = "FromImage" } os_profile { - computer_name = "${var.hostname}" + computer_name = "vm${count.index}" admin_username = "${var.admin_username}" admin_password = "${var.admin_password}" } @@ -75,86 +69,50 @@ resource "azurerm_virtual_machine" "vm" { os_profile_linux_config { disable_password_authentication = false } - - boot_diagnostics { - enabled = "true" - storage_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}" - } } - "type": "Microsoft.Compute/virtualMachines/extensions", - "name": "[concat(variables('vmName'), copyIndex(), '/installcustomscript')]", - "copy": { - "name": "extLoop", - "count": "[variables('numVMs')]" - }, - "apiVersion": "[variables('apiVersion')]", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[concat('Microsoft.Compute/virtualMachines/', variables('vmName'), copyIndex())]" - ], - "properties": { - "publisher": "Microsoft.Azure.Extensions", - "type": "CustomScript", - "typeHandlerVersion": "2.0", - "autoUpgradeMinorVersion": true, - "settings": { - "commandToExecute": "sudo bash -c 'apt-get update && apt-get -y install apache2' " - } - } - }, +resource "azurerm_virtual_machine_extension" "ext" { + depends_on = ["azurerm_virtual_machine.vm"] + name = "CustomScript" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + virtual_machine_name = "vm${count.index}" + publisher = "Microsoft.Azure.Extensions" + type = "CustomScript" + type_handler_version = "2.0" + count = "${var.num_vms}" + auto_upgrade_minor_version = true + + settings = < Date: Tue, 2 May 2017 09:27:47 -0500 Subject: [PATCH 50/67] small README change --- examples/azure-traffic-manager-vm/variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/azure-traffic-manager-vm/variables.tf b/examples/azure-traffic-manager-vm/variables.tf index 5bf50559dfe6..ec157ab69f1a 100644 --- a/examples/azure-traffic-manager-vm/variables.tf +++ b/examples/azure-traffic-manager-vm/variables.tf @@ -1,5 +1,5 @@ variable "resource_group" { - description = "The name of the resource group in which to create the virtual network" + description = "The name of the resource group in which to create the virtual network, virtual machines, and traffic manager." } variable "location" { From 25ef50c256a212c13ec6ac60175f46d2a577d73b Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Tue, 2 May 2017 15:18:48 -0500 Subject: [PATCH 51/67] took out armviz button and minor README changes --- examples/azure-cdn-with-storage-account/README.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/examples/azure-cdn-with-storage-account/README.md b/examples/azure-cdn-with-storage-account/README.md index 6536f729385c..8d2d0377caa3 100644 --- a/examples/azure-cdn-with-storage-account/README.md +++ b/examples/azure-cdn-with-storage-account/README.md @@ -1,18 +1,14 @@ # Create a CDN Profile, a CDN Endpoint with a Storage Account as origin -This Terraform template was based on [this](https://github.com/Azure/azure-quickstart-templates/tree/master/201-cdn-with-storage-account) Azure Quickstart Template. Changes to the ARM template that may have occurred since the creation of this example may not be reflected here. +This Terraform template was based on [this](https://github.com/Azure/azure-quickstart-templates/tree/master/201-cdn-with-storage-account) Azure Quickstart Template. Changes to the ARM template that may have occurred since the creation of this example may not be reflected in this Terraform template. - - - - -This template creates a [CDN Profile](https://docs.microsoft.com/en-us/azure/cdn/cdn-overview) and a CDN Endpoint with origin as a Storage Account. Note that user needs to create a public container in the Storage Account in order for CDN Endpoint to serve content from the Storage Account. +This template creates a [CDN Profile](https://docs.microsoft.com/en-us/azure/cdn/cdn-overview) and a CDN Endpoint with the origin as a Storage Account. Note that the user needs to create a public container in the Storage Account in order for CDN Endpoint to serve content from the Storage Account. # Important The endpoint will not immediately be available for use, as it takes time for the registration to propagate through the CDN. For Azure CDN from Akamai profiles, propagation will usually complete within one minute. For Azure CDN from Verizon profiles, propagation will usually complete within 90 minutes, but in some cases can take longer. -Users who try to use the CDN domain name before the endpoint configuration has propagated to the POPs will receive HTTP 404 response codes. If it's been several hours since you created your endpoint and you're still receiving 404 responses, please see [Troubleshooting CDN endpoints returning 404 statuses](https://docs.microsoft.com/en-us/azure/cdn/cdn-troubleshoot-endpoint). +Users who try to use the CDN domain name before the endpoint configuration has propagated to the POPs will receive HTTP 404 response codes. If it has been several hours since you created your endpoint and you're still receiving 404 responses, please see [Troubleshooting CDN endpoints returning 404 statuses](https://docs.microsoft.com/en-us/azure/cdn/cdn-troubleshoot-endpoint). ## main.tf The `main.tf` file contains the actual resources that will be deployed. It also contains the Azure Resource Group definition and any defined variables. From 63c494dc58b24a0dd6723fdd80955de60609b4eb Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Tue, 2 May 2017 15:31:03 -0500 Subject: [PATCH 52/67] changed host_name --- examples/azure-cdn-with-storage-account/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/azure-cdn-with-storage-account/main.tf b/examples/azure-cdn-with-storage-account/main.tf index 1f9c9ecbf843..b161e37258d3 100644 --- a/examples/azure-cdn-with-storage-account/main.tf +++ b/examples/azure-cdn-with-storage-account/main.tf @@ -25,7 +25,7 @@ resource "azurerm_cdn_endpoint" "cdnendpt" { origin { name = "${var.resource_group}Origin1" - host_name = "${var.host_name}" + host_name = "www.${var.host_name}.com" http_port = 80 https_port = 443 } From 9a5a15a99ffaf0715900e2adc37af26d2d385962 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Tue, 2 May 2017 16:54:13 -0500 Subject: [PATCH 53/67] fixed merge conflicts --- .../deploy.ci.sh | 8 ++------ .../azure-vm-simple-linux-managed-disk/main.tf | 15 +-------------- .../variables.tf | 10 +--------- 3 files changed, 4 insertions(+), 29 deletions(-) diff --git a/examples/azure-vm-simple-linux-managed-disk/deploy.ci.sh b/examples/azure-vm-simple-linux-managed-disk/deploy.ci.sh index 8ffbeae2ad9f..71e407526186 100755 --- a/examples/azure-vm-simple-linux-managed-disk/deploy.ci.sh +++ b/examples/azure-vm-simple-linux-managed-disk/deploy.ci.sh @@ -14,14 +14,13 @@ docker run --rm -it \ -c "/bin/terraform get; \ /bin/terraform validate; \ /bin/terraform plan -out=out.tfplan -var dns_name=$KEY -var hostname=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD; \ -<<<<<<< HEAD /bin/terraform apply out.tfplan; \ /bin/terraform show;" # cleanup deployed azure resources via azure-cli docker run --rm -it \ azuresdk/azure-cli-python \ - sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ + sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID > /dev/null; \ az vm show -g $KEY -n rgvm" # cleanup deployed azure resources via terraform @@ -34,7 +33,4 @@ docker run --rm -it \ --workdir=/data \ --entrypoint "/bin/sh" \ hashicorp/terraform:light \ - -c "/bin/terraform destroy -force -var dns_name=$KEY -var hostname=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD;" -======= - /bin/terraform apply out.tfplan" ->>>>>>> ace9b0bd91019122d098ba02d53c561365c4b613 + -c "/bin/terraform destroy -force -var dns_name=$KEY -var hostname=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD;" \ No newline at end of file diff --git a/examples/azure-vm-simple-linux-managed-disk/main.tf b/examples/azure-vm-simple-linux-managed-disk/main.tf index e39e6f16cfe8..8ef283c7819c 100644 --- a/examples/azure-vm-simple-linux-managed-disk/main.tf +++ b/examples/azure-vm-simple-linux-managed-disk/main.tf @@ -45,16 +45,6 @@ resource "azurerm_storage_account" "stor" { account_type = "${var.storage_account_type}" } -<<<<<<< HEAD -======= -resource "azurerm_storage_container" "storc" { - name = "${var.hostname}-vhds" - resource_group_name = "${azurerm_resource_group.rg.name}" - storage_account_name = "${azurerm_storage_account.stor.name}" - container_access_type = "private" -} - ->>>>>>> ace9b0bd91019122d098ba02d53c561365c4b613 resource "azurerm_managed_disk" "datadisk" { name = "${var.hostname}-datadisk" location = "${var.location}" @@ -100,15 +90,12 @@ resource "azurerm_virtual_machine" "vm" { admin_password = "${var.admin_password}" } -<<<<<<< HEAD os_profile_linux_config { disable_password_authentication = false } -======= ->>>>>>> ace9b0bd91019122d098ba02d53c561365c4b613 boot_diagnostics { enabled = "true" storage_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}" } -} +} \ No newline at end of file diff --git a/examples/azure-vm-simple-linux-managed-disk/variables.tf b/examples/azure-vm-simple-linux-managed-disk/variables.tf index 09cf59f8f834..91024000bc03 100644 --- a/examples/azure-vm-simple-linux-managed-disk/variables.tf +++ b/examples/azure-vm-simple-linux-managed-disk/variables.tf @@ -36,20 +36,12 @@ variable "subnet_prefix" { } variable "storage_account_type" { -<<<<<<< HEAD description = "Defines the type of storage account to be created. Valid options are Standard_LRS, Standard_ZRS, Standard_GRS, Standard_RAGRS, Premium_LRS. Changing this is sometimes valid - see the Azure documentation for more information on which types of accounts can be converted into other types." -======= - description = "Specifies the name of the storage account. Changing this forces a new resource to be created. This must be unique across the entire Azure service, not just within the resource group." ->>>>>>> ace9b0bd91019122d098ba02d53c561365c4b613 default = "Standard_LRS" } variable "vm_size" { -<<<<<<< HEAD description = "Specifies the size of the virtual machine." -======= - description = "Specifies the name of the virtual machine resource. Changing this forces a new resource to be created." ->>>>>>> ace9b0bd91019122d098ba02d53c561365c4b613 default = "Standard_A0" } @@ -80,4 +72,4 @@ variable "admin_username" { variable "admin_password" { description = "administrator password (recommended to disable password auth)" -} +} \ No newline at end of file From d7e2acb5cbe1608b45d9429f78ac318e29ca661d Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Tue, 2 May 2017 17:45:06 -0500 Subject: [PATCH 54/67] changed host_name variable --- examples/azure-cdn-with-storage-account/deploy.ci.sh | 4 ++-- examples/azure-cdn-with-storage-account/main.tf | 2 +- examples/azure-cdn-with-storage-account/variables.tf | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/azure-cdn-with-storage-account/deploy.ci.sh b/examples/azure-cdn-with-storage-account/deploy.ci.sh index dcb4de86e3b9..3fd0efac3f6d 100755 --- a/examples/azure-cdn-with-storage-account/deploy.ci.sh +++ b/examples/azure-cdn-with-storage-account/deploy.ci.sh @@ -13,7 +13,7 @@ docker run --rm -it \ hashicorp/terraform:light \ -c "/bin/terraform get; \ /bin/terraform validate; \ - /bin/terraform plan -out=out.tfplan -var resource_group=$KEY -var host_name=$KEY; \ + /bin/terraform plan -out=out.tfplan -var resource_group=$KEY; \ /bin/terraform apply out.tfplan" #TODO: how do we validate? @@ -28,4 +28,4 @@ docker run --rm -it \ --workdir=/data \ --entrypoint "/bin/sh" \ hashicorp/terraform:light \ - -c "/bin/terraform destroy -force -var resource_group=$KEY -var host_name=$KEY;" + -c "/bin/terraform destroy -force -var resource_group=$KEY;" diff --git a/examples/azure-cdn-with-storage-account/main.tf b/examples/azure-cdn-with-storage-account/main.tf index b161e37258d3..1f9c9ecbf843 100644 --- a/examples/azure-cdn-with-storage-account/main.tf +++ b/examples/azure-cdn-with-storage-account/main.tf @@ -25,7 +25,7 @@ resource "azurerm_cdn_endpoint" "cdnendpt" { origin { name = "${var.resource_group}Origin1" - host_name = "www.${var.host_name}.com" + host_name = "${var.host_name}" http_port = 80 https_port = 443 } diff --git a/examples/azure-cdn-with-storage-account/variables.tf b/examples/azure-cdn-with-storage-account/variables.tf index e42c937ab0d7..d9bf51015d93 100644 --- a/examples/azure-cdn-with-storage-account/variables.tf +++ b/examples/azure-cdn-with-storage-account/variables.tf @@ -8,10 +8,11 @@ variable "location" { } variable "storage_account_type" { - description = "Specifies the name of the storage account. Changing this forces a new resource to be created. This must be unique across the entire Azure service, not just within the resource group." + description = "Specifies the type of the storage account" default = "Standard_LRS" } variable "host_name" { - description = "Storage account endpoint. This template requires that the user creates a public container in the Storage Account in order for CDN Endpoint to serve content from the Storage Account." + description = "A string that determines the hostname/IP address of the origin server. This string could be a domain name, IPv4 address or IPv6 address." + default = "www.hostnameoforiginserver.com" } \ No newline at end of file From dbaf8d14a9cdfcef0281919671357f6171ebd4e6 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Wed, 3 May 2017 13:17:55 -0500 Subject: [PATCH 55/67] updating Hashicorp's changes to merged simple linux branch --- examples/azure-vm-simple-linux-managed-disk/README.md | 8 ++------ .../azure-vm-simple-linux-managed-disk/deploy.mac.sh | 2 +- examples/azure-vm-simple-linux-managed-disk/main.tf | 11 +++++++++-- .../azure-vm-simple-linux-managed-disk/outputs.tf | 4 ++-- .../azure-vm-simple-linux-managed-disk/provider.tf | 7 ------- .../terraform.tfvars | 8 -------- 6 files changed, 14 insertions(+), 26 deletions(-) delete mode 100644 examples/azure-vm-simple-linux-managed-disk/provider.tf delete mode 100644 examples/azure-vm-simple-linux-managed-disk/terraform.tfvars diff --git a/examples/azure-vm-simple-linux-managed-disk/README.md b/examples/azure-vm-simple-linux-managed-disk/README.md index 0492e3edccb9..cde5c458e82d 100644 --- a/examples/azure-vm-simple-linux-managed-disk/README.md +++ b/examples/azure-vm-simple-linux-managed-disk/README.md @@ -1,10 +1,6 @@ # Very simple deployment of a Linux VM - - - - -This template allows you to deploy a simple Linux VM using a few different options for the Ubuntu version, using the latest patched version. This will deploy a A1 size VM in the resource group location and return the FQDN of the VM. +This template allows you to deploy a simple Linux VM using a few different options for the Ubuntu version, using the latest patched version. This will deploy an A0 size VM in the resource group location and return the FQDN of the VM. This template takes a minimum amount of parameters and deploys a Linux VM, using the latest patched version. @@ -21,4 +17,4 @@ Azure requires that an application is added to Azure Active Directory to generat If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. ## variables.tf -The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. +The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. \ No newline at end of file diff --git a/examples/azure-vm-simple-linux-managed-disk/deploy.mac.sh b/examples/azure-vm-simple-linux-managed-disk/deploy.mac.sh index 9c6563f07d71..dfc34c2be2fc 100755 --- a/examples/azure-vm-simple-linux-managed-disk/deploy.mac.sh +++ b/examples/azure-vm-simple-linux-managed-disk/deploy.mac.sh @@ -12,4 +12,4 @@ if docker -v; then else echo "Docker is used to run terraform commands, please install before run: https://docs.docker.com/docker-for-mac/install/" -fi +fi \ No newline at end of file diff --git a/examples/azure-vm-simple-linux-managed-disk/main.tf b/examples/azure-vm-simple-linux-managed-disk/main.tf index 8ef283c7819c..5dc9ce1cb086 100644 --- a/examples/azure-vm-simple-linux-managed-disk/main.tf +++ b/examples/azure-vm-simple-linux-managed-disk/main.tf @@ -1,3 +1,10 @@ +# provider "azurerm" { +# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" +# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" +# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" +# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" +# } + resource "azurerm_resource_group" "rg" { name = "${var.resource_group}" location = "${var.location}" @@ -34,7 +41,7 @@ resource "azurerm_public_ip" "pip" { name = "${var.rg_prefix}-ip" location = "${var.location}" resource_group_name = "${azurerm_resource_group.rg.name}" - public_ip_address_allocation = "dynamic" + public_ip_address_allocation = "Dynamic" domain_name_label = "${var.dns_name}" } @@ -95,7 +102,7 @@ resource "azurerm_virtual_machine" "vm" { } boot_diagnostics { - enabled = "true" + enabled = true storage_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}" } } \ No newline at end of file diff --git a/examples/azure-vm-simple-linux-managed-disk/outputs.tf b/examples/azure-vm-simple-linux-managed-disk/outputs.tf index 9e3c2f0712bc..32c6294ceeab 100644 --- a/examples/azure-vm-simple-linux-managed-disk/outputs.tf +++ b/examples/azure-vm-simple-linux-managed-disk/outputs.tf @@ -6,6 +6,6 @@ output "vm_fqdn" { value = "${azurerm_public_ip.pip.fqdn}" } -output "sshCommand" { +output "ssh_command" { value = "ssh ${var.admin_username}@${azurerm_public_ip.pip.fqdn}" -} +} \ No newline at end of file diff --git a/examples/azure-vm-simple-linux-managed-disk/provider.tf b/examples/azure-vm-simple-linux-managed-disk/provider.tf deleted file mode 100644 index 79291f7ca895..000000000000 --- a/examples/azure-vm-simple-linux-managed-disk/provider.tf +++ /dev/null @@ -1,7 +0,0 @@ -# provider "azurerm" { -# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" -# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" -# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" -# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" -# } - diff --git a/examples/azure-vm-simple-linux-managed-disk/terraform.tfvars b/examples/azure-vm-simple-linux-managed-disk/terraform.tfvars deleted file mode 100644 index bee98e4e11bc..000000000000 --- a/examples/azure-vm-simple-linux-managed-disk/terraform.tfvars +++ /dev/null @@ -1,8 +0,0 @@ -# Replace with relevant values - -# resource_group = "myresourcegroup" -# rg_prefix = "rg" -# hostname = "myvm" -# dns_name = "mydnsname" -# location = "southcentralus" -# admin_password = "T3rr@f0rmP@ssword" From b850cd5d2a858eff073fc5a1097a6813d0f8b362 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Wed, 3 May 2017 13:35:37 -0500 Subject: [PATCH 56/67] updating files to merge w/master and prep for Hashicorp pr --- .travis.yml | 17 +++++++++++++---- .../azure-cdn-with-storage-account/deploy.ci.sh | 2 +- .../deploy.mac.sh | 2 +- examples/azure-cdn-with-storage-account/main.tf | 7 +++++++ .../provider.tf.example | 7 ------- .../terraform.tfvars | 8 -------- 6 files changed, 22 insertions(+), 21 deletions(-) delete mode 100644 examples/azure-cdn-with-storage-account/provider.tf.example delete mode 100644 examples/azure-cdn-with-storage-account/terraform.tfvars diff --git a/.travis.yml b/.travis.yml index 42e207da6b9c..6180da4dec64 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,7 @@ +#################################################################################### +## NOT FOR UPSTREAM PROPOSAL; INTENDED FOR CI OF AZURE EXAMPLES IN THIS REPO ONLY ## +#################################################################################### + sudo: required services: @@ -5,14 +9,19 @@ services: language: generic +# on branches: ignore multiple commits that will queue build jobs, just run latest commit +git: + depth: 1 + # establish environment variables env: - - TEST_DIR=examples/azure-cdn-with-storage-account + - TEST_DIR=examples/azure-vm-simple-linux-managed-disk + - TEST_DIR=examples/azure-vnet-two-subnets branches: only: -# - master - - /^(?i:topic)-.*$/ + - master + - /^(?i:topic)-.*$/ # install terraform before_deploy: @@ -26,4 +35,4 @@ deploy: script: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./deploy.ci.sh on: repo: harijayms/terraform - branch: topic-201-cdn-with-storage-account + branch: master \ No newline at end of file diff --git a/examples/azure-cdn-with-storage-account/deploy.ci.sh b/examples/azure-cdn-with-storage-account/deploy.ci.sh index 3fd0efac3f6d..3ccc9b9ecef2 100755 --- a/examples/azure-cdn-with-storage-account/deploy.ci.sh +++ b/examples/azure-cdn-with-storage-account/deploy.ci.sh @@ -28,4 +28,4 @@ docker run --rm -it \ --workdir=/data \ --entrypoint "/bin/sh" \ hashicorp/terraform:light \ - -c "/bin/terraform destroy -force -var resource_group=$KEY;" + -c "/bin/terraform destroy -force -var resource_group=$KEY;" \ No newline at end of file diff --git a/examples/azure-cdn-with-storage-account/deploy.mac.sh b/examples/azure-cdn-with-storage-account/deploy.mac.sh index 9c6563f07d71..dfc34c2be2fc 100755 --- a/examples/azure-cdn-with-storage-account/deploy.mac.sh +++ b/examples/azure-cdn-with-storage-account/deploy.mac.sh @@ -12,4 +12,4 @@ if docker -v; then else echo "Docker is used to run terraform commands, please install before run: https://docs.docker.com/docker-for-mac/install/" -fi +fi \ No newline at end of file diff --git a/examples/azure-cdn-with-storage-account/main.tf b/examples/azure-cdn-with-storage-account/main.tf index 1f9c9ecbf843..2e7f56954c3c 100644 --- a/examples/azure-cdn-with-storage-account/main.tf +++ b/examples/azure-cdn-with-storage-account/main.tf @@ -1,3 +1,10 @@ +# provider "azurerm" { +# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" +# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" +# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" +# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" +# } + resource "azurerm_resource_group" "rg" { name = "${var.resource_group}" location = "${var.location}" diff --git a/examples/azure-cdn-with-storage-account/provider.tf.example b/examples/azure-cdn-with-storage-account/provider.tf.example deleted file mode 100644 index 79291f7ca895..000000000000 --- a/examples/azure-cdn-with-storage-account/provider.tf.example +++ /dev/null @@ -1,7 +0,0 @@ -# provider "azurerm" { -# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" -# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" -# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" -# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" -# } - diff --git a/examples/azure-cdn-with-storage-account/terraform.tfvars b/examples/azure-cdn-with-storage-account/terraform.tfvars deleted file mode 100644 index bee98e4e11bc..000000000000 --- a/examples/azure-cdn-with-storage-account/terraform.tfvars +++ /dev/null @@ -1,8 +0,0 @@ -# Replace with relevant values - -# resource_group = "myresourcegroup" -# rg_prefix = "rg" -# hostname = "myvm" -# dns_name = "mydnsname" -# location = "southcentralus" -# admin_password = "T3rr@f0rmP@ssword" From a6151bcc5970a2a5262fcbe3ed8908315ac54187 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Wed, 3 May 2017 17:48:18 -0500 Subject: [PATCH 57/67] Revert "updating files to merge w/master and prep for Hashicorp pr" This reverts commit b850cd5d2a858eff073fc5a1097a6813d0f8b362. --- .travis.yml | 17 ++++------------- .../azure-cdn-with-storage-account/deploy.ci.sh | 2 +- .../deploy.mac.sh | 2 +- examples/azure-cdn-with-storage-account/main.tf | 7 ------- .../provider.tf.example | 7 +++++++ .../terraform.tfvars | 8 ++++++++ 6 files changed, 21 insertions(+), 22 deletions(-) create mode 100644 examples/azure-cdn-with-storage-account/provider.tf.example create mode 100644 examples/azure-cdn-with-storage-account/terraform.tfvars diff --git a/.travis.yml b/.travis.yml index 6180da4dec64..42e207da6b9c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,3 @@ -#################################################################################### -## NOT FOR UPSTREAM PROPOSAL; INTENDED FOR CI OF AZURE EXAMPLES IN THIS REPO ONLY ## -#################################################################################### - sudo: required services: @@ -9,19 +5,14 @@ services: language: generic -# on branches: ignore multiple commits that will queue build jobs, just run latest commit -git: - depth: 1 - # establish environment variables env: - - TEST_DIR=examples/azure-vm-simple-linux-managed-disk - - TEST_DIR=examples/azure-vnet-two-subnets + - TEST_DIR=examples/azure-cdn-with-storage-account branches: only: - - master - - /^(?i:topic)-.*$/ +# - master + - /^(?i:topic)-.*$/ # install terraform before_deploy: @@ -35,4 +26,4 @@ deploy: script: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./deploy.ci.sh on: repo: harijayms/terraform - branch: master \ No newline at end of file + branch: topic-201-cdn-with-storage-account diff --git a/examples/azure-cdn-with-storage-account/deploy.ci.sh b/examples/azure-cdn-with-storage-account/deploy.ci.sh index 3ccc9b9ecef2..3fd0efac3f6d 100755 --- a/examples/azure-cdn-with-storage-account/deploy.ci.sh +++ b/examples/azure-cdn-with-storage-account/deploy.ci.sh @@ -28,4 +28,4 @@ docker run --rm -it \ --workdir=/data \ --entrypoint "/bin/sh" \ hashicorp/terraform:light \ - -c "/bin/terraform destroy -force -var resource_group=$KEY;" \ No newline at end of file + -c "/bin/terraform destroy -force -var resource_group=$KEY;" diff --git a/examples/azure-cdn-with-storage-account/deploy.mac.sh b/examples/azure-cdn-with-storage-account/deploy.mac.sh index dfc34c2be2fc..9c6563f07d71 100755 --- a/examples/azure-cdn-with-storage-account/deploy.mac.sh +++ b/examples/azure-cdn-with-storage-account/deploy.mac.sh @@ -12,4 +12,4 @@ if docker -v; then else echo "Docker is used to run terraform commands, please install before run: https://docs.docker.com/docker-for-mac/install/" -fi \ No newline at end of file +fi diff --git a/examples/azure-cdn-with-storage-account/main.tf b/examples/azure-cdn-with-storage-account/main.tf index 2e7f56954c3c..1f9c9ecbf843 100644 --- a/examples/azure-cdn-with-storage-account/main.tf +++ b/examples/azure-cdn-with-storage-account/main.tf @@ -1,10 +1,3 @@ -# provider "azurerm" { -# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" -# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" -# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" -# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" -# } - resource "azurerm_resource_group" "rg" { name = "${var.resource_group}" location = "${var.location}" diff --git a/examples/azure-cdn-with-storage-account/provider.tf.example b/examples/azure-cdn-with-storage-account/provider.tf.example new file mode 100644 index 000000000000..79291f7ca895 --- /dev/null +++ b/examples/azure-cdn-with-storage-account/provider.tf.example @@ -0,0 +1,7 @@ +# provider "azurerm" { +# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" +# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" +# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" +# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" +# } + diff --git a/examples/azure-cdn-with-storage-account/terraform.tfvars b/examples/azure-cdn-with-storage-account/terraform.tfvars new file mode 100644 index 000000000000..bee98e4e11bc --- /dev/null +++ b/examples/azure-cdn-with-storage-account/terraform.tfvars @@ -0,0 +1,8 @@ +# Replace with relevant values + +# resource_group = "myresourcegroup" +# rg_prefix = "rg" +# hostname = "myvm" +# dns_name = "mydnsname" +# location = "southcentralus" +# admin_password = "T3rr@f0rmP@ssword" From 542673967d74e18c5e8759b685288596c243381a Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Wed, 3 May 2017 17:49:04 -0500 Subject: [PATCH 58/67] Revert "updating Hashicorp's changes to merged simple linux branch" This reverts commit dbaf8d14a9cdfcef0281919671357f6171ebd4e6. --- examples/azure-vm-simple-linux-managed-disk/README.md | 8 ++++++-- .../azure-vm-simple-linux-managed-disk/deploy.mac.sh | 2 +- examples/azure-vm-simple-linux-managed-disk/main.tf | 11 ++--------- .../azure-vm-simple-linux-managed-disk/outputs.tf | 4 ++-- .../azure-vm-simple-linux-managed-disk/provider.tf | 7 +++++++ .../terraform.tfvars | 8 ++++++++ 6 files changed, 26 insertions(+), 14 deletions(-) create mode 100644 examples/azure-vm-simple-linux-managed-disk/provider.tf create mode 100644 examples/azure-vm-simple-linux-managed-disk/terraform.tfvars diff --git a/examples/azure-vm-simple-linux-managed-disk/README.md b/examples/azure-vm-simple-linux-managed-disk/README.md index cde5c458e82d..0492e3edccb9 100644 --- a/examples/azure-vm-simple-linux-managed-disk/README.md +++ b/examples/azure-vm-simple-linux-managed-disk/README.md @@ -1,6 +1,10 @@ # Very simple deployment of a Linux VM -This template allows you to deploy a simple Linux VM using a few different options for the Ubuntu version, using the latest patched version. This will deploy an A0 size VM in the resource group location and return the FQDN of the VM. + + + + +This template allows you to deploy a simple Linux VM using a few different options for the Ubuntu version, using the latest patched version. This will deploy a A1 size VM in the resource group location and return the FQDN of the VM. This template takes a minimum amount of parameters and deploys a Linux VM, using the latest patched version. @@ -17,4 +21,4 @@ Azure requires that an application is added to Azure Active Directory to generat If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. ## variables.tf -The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. \ No newline at end of file +The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. diff --git a/examples/azure-vm-simple-linux-managed-disk/deploy.mac.sh b/examples/azure-vm-simple-linux-managed-disk/deploy.mac.sh index dfc34c2be2fc..9c6563f07d71 100755 --- a/examples/azure-vm-simple-linux-managed-disk/deploy.mac.sh +++ b/examples/azure-vm-simple-linux-managed-disk/deploy.mac.sh @@ -12,4 +12,4 @@ if docker -v; then else echo "Docker is used to run terraform commands, please install before run: https://docs.docker.com/docker-for-mac/install/" -fi \ No newline at end of file +fi diff --git a/examples/azure-vm-simple-linux-managed-disk/main.tf b/examples/azure-vm-simple-linux-managed-disk/main.tf index 5dc9ce1cb086..8ef283c7819c 100644 --- a/examples/azure-vm-simple-linux-managed-disk/main.tf +++ b/examples/azure-vm-simple-linux-managed-disk/main.tf @@ -1,10 +1,3 @@ -# provider "azurerm" { -# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" -# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" -# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" -# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" -# } - resource "azurerm_resource_group" "rg" { name = "${var.resource_group}" location = "${var.location}" @@ -41,7 +34,7 @@ resource "azurerm_public_ip" "pip" { name = "${var.rg_prefix}-ip" location = "${var.location}" resource_group_name = "${azurerm_resource_group.rg.name}" - public_ip_address_allocation = "Dynamic" + public_ip_address_allocation = "dynamic" domain_name_label = "${var.dns_name}" } @@ -102,7 +95,7 @@ resource "azurerm_virtual_machine" "vm" { } boot_diagnostics { - enabled = true + enabled = "true" storage_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}" } } \ No newline at end of file diff --git a/examples/azure-vm-simple-linux-managed-disk/outputs.tf b/examples/azure-vm-simple-linux-managed-disk/outputs.tf index 32c6294ceeab..9e3c2f0712bc 100644 --- a/examples/azure-vm-simple-linux-managed-disk/outputs.tf +++ b/examples/azure-vm-simple-linux-managed-disk/outputs.tf @@ -6,6 +6,6 @@ output "vm_fqdn" { value = "${azurerm_public_ip.pip.fqdn}" } -output "ssh_command" { +output "sshCommand" { value = "ssh ${var.admin_username}@${azurerm_public_ip.pip.fqdn}" -} \ No newline at end of file +} diff --git a/examples/azure-vm-simple-linux-managed-disk/provider.tf b/examples/azure-vm-simple-linux-managed-disk/provider.tf new file mode 100644 index 000000000000..79291f7ca895 --- /dev/null +++ b/examples/azure-vm-simple-linux-managed-disk/provider.tf @@ -0,0 +1,7 @@ +# provider "azurerm" { +# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" +# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" +# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" +# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" +# } + diff --git a/examples/azure-vm-simple-linux-managed-disk/terraform.tfvars b/examples/azure-vm-simple-linux-managed-disk/terraform.tfvars new file mode 100644 index 000000000000..bee98e4e11bc --- /dev/null +++ b/examples/azure-vm-simple-linux-managed-disk/terraform.tfvars @@ -0,0 +1,8 @@ +# Replace with relevant values + +# resource_group = "myresourcegroup" +# rg_prefix = "rg" +# hostname = "myvm" +# dns_name = "mydnsname" +# location = "southcentralus" +# admin_password = "T3rr@f0rmP@ssword" From 1351e8090d3a1a2fe1665c4d6c3c0d540ee3a155 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Wed, 3 May 2017 19:25:42 -0500 Subject: [PATCH 59/67] added provider to main.tf --- examples/azure-traffic-manager-vm/deploy.ci.sh | 2 +- examples/azure-traffic-manager-vm/deploy.mac.sh | 2 +- examples/azure-traffic-manager-vm/main.tf | 7 +++++++ examples/azure-traffic-manager-vm/provider.tf.example | 7 ------- examples/azure-traffic-manager-vm/terraform.tfvars | 8 -------- 5 files changed, 9 insertions(+), 17 deletions(-) delete mode 100644 examples/azure-traffic-manager-vm/provider.tf.example delete mode 100644 examples/azure-traffic-manager-vm/terraform.tfvars diff --git a/examples/azure-traffic-manager-vm/deploy.ci.sh b/examples/azure-traffic-manager-vm/deploy.ci.sh index 6bd4a3a19f73..c9f291444666 100755 --- a/examples/azure-traffic-manager-vm/deploy.ci.sh +++ b/examples/azure-traffic-manager-vm/deploy.ci.sh @@ -33,4 +33,4 @@ docker run --rm -it \ --workdir=/data \ --entrypoint "/bin/sh" \ hashicorp/terraform:light \ - -c "/bin/terraform destroy -force -var dns_name=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD;" + -c "/bin/terraform destroy -force -var dns_name=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD;" \ No newline at end of file diff --git a/examples/azure-traffic-manager-vm/deploy.mac.sh b/examples/azure-traffic-manager-vm/deploy.mac.sh index 9c6563f07d71..dfc34c2be2fc 100755 --- a/examples/azure-traffic-manager-vm/deploy.mac.sh +++ b/examples/azure-traffic-manager-vm/deploy.mac.sh @@ -12,4 +12,4 @@ if docker -v; then else echo "Docker is used to run terraform commands, please install before run: https://docs.docker.com/docker-for-mac/install/" -fi +fi \ No newline at end of file diff --git a/examples/azure-traffic-manager-vm/main.tf b/examples/azure-traffic-manager-vm/main.tf index 753cdb4de540..ef34a8ad5391 100644 --- a/examples/azure-traffic-manager-vm/main.tf +++ b/examples/azure-traffic-manager-vm/main.tf @@ -1,3 +1,10 @@ +# provider "azurerm" { +# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" +# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" +# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" +# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" +# } + resource "azurerm_resource_group" "rg" { name = "${var.resource_group}" location = "${var.location}" diff --git a/examples/azure-traffic-manager-vm/provider.tf.example b/examples/azure-traffic-manager-vm/provider.tf.example deleted file mode 100644 index 79291f7ca895..000000000000 --- a/examples/azure-traffic-manager-vm/provider.tf.example +++ /dev/null @@ -1,7 +0,0 @@ -# provider "azurerm" { -# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" -# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" -# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" -# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" -# } - diff --git a/examples/azure-traffic-manager-vm/terraform.tfvars b/examples/azure-traffic-manager-vm/terraform.tfvars deleted file mode 100644 index bee98e4e11bc..000000000000 --- a/examples/azure-traffic-manager-vm/terraform.tfvars +++ /dev/null @@ -1,8 +0,0 @@ -# Replace with relevant values - -# resource_group = "myresourcegroup" -# rg_prefix = "rg" -# hostname = "myvm" -# dns_name = "mydnsname" -# location = "southcentralus" -# admin_password = "T3rr@f0rmP@ssword" From 54d33bfc790c59a17db66cc9ae511593ed0a39c4 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Wed, 3 May 2017 20:02:13 -0500 Subject: [PATCH 60/67] took out armviz button; resolved conflicts in .travis.yml --- .travis.yml | 16 ++++++++++------ examples/azure-traffic-manager-vm/README.md | 4 ---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index b611c7f098af..c8febea1331d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,3 @@ -#################################################################################### -## NOT FOR UPSTREAM PROPOSAL; INTENDED FOR CI OF AZURE EXAMPLES IN THIS REPO ONLY ## -#################################################################################### - sudo: required services: @@ -9,13 +5,21 @@ services: language: generic +# on branches: ignore multiple commits that will queue build jobs, just run latest commit +git: + depth: 1 + # establish environment variables env: - TEST_DIR=examples/azure-traffic-manager-vm + - TEST_DIR=examples/azure-cdn-with-storage-account + - TEST_DIR=examples/azure-vm-simple-linux-managed-disk + - TEST_DIR=examples/azure-vnet-two-subnets branches: only: - - /^(?i:topic)-.*$/ + - master + - /^(?i:topic)-.*$/ # install terraform before_deploy: @@ -29,4 +33,4 @@ deploy: script: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./deploy.ci.sh on: repo: harijayms/terraform - branch: topic-201-traffic-manager-vm + branch: master \ No newline at end of file diff --git a/examples/azure-traffic-manager-vm/README.md b/examples/azure-traffic-manager-vm/README.md index 0556bf066cf7..e9e7ee1f3898 100644 --- a/examples/azure-traffic-manager-vm/README.md +++ b/examples/azure-traffic-manager-vm/README.md @@ -2,10 +2,6 @@ This Terraform template was based on [this](https://github.com/Azure/azure-quickstart-templates/tree/master/201-traffic-manager-vm) Azure Quickstart Template. Changes to the ARM template that may have occurred since the creation of this example may not be reflected here. - - - - This template shows how to create an Azure Traffic Manager profile to load-balance across a couple of Azure virtual machines. Each endpoint has an equal weight but different weights can be specified to distribute load non-uniformly. See also: From 5174369a32b753c9980b01165b1d3a12e9ee4c10 Mon Sep 17 00:00:00 2001 From: anniehedgpeth Date: Thu, 4 May 2017 11:47:57 -0500 Subject: [PATCH 61/67] removing vm from user image example from this branch --- examples/azure-vm-from-user-image/README.md | 28 --------- .../after_deploy.ci.sh | 11 ---- .../azure-vm-from-user-image/deploy.ci.sh | 37 ----------- .../azure-vm-from-user-image/deploy.mac.sh | 18 ------ examples/azure-vm-from-user-image/main.tf | 62 ------------------- examples/azure-vm-from-user-image/outputs.tf | 11 ---- examples/azure-vm-from-user-image/provider.tf | 6 -- .../azure-vm-from-user-image/terraform.tfvars | 13 ---- .../azure-vm-from-user-image/variables.tf | 56 ----------------- 9 files changed, 242 deletions(-) delete mode 100644 examples/azure-vm-from-user-image/README.md delete mode 100755 examples/azure-vm-from-user-image/after_deploy.ci.sh delete mode 100755 examples/azure-vm-from-user-image/deploy.ci.sh delete mode 100755 examples/azure-vm-from-user-image/deploy.mac.sh delete mode 100644 examples/azure-vm-from-user-image/main.tf delete mode 100644 examples/azure-vm-from-user-image/outputs.tf delete mode 100644 examples/azure-vm-from-user-image/provider.tf delete mode 100644 examples/azure-vm-from-user-image/terraform.tfvars delete mode 100644 examples/azure-vm-from-user-image/variables.tf diff --git a/examples/azure-vm-from-user-image/README.md b/examples/azure-vm-from-user-image/README.md deleted file mode 100644 index 455b0bcb1a5f..000000000000 --- a/examples/azure-vm-from-user-image/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# [Create a Virtual Machine from a User Image](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/cli-deploy-templates#create-a-custom-vm-image) - -This Terraform template was based on [this](https://github.com/Azure/azure-quickstart-templates/tree/master/101-vm-from-user-image) Azure Quickstart Template. Changes to the ARM template may have occured since the creation of this example may not be reflected here. - - - - - -> Prerequisite - The generalized image VHD should exist, as well as a Storage Account for boot diagnostics - -This template allows you to create a Virtual Machine from an unmanaged User image vhd. This template also deploys a Virtual Network, Public IP addresses and a Network Interface. - -## main.tf -The `main.tf` file contains the actual resources that will be deployed. It also contains the Azure Resource Group definition and any defined variables. - -## outputs.tf -This data is outputted when `terraform apply` is called, and can be queried using the `terraform output` command. - -## provider.tf -Azure requires that an application is added to Azure Active Directory to generate the `client_id`, `client_secret`, and `tenant_id` needed by Terraform (`subscription_id` can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this to populate your `provider.tf` file. - -## terraform.tfvars -If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. - -If you are committing this template to source control, please insure that you add this file to your `.gitignore` file. - -## variables.tf -The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. diff --git a/examples/azure-vm-from-user-image/after_deploy.ci.sh b/examples/azure-vm-from-user-image/after_deploy.ci.sh deleted file mode 100755 index 8a5624eb7681..000000000000 --- a/examples/azure-vm-from-user-image/after_deploy.ci.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -set -o errexit -o nounset - -docker run --rm -it \ - azuresdk/azure-cli-python \ - sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ - az vm delete --name $KEY --resource-group permanent -y; \ - az network nic delete --name $KEY'nic' --resource-group permanent; \ - az network vnet delete --name $KEY'vnet' --resource-group permanent; \ - az network public-ip delete --name $KEY'-ip' --resource-group permanent;" diff --git a/examples/azure-vm-from-user-image/deploy.ci.sh b/examples/azure-vm-from-user-image/deploy.ci.sh deleted file mode 100755 index f45c5bb2a6cd..000000000000 --- a/examples/azure-vm-from-user-image/deploy.ci.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash - -set -o errexit -o nounset - -docker run --rm -it \ - -e ARM_CLIENT_ID \ - -e ARM_CLIENT_SECRET \ - -e ARM_SUBSCRIPTION_ID \ - -e ARM_TENANT_ID \ - -v $(pwd):/data \ - --workdir=/data \ - --entrypoint "/bin/sh" \ - hashicorp/terraform:light \ - -c "/bin/terraform get; \ - /bin/terraform validate; \ - /bin/terraform plan -out=out.tfplan -var hostname=$KEY -var resource_group=$EXISTING_RESOURCE_GROUP -var admin_username=$KEY -var admin_password=$PASSWORD -var image_uri=$EXISTING_IMAGE_URI -var storage_account_name=$EXISTING_STORAGE_ACCOUNT_NAME; \ - /bin/terraform apply out.tfplan" - -# echo "Setting git user name" -# git config user.name $GH_USER_NAME -# -# echo "Setting git user email" -# git config user.email $GH_USER_EMAIL -# -# echo "Adding git upstream remote" -# git remote add upstream "https://$GH_TOKEN@github.com/$GH_REPO.git" -# -# git checkout master - - -# -# NOW=$(TZ=America/Chicago date) -# -# git commit -m "tfstate: $NOW [ci skip]" -# -# echo "Pushing changes to upstream master" -# git push upstream master diff --git a/examples/azure-vm-from-user-image/deploy.mac.sh b/examples/azure-vm-from-user-image/deploy.mac.sh deleted file mode 100755 index acd4ca7028fc..000000000000 --- a/examples/azure-vm-from-user-image/deploy.mac.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -set -o errexit -o nounset - -if docker -v; then - - # generate a unique string for CI deployment - export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) - export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) - export EXISTING_IMAGE_URI=https://permanentstor.blob.core.windows.net/permanent-vhds/permanent-osdisk1.vhd - export EXISTING_STORAGE_ACCOUNT_NAME=permanentstor - export EXISTING_RESOURCE_GROUP=permanent - - /bin/sh ./deploy.ci.sh - -else - echo "Docker is used to run terraform commands, please install before run: https://docs.docker.com/docker-for-mac/install/" -fi diff --git a/examples/azure-vm-from-user-image/main.tf b/examples/azure-vm-from-user-image/main.tf deleted file mode 100644 index 28edf99204b5..000000000000 --- a/examples/azure-vm-from-user-image/main.tf +++ /dev/null @@ -1,62 +0,0 @@ -resource "azurerm_resource_group" "rg" { - name = "${var.resource_group}" - location = "${var.location}" -} - -resource "azurerm_virtual_network" "vnet" { - name = "${var.hostname}vnet" - location = "${var.location}" - address_space = ["${var.address_space}"] - resource_group_name = "${azurerm_resource_group.rg.name}" -} - -resource "azurerm_subnet" "subnet" { - name = "${var.hostname}subnet" - virtual_network_name = "${azurerm_virtual_network.vnet.name}" - resource_group_name = "${azurerm_resource_group.rg.name}" - address_prefix = "${var.subnet_prefix}" -} - -resource "azurerm_network_interface" "nic" { - name = "${var.hostname}nic" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - - ip_configuration { - name = "${var.hostname}ipconfig" - subnet_id = "${azurerm_subnet.subnet.id}" - private_ip_address_allocation = "Dynamic" - public_ip_address_id = "${azurerm_public_ip.pip.id}" - } -} - -resource "azurerm_public_ip" "pip" { - name = "${var.hostname}-ip" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - public_ip_address_allocation = "dynamic" - domain_name_label = "${var.hostname}" -} - -resource "azurerm_virtual_machine" "vm" { - name = "${var.hostname}" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - vm_size = "${var.vm_size}" - network_interface_ids = ["${azurerm_network_interface.nic.id}"] - - storage_os_disk { - name = "${var.hostname}-osdisk1" - image_uri = "${var.image_uri}" - vhd_uri = "https://${var.storage_account_name}.blob.core.windows.net/vhds/${var.hostname}osdisk.vhd" - os_type = "${var.os_type}" - caching = "ReadWrite" - create_option = "FromImage" - } - - os_profile { - computer_name = "${var.hostname}" - admin_username = "${var.admin_username}" - admin_password = "${var.admin_password}" - } -} diff --git a/examples/azure-vm-from-user-image/outputs.tf b/examples/azure-vm-from-user-image/outputs.tf deleted file mode 100644 index e0e255a9e01c..000000000000 --- a/examples/azure-vm-from-user-image/outputs.tf +++ /dev/null @@ -1,11 +0,0 @@ -output "hostname" { - value = "${var.hostname}" -} - -output "vm_fqdn" { - value = "${azurerm_public_ip.pip.fqdn}" -} - -output "sshCommand" { - value = "${concat("ssh ", var.admin_username, "@", azurerm_public_ip.pip.fqdn)}" -} diff --git a/examples/azure-vm-from-user-image/provider.tf b/examples/azure-vm-from-user-image/provider.tf deleted file mode 100644 index bdf0583f3259..000000000000 --- a/examples/azure-vm-from-user-image/provider.tf +++ /dev/null @@ -1,6 +0,0 @@ -# provider "azurerm" { -# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" -# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" -# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" -# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" -# } diff --git a/examples/azure-vm-from-user-image/terraform.tfvars b/examples/azure-vm-from-user-image/terraform.tfvars deleted file mode 100644 index 15da2a9c6ca7..000000000000 --- a/examples/azure-vm-from-user-image/terraform.tfvars +++ /dev/null @@ -1,13 +0,0 @@ -# resource_group = "myresourcegroup" -# image_uri = "https://DISK.blob.core.windows.net/vhds/ORIGINAL-VM.vhd" -# primary_blob_endpoint = "https://DISK.blob.core.windows.net/" -# location = "southcentralus" -# os_type = "linux" -# address_space = "10.0.0.0/16" -# subnet_prefix = "10.0.0.0/24" -# storage_account_name = "STOR-ACCT-NAME" -# storage_account_type = "Standard_LRS" -# vm_size = "Standard_DS1_v2" -# hostname = "HOSTNAME" -# admin_username = "vmadmin" -# admin_password = "YOURPASSWORDHERE" diff --git a/examples/azure-vm-from-user-image/variables.tf b/examples/azure-vm-from-user-image/variables.tf deleted file mode 100644 index b3e94928978d..000000000000 --- a/examples/azure-vm-from-user-image/variables.tf +++ /dev/null @@ -1,56 +0,0 @@ -variable "resource_group" { - description = "The name of the resource group in which the image to clone resides." - default = "myrg" -} - -variable "image_uri" { - description = "Specifies the image_uri in the form publisherName:offer:skus:version. image_uri can also specify the VHD uri of a custom VM image to clone." -} - -variable "os_type" { - description = "Specifies the operating system Type, valid values are windows, linux." - default = "linux" -} - -variable "location" { - description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." - default = "southcentralus" -} - -variable "address_space" { - description = "The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created." - default = "10.0.0.0/24" -} - -variable "subnet_prefix" { - description = "The address prefix to use for the subnet." - default = "10.0.0.0/24" -} - -variable "storage_account_name" { - description = "The name of the storage account in which the image from which you are cloning resides." -} - -variable "storage_account_type" { - description = "Defines the type of storage account to be created. Valid options are Standard_LRS, Standard_ZRS, Standard_GRS, Standard_RAGRS, Premium_LRS. Changing this is sometimes valid - see the Azure documentation for more information on which types of accounts can be converted into other types." - default = "Premium_LRS" -} - -variable "vm_size" { - description = "Specifies the size of the virtual machine. This must be the same as the vm image from which you are copying." - default = "Standard_DS1_v2" -} - -variable "hostname" { - description = "VM name referenced also in storage-related names. This is also used as the label for the Domain Name and to make up the FQDN. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system." -} - -variable "admin_username" { - description = "administrator user name" - default = "vmadmin" -} - -variable "admin_password" { - description = "administrator password (recommended to disable password auth)" - default = "T3rr@f0rmP@ssword" -} From 1366980b8b8a03eadd4472d9d9346d50eeebb9c0 Mon Sep 17 00:00:00 2001 From: Annie Hedgpeth Date: Thu, 4 May 2017 18:10:14 -0500 Subject: [PATCH 62/67] removed old branch --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 907dafde5d25..2432ad83a56b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,5 +36,4 @@ deploy: script: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./deploy.ci.sh on: repo: harijayms/terraform - branch: topic-101-vm-from-user-image - branch: master \ No newline at end of file + branch: master From 6a1813e07fa3412b86d523973c96374c1976ea64 Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Fri, 5 May 2017 15:08:09 -0500 Subject: [PATCH 63/67] azure-2-vms-loadbalancer-lbrules (#13) * initial commit * need to change lb_rule & nic * deploys locally * updated README * updated travis and deploy scripts for Hari's repo * renamed deploy script * clean up * prep for PR * updated readme * fixing conflict in .travis.yml --- .travis.yml | 2 +- .../README.md | 26 ++++ .../deploy.ci.sh | 36 +++++ .../deploy.mac.sh | 15 ++ .../azure-2-vms-loadbalancer-lbrules/main.tf | 138 ++++++++++++++++++ .../outputs.tf | 11 ++ .../provider.tf | 6 + .../terraform.tfvars | 6 + .../variables.tf | 80 ++++++++++ 9 files changed, 319 insertions(+), 1 deletion(-) create mode 100644 examples/azure-2-vms-loadbalancer-lbrules/README.md create mode 100755 examples/azure-2-vms-loadbalancer-lbrules/deploy.ci.sh create mode 100755 examples/azure-2-vms-loadbalancer-lbrules/deploy.mac.sh create mode 100644 examples/azure-2-vms-loadbalancer-lbrules/main.tf create mode 100644 examples/azure-2-vms-loadbalancer-lbrules/outputs.tf create mode 100644 examples/azure-2-vms-loadbalancer-lbrules/provider.tf create mode 100644 examples/azure-2-vms-loadbalancer-lbrules/terraform.tfvars create mode 100644 examples/azure-2-vms-loadbalancer-lbrules/variables.tf diff --git a/.travis.yml b/.travis.yml index 2432ad83a56b..93e8b1d03795 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,4 +36,4 @@ deploy: script: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./deploy.ci.sh on: repo: harijayms/terraform - branch: master + branch: master \ No newline at end of file diff --git a/examples/azure-2-vms-loadbalancer-lbrules/README.md b/examples/azure-2-vms-loadbalancer-lbrules/README.md new file mode 100644 index 000000000000..d7ca3d4d3cb6 --- /dev/null +++ b/examples/azure-2-vms-loadbalancer-lbrules/README.md @@ -0,0 +1,26 @@ +# Create 2 Virtual Machines under a Load balancer and configures Load Balancing rules for the VMs + +This Terraform template was based on [this](https://github.com/Azure/azure-quickstart-templates/tree/master/201-2-vms-loadbalancer-lbrules) Azure Quickstart Template. Changes to the ARM template may have occured since the creation of this example may not be reflected here. + + + + + +This template allows you to create 2 Virtual Machines under a Load balancer and configure a load balancing rule on Port 80. This template also deploys a Storage Account, Virtual Network, Public IP address, Availability Set, and Network Interfaces. + +## main.tf +The `main.tf` file contains the actual resources that will be deployed. It also contains the Azure Resource Group definition and any defined variables. + +## outputs.tf +This data is outputted when `terraform apply` is called, and can be queried using the `terraform output` command. + +## provider.tf +Azure requires that an application is added to Azure Active Directory to generate the `client_id`, `client_secret`, and `tenant_id` needed by Terraform (`subscription_id` can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this to populate your `provider.tf` file. + +## terraform.tfvars +If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. + +If you are committing this template to source control, please insure that you add this file to your .gitignore file. + +## variables.tf +The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. diff --git a/examples/azure-2-vms-loadbalancer-lbrules/deploy.ci.sh b/examples/azure-2-vms-loadbalancer-lbrules/deploy.ci.sh new file mode 100755 index 000000000000..57e0dc9b1a42 --- /dev/null +++ b/examples/azure-2-vms-loadbalancer-lbrules/deploy.ci.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -o errexit -o nounset + +docker run --rm -it \ + -e ARM_CLIENT_ID \ + -e ARM_CLIENT_SECRET \ + -e ARM_SUBSCRIPTION_ID \ + -e ARM_TENANT_ID \ + -v $(pwd):/data \ + --entrypoint "/bin/sh" \ + hashicorp/terraform:light \ + -c "cd /data; \ + /bin/terraform get; \ + /bin/terraform validate; \ + /bin/terraform plan -out=out.tfplan -var dns_name=$KEY -var hostname=$KEY -var lb_ip_dns_name=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD; \ + /bin/terraform apply out.tfplan" + +# cleanup deployed azure resources via azure-cli +docker run --rm -it \ + azuresdk/azure-cli-python \ + sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID > /dev/null; \ + az network lb show -g $KEY -n rglb; \ + az network lb rule list -g $KEY --lb-name rglb;" + +# cleanup deployed azure resources via terraform +docker run --rm -it \ + -e ARM_CLIENT_ID \ + -e ARM_CLIENT_SECRET \ + -e ARM_SUBSCRIPTION_ID \ + -e ARM_TENANT_ID \ + -v $(pwd):/data \ + --workdir=/data \ + --entrypoint "/bin/sh" \ + hashicorp/terraform:light \ + -c "/bin/terraform destroy -force -var dns_name=$KEY -var hostname=$KEY -var lb_ip_dns_name=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD;" diff --git a/examples/azure-2-vms-loadbalancer-lbrules/deploy.mac.sh b/examples/azure-2-vms-loadbalancer-lbrules/deploy.mac.sh new file mode 100755 index 000000000000..cf5cdc32279f --- /dev/null +++ b/examples/azure-2-vms-loadbalancer-lbrules/deploy.mac.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -o errexit -o nounset + +if docker -v; then + + # generate a unique string for CI deployment + export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) + export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) + +/bin/sh ./deploy.ci.sh + +else + echo "Docker is used to run terraform commands, please install before run: https://docs.docker.com/docker-for-mac/install/" +fi \ No newline at end of file diff --git a/examples/azure-2-vms-loadbalancer-lbrules/main.tf b/examples/azure-2-vms-loadbalancer-lbrules/main.tf new file mode 100644 index 000000000000..24981118d3e2 --- /dev/null +++ b/examples/azure-2-vms-loadbalancer-lbrules/main.tf @@ -0,0 +1,138 @@ +resource "azurerm_resource_group" "rg" { + name = "${var.resource_group}" + location = "${var.location}" +} + +resource "azurerm_storage_account" "stor" { + name = "${var.dns_name}stor" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + account_type = "${var.storage_account_type}" +} + +resource "azurerm_availability_set" "avset" { + name = "${var.dns_name}avset" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + platform_fault_domain_count = 2 + platform_update_domain_count = 2 + managed = true +} + +resource "azurerm_public_ip" "lbpip" { + name = "${var.rg_prefix}-ip" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + public_ip_address_allocation = "dynamic" + domain_name_label = "${var.lb_ip_dns_name}" +} + +resource "azurerm_virtual_network" "vnet" { + name = "${var.virtual_network_name}" + location = "${var.location}" + address_space = ["${var.address_space}"] + resource_group_name = "${azurerm_resource_group.rg.name}" +} + +resource "azurerm_subnet" "subnet" { + name = "${var.rg_prefix}subnet" + virtual_network_name = "${azurerm_virtual_network.vnet.name}" + resource_group_name = "${azurerm_resource_group.rg.name}" + address_prefix = "${var.subnet_prefix}" +} + +resource "azurerm_lb" "lb" { + resource_group_name = "${azurerm_resource_group.rg.name}" + name = "${var.rg_prefix}lb" + location = "${var.location}" + + frontend_ip_configuration { + name = "LoadBalancerFrontEnd" + public_ip_address_id = "${azurerm_public_ip.lbpip.id}" + } +} + +resource "azurerm_lb_backend_address_pool" "backend_pool" { + resource_group_name = "${azurerm_resource_group.rg.name}" + loadbalancer_id = "${azurerm_lb.lb.id}" + name = "BackendPool1" +} + +resource "azurerm_lb_nat_rule" "tcp" { + resource_group_name = "${azurerm_resource_group.rg.name}" + loadbalancer_id = "${azurerm_lb.lb.id}" + name = "RDP-VM-${count.index}" + protocol = "tcp" + frontend_port = "5000${count.index + 1}" + backend_port = 3389 + frontend_ip_configuration_name = "LoadBalancerFrontEnd" + count = 2 +} + +resource "azurerm_lb_rule" "lb_rule" { + resource_group_name = "${azurerm_resource_group.rg.name}" + loadbalancer_id = "${azurerm_lb.lb.id}" + name = "LBRule" + protocol = "tcp" + frontend_port = 80 + backend_port = 80 + frontend_ip_configuration_name = "LoadBalancerFrontEnd" + enable_floating_ip = false + backend_address_pool_id = "${azurerm_lb_backend_address_pool.backend_pool.id}" + idle_timeout_in_minutes = 5 + probe_id = "${azurerm_lb_probe.lb_probe.id}" + depends_on = ["azurerm_lb_probe.lb_probe"] +} + +resource "azurerm_lb_probe" "lb_probe" { + resource_group_name = "${azurerm_resource_group.rg.name}" + loadbalancer_id = "${azurerm_lb.lb.id}" + name = "tcpProbe" + protocol = "tcp" + port = 80 + interval_in_seconds = 5 + number_of_probes = 2 +} + +resource "azurerm_network_interface" "nic" { + name = "nic${count.index}" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + count = 2 + + ip_configuration { + name = "ipconfig${count.index}" + subnet_id = "${azurerm_subnet.subnet.id}" + private_ip_address_allocation = "Dynamic" + load_balancer_backend_address_pools_ids = ["${azurerm_lb_backend_address_pool.backend_pool.id}"] + load_balancer_inbound_nat_rules_ids = ["${element(azurerm_lb_nat_rule.tcp.*.id, count.index)}"] + } +} + +resource "azurerm_virtual_machine" "vm" { + name = "vm${count.index}" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + availability_set_id = "${azurerm_availability_set.avset.id}" + vm_size = "${var.vm_size}" + network_interface_ids = ["${element(azurerm_network_interface.nic.*.id, count.index)}"] + count = 2 + + storage_image_reference { + publisher = "${var.image_publisher}" + offer = "${var.image_offer}" + sku = "${var.image_sku}" + version = "${var.image_version}" + } + + storage_os_disk { + name = "osdisk${count.index}" + create_option = "FromImage" + } + + os_profile { + computer_name = "${var.hostname}" + admin_username = "${var.admin_username}" + admin_password = "${var.admin_password}" + } +} diff --git a/examples/azure-2-vms-loadbalancer-lbrules/outputs.tf b/examples/azure-2-vms-loadbalancer-lbrules/outputs.tf new file mode 100644 index 000000000000..a21a9568b688 --- /dev/null +++ b/examples/azure-2-vms-loadbalancer-lbrules/outputs.tf @@ -0,0 +1,11 @@ +output "hostname" { + value = "${var.hostname}" +} + +output "vm_fqdn" { + value = "${azurerm_public_ip.lbpip.fqdn}" +} + +output "sshCommand" { + value = "ssh ${var.admin_username}@${azurerm_public_ip.lbpip.fqdn}" +} diff --git a/examples/azure-2-vms-loadbalancer-lbrules/provider.tf b/examples/azure-2-vms-loadbalancer-lbrules/provider.tf new file mode 100644 index 000000000000..bdf0583f3259 --- /dev/null +++ b/examples/azure-2-vms-loadbalancer-lbrules/provider.tf @@ -0,0 +1,6 @@ +# provider "azurerm" { +# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" +# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" +# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" +# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" +# } diff --git a/examples/azure-2-vms-loadbalancer-lbrules/terraform.tfvars b/examples/azure-2-vms-loadbalancer-lbrules/terraform.tfvars new file mode 100644 index 000000000000..824d4a691b61 --- /dev/null +++ b/examples/azure-2-vms-loadbalancer-lbrules/terraform.tfvars @@ -0,0 +1,6 @@ +# resource_group = "myresourcegroup" +# rg_prefix = "rg" +# hostname = "myvm" +# dns_name = "mydnsname" +# location = "southcentralus" +# admin_password = "T3rr@f0rmP@ssword" diff --git a/examples/azure-2-vms-loadbalancer-lbrules/variables.tf b/examples/azure-2-vms-loadbalancer-lbrules/variables.tf new file mode 100644 index 000000000000..55320c2653a4 --- /dev/null +++ b/examples/azure-2-vms-loadbalancer-lbrules/variables.tf @@ -0,0 +1,80 @@ +variable "resource_group" { + description = "The name of the resource group in which to create the virtual network." +} + +variable "rg_prefix" { + description = "The shortened abbreviation to represent your resource group that will go on the front of some resources." + default = "rg" +} + +variable "hostname" { + description = "VM name referenced also in storage-related names." +} + +variable "dns_name" { + description = " Label for the Domain Name. Will be used to make up the FQDN. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system." +} + +variable "lb_ip_dns_name" { + description = "DNS for Load Balancer IP" +} + +variable "location" { + description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." + default = "southcentralus" +} + +variable "virtual_network_name" { + description = "The name for the virtual network." + default = "vnet" +} + +variable "address_space" { + description = "The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created." + default = "10.0.0.0/16" +} + +variable "subnet_prefix" { + description = "The address prefix to use for the subnet." + default = "10.0.10.0/24" +} + +variable "storage_account_type" { + description = "Defines the type of storage account to be created. Valid options are Standard_LRS, Standard_ZRS, Standard_GRS, Standard_RAGRS, Premium_LRS. Changing this is sometimes valid - see the Azure documentation for more information on which types of accounts can be converted into other types." + default = "Standard_LRS" +} + +variable "vm_size" { + description = "Specifies the size of the virtual machine." + default = "Standard_D1" +} + +variable "image_publisher" { + description = "name of the publisher of the image (az vm image list)" + default = "MicrosoftWindowsServer" +} + +variable "image_offer" { + description = "the name of the offer (az vm image list)" + default = "WindowsServer" +} + +variable "image_sku" { + description = "image sku to apply (az vm image list)" + default = "2012-R2-Datacenter" +} + +variable "image_version" { + description = "version of the image to apply (az vm image list)" + default = "latest" +} + +variable "admin_username" { + description = "administrator user name" + default = "vmadmin" +} + +variable "admin_password" { + description = "administrator password (recommended to disable password auth)" + default = "T3rr@f0rmP@ssword" +} From 888b57830ff72f511e8204a48004f705f92e769e Mon Sep 17 00:00:00 2001 From: Scott Nowicki Date: Mon, 8 May 2017 15:23:30 -0500 Subject: [PATCH 64/67] add CI build tag --- examples/azure-vm-simple-linux-managed-disk/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/azure-vm-simple-linux-managed-disk/README.md b/examples/azure-vm-simple-linux-managed-disk/README.md index 0492e3edccb9..79b1d7ee9458 100644 --- a/examples/azure-vm-simple-linux-managed-disk/README.md +++ b/examples/azure-vm-simple-linux-managed-disk/README.md @@ -1,4 +1,4 @@ -# Very simple deployment of a Linux VM +# Very simple deployment of a Linux VM [![Build Status](https://travis-ci.org/harijayms/terraform.svg?branch=topic-101-vm-simple-linux)](https://travis-ci.org/harijayms/terraform) From a0b4cadd9dd7ace318677cc457b92f4ce86cbca9 Mon Sep 17 00:00:00 2001 From: Annie Hedgpeth Date: Tue, 9 May 2017 19:24:15 -0500 Subject: [PATCH 65/67] azure-traffic-manager-vm (#17) adding example of azure traffic manager --- .travis.yml | 1 + examples/azure-traffic-manager-vm/README.md | 27 ++++ .../azure-traffic-manager-vm/deploy.ci.sh | 36 +++++ .../azure-traffic-manager-vm/deploy.mac.sh | 15 +++ examples/azure-traffic-manager-vm/main.tf | 125 ++++++++++++++++++ examples/azure-traffic-manager-vm/outputs.tf | 3 + .../azure-traffic-manager-vm/variables.tf | 71 ++++++++++ 7 files changed, 278 insertions(+) create mode 100644 examples/azure-traffic-manager-vm/README.md create mode 100755 examples/azure-traffic-manager-vm/deploy.ci.sh create mode 100755 examples/azure-traffic-manager-vm/deploy.mac.sh create mode 100644 examples/azure-traffic-manager-vm/main.tf create mode 100644 examples/azure-traffic-manager-vm/outputs.tf create mode 100644 examples/azure-traffic-manager-vm/variables.tf diff --git a/.travis.yml b/.travis.yml index 93e8b1d03795..c210660191aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ git: # establish environment variables env: + - TEST_DIR=examples/azure-traffic-manager-vm - TEST_DIR=examples/azure-vm-from-user-image - TEST_DIR=examples/azure-cdn-with-storage-account - TEST_DIR=examples/azure-vm-simple-linux-managed-disk diff --git a/examples/azure-traffic-manager-vm/README.md b/examples/azure-traffic-manager-vm/README.md new file mode 100644 index 000000000000..e9e7ee1f3898 --- /dev/null +++ b/examples/azure-traffic-manager-vm/README.md @@ -0,0 +1,27 @@ +# Azure Traffic Manager with virtual machines + +This Terraform template was based on [this](https://github.com/Azure/azure-quickstart-templates/tree/master/201-traffic-manager-vm) Azure Quickstart Template. Changes to the ARM template that may have occurred since the creation of this example may not be reflected here. + +This template shows how to create an Azure Traffic Manager profile to load-balance across a couple of Azure virtual machines. Each endpoint has an equal weight but different weights can be specified to distribute load non-uniformly. + +See also: + +- Traffic Manager routing methods for details of the different routing methods available. +- Create or update a Traffic Manager profile for details of the JSON elements relating to a Traffic Manager profile. + +## main.tf +The `main.tf` file contains the actual resources that will be deployed. It also contains the Azure Resource Group definition and any defined variables. + +## outputs.tf +This data is outputted when `terraform apply` is called, and can be queried using the `terraform output` command. + +## provider.tf +Azure requires that an application is added to Azure Active Directory to generate the `client_id`, `client_secret`, and `tenant_id` needed by Terraform (`subscription_id` can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this to populate your `provider.tf` file. + +## terraform.tfvars +If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. + +If you are committing this template to source control, please insure that you add this file to your `.gitignore` file. + +## variables.tf +The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. diff --git a/examples/azure-traffic-manager-vm/deploy.ci.sh b/examples/azure-traffic-manager-vm/deploy.ci.sh new file mode 100755 index 000000000000..c9f291444666 --- /dev/null +++ b/examples/azure-traffic-manager-vm/deploy.ci.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -o errexit -o nounset + +docker run --rm -it \ + -e ARM_CLIENT_ID \ + -e ARM_CLIENT_SECRET \ + -e ARM_SUBSCRIPTION_ID \ + -e ARM_TENANT_ID \ + -v $(pwd):/data \ + --workdir=/data \ + --entrypoint "/bin/sh" \ + hashicorp/terraform:light \ + -c "/bin/terraform get; \ + /bin/terraform validate; \ + /bin/terraform plan -out=out.tfplan -var dns_name=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD; \ + /bin/terraform apply out.tfplan; \ + /bin/terraform show;" + +# cleanup deployed azure resources via azure-cli +docker run --rm -it \ + azuresdk/azure-cli-python \ + sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID > /dev/null; \ + az vm show -g $KEY -n rgvm" + +# cleanup deployed azure resources via terraform +docker run --rm -it \ + -e ARM_CLIENT_ID \ + -e ARM_CLIENT_SECRET \ + -e ARM_SUBSCRIPTION_ID \ + -e ARM_TENANT_ID \ + -v $(pwd):/data \ + --workdir=/data \ + --entrypoint "/bin/sh" \ + hashicorp/terraform:light \ + -c "/bin/terraform destroy -force -var dns_name=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD;" \ No newline at end of file diff --git a/examples/azure-traffic-manager-vm/deploy.mac.sh b/examples/azure-traffic-manager-vm/deploy.mac.sh new file mode 100755 index 000000000000..dfc34c2be2fc --- /dev/null +++ b/examples/azure-traffic-manager-vm/deploy.mac.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -o errexit -o nounset + +if docker -v; then + + # generate a unique string for CI deployment + export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) + export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) + + /bin/sh ./deploy.ci.sh + +else + echo "Docker is used to run terraform commands, please install before run: https://docs.docker.com/docker-for-mac/install/" +fi \ No newline at end of file diff --git a/examples/azure-traffic-manager-vm/main.tf b/examples/azure-traffic-manager-vm/main.tf new file mode 100644 index 000000000000..ef34a8ad5391 --- /dev/null +++ b/examples/azure-traffic-manager-vm/main.tf @@ -0,0 +1,125 @@ +# provider "azurerm" { +# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" +# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" +# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" +# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" +# } + +resource "azurerm_resource_group" "rg" { + name = "${var.resource_group}" + location = "${var.location}" +} + +resource "azurerm_public_ip" "pip" { + name = "ip${count.index}" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + public_ip_address_allocation = "dynamic" + domain_name_label = "${var.dns_name}${count.index}" + count = "${var.num_vms}" +} + +resource "azurerm_virtual_network" "vnet" { + name = "${var.vnet}" + location = "${var.location}" + address_space = ["${var.address_space}"] + resource_group_name = "${azurerm_resource_group.rg.name}" +} + +resource "azurerm_subnet" "subnet" { + name = "${var.subnet_name}" + virtual_network_name = "${azurerm_virtual_network.vnet.name}" + resource_group_name = "${azurerm_resource_group.rg.name}" + address_prefix = "${var.subnet_prefix}" +} + +resource "azurerm_network_interface" "nic" { + name = "nic${count.index}" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + count = "${var.num_vms}" + + ip_configuration { + name = "ipconfig${count.index}" + subnet_id = "${azurerm_subnet.subnet.id}" + private_ip_address_allocation = "Dynamic" + public_ip_address_id = "${element(azurerm_public_ip.pip.*.id, count.index)}" + } +} + +resource "azurerm_virtual_machine" "vm" { + name = "vm${count.index}" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + vm_size = "${var.vm_size}" + count = "${var.num_vms}" + network_interface_ids = ["${element(azurerm_network_interface.nic.*.id, count.index)}"] + + storage_image_reference { + publisher = "${var.image_publisher}" + offer = "${var.image_offer}" + sku = "${var.image_sku}" + version = "${var.image_version}" + } + + storage_os_disk { + name = "osdisk${count.index}" + create_option = "FromImage" + } + + os_profile { + computer_name = "vm${count.index}" + admin_username = "${var.admin_username}" + admin_password = "${var.admin_password}" + } + + os_profile_linux_config { + disable_password_authentication = false + } +} + +resource "azurerm_virtual_machine_extension" "ext" { + depends_on = ["azurerm_virtual_machine.vm"] + name = "CustomScript" + location = "${var.location}" + resource_group_name = "${azurerm_resource_group.rg.name}" + virtual_machine_name = "vm${count.index}" + publisher = "Microsoft.Azure.Extensions" + type = "CustomScript" + type_handler_version = "2.0" + count = "${var.num_vms}" + auto_upgrade_minor_version = true + + settings = < Date: Tue, 9 May 2017 19:54:02 -0500 Subject: [PATCH 66/67] prepped for Hashicorp PR --- .travis.yml | 72 ++++++++++---------- examples/azure-traffic-manager-vm/README.md | 2 + examples/azure-traffic-manager-vm/graph.png | Bin 0 -> 219786 bytes 3 files changed, 39 insertions(+), 35 deletions(-) create mode 100644 examples/azure-traffic-manager-vm/graph.png diff --git a/.travis.yml b/.travis.yml index e22bdbb6a3ae..7fe62879facf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,40 +1,42 @@ -sudo: required +dist: trusty +sudo: false +language: go +go: +- 1.8 -services: - - docker - -language: generic - -# on branches: ignore multiple commits that will queue build jobs, just run latest commit -git: - depth: 1 - -# establish environment variables +# add TF_CONSUL_TEST=1 to run consul tests +# they were causing timouts in travis env: - - TEST_DIR=examples/azure-traffic-manager-vm - - TEST_DIR=examples/azure-vm-from-user-image - - TEST_DIR=examples/azure-cdn-with-storage-account - - TEST_DIR=examples/azure-vm-simple-linux-managed-disk - - TEST_DIR=examples/azure-vnet-two-subnets + - CONSUL_VERSION=0.7.5 GOMAXPROCS=4 +# Fetch consul for the backend and provider tests +before_install: + - curl -sLo consul.zip https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip + - unzip consul.zip + - mkdir ~/bin + - mv consul ~/bin + - export PATH="~/bin:$PATH" + +install: +# This script is used by the Travis build to install a cookie for +# go.googlesource.com so rate limits are higher when using `go get` to fetch +# packages that live there. +# See: https://github.com/golang/go/issues/12933 +- bash scripts/gogetcookie.sh +- go get github.com/kardianos/govendor +script: +- make vet vendor-status test +- GOOS=windows go build branches: only: - - master - - /^(?i:topic)-.*$/ - -# install terraform -before_deploy: - - export KEY=$(cat /dev/urandom | tr -cd 'a-z' | head -c 12) - - export PASSWORD=$KEY$(cat /dev/urandom | tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | tr -cd '0-9' | head -c 2) - - export EXISTING_IMAGE_URI=https://permanentstor.blob.core.windows.net/permanent-vhds/permanent-osdisk1.vhd - - export EXISTING_STORAGE_ACCOUNT_NAME=permanentstor - - export EXISTING_RESOURCE_GROUP=permanent - -# terraform deploy + script -deploy: - - provider: script - skip_cleanup: true - script: cd $TRAVIS_BUILD_DIR/$TEST_DIR && ./deploy.ci.sh - on: - repo: harijayms/terraform - branch: master + - master +notifications: + irc: + channels: + - irc.freenode.org#terraform-tool + skip_join: true + use_notice: true +matrix: + fast_finish: true + allow_failures: + - go: tip \ No newline at end of file diff --git a/examples/azure-traffic-manager-vm/README.md b/examples/azure-traffic-manager-vm/README.md index e9e7ee1f3898..e8fab9df2465 100644 --- a/examples/azure-traffic-manager-vm/README.md +++ b/examples/azure-traffic-manager-vm/README.md @@ -25,3 +25,5 @@ If you are committing this template to source control, please insure that you ad ## variables.tf The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. + +![`terraform graph`](/examples/azure-traffic-manager-vm/graph.png) \ No newline at end of file diff --git a/examples/azure-traffic-manager-vm/graph.png b/examples/azure-traffic-manager-vm/graph.png new file mode 100644 index 0000000000000000000000000000000000000000..fddb46a666fcec9a1702953c25bbc2e1c6f6f375 GIT binary patch literal 219786 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mMDC>(+-m8duDHIX^ldqf+zKnfuWRC5%d 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 literal 0 HcmV?d00001 diff --git a/examples/azure-cdn-with-storage-account/main.tf b/examples/azure-cdn-with-storage-account/main.tf index 1f9c9ecbf843..2e7f56954c3c 100644 --- a/examples/azure-cdn-with-storage-account/main.tf +++ b/examples/azure-cdn-with-storage-account/main.tf @@ -1,3 +1,10 @@ +# provider "azurerm" { +# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" +# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" +# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" +# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" +# } + resource "azurerm_resource_group" "rg" { name = "${var.resource_group}" location = "${var.location}" diff --git a/examples/azure-cdn-with-storage-account/provider.tf.example b/examples/azure-cdn-with-storage-account/provider.tf.example deleted file mode 100644 index 79291f7ca895..000000000000 --- a/examples/azure-cdn-with-storage-account/provider.tf.example +++ /dev/null @@ -1,7 +0,0 @@ -# provider "azurerm" { -# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" -# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" -# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" -# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" -# } - diff --git a/examples/azure-cdn-with-storage-account/terraform.tfvars b/examples/azure-cdn-with-storage-account/terraform.tfvars deleted file mode 100644 index bee98e4e11bc..000000000000 --- a/examples/azure-cdn-with-storage-account/terraform.tfvars +++ /dev/null @@ -1,8 +0,0 @@ -# Replace with relevant values - -# resource_group = "myresourcegroup" -# rg_prefix = "rg" -# hostname = "myvm" -# dns_name = "mydnsname" -# location = "southcentralus" -# admin_password = "T3rr@f0rmP@ssword" diff --git a/examples/azure-vm-from-user-image/README.md b/examples/azure-vm-from-user-image/README.md deleted file mode 100644 index 7f575a7926de..000000000000 --- a/examples/azure-vm-from-user-image/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# [Create a Virtual Machine from a User Image](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/cli-deploy-templates#create-a-custom-vm-image) - -This Terraform template was based on [this](https://github.com/Azure/azure-quickstart-templates/tree/master/101-vm-from-user-image) Azure Quickstart Template. Changes to the ARM template that may have occured since the creation of this example may not be reflected here. - - - - - -> Prerequisite - The generalized image VHD should exist, as well as a Storage Account for boot diagnostics - -This template allows you to create a Virtual Machine from an unmanaged User image vhd. This template also deploys a Virtual Network, Public IP addresses and a Network Interface. - -## main.tf -The `main.tf` file contains the actual resources that will be deployed. It also contains the Azure Resource Group definition and any defined variables. - -## outputs.tf -This data is outputted when `terraform apply` is called, and can be queried using the `terraform output` command. - -## provider.tf -Azure requires that an application is added to Azure Active Directory to generate the `client_id`, `client_secret`, and `tenant_id` needed by Terraform (`subscription_id` can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this to populate your `provider.tf` file. - -## terraform.tfvars -If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. - -If you are committing this template to source control, please insure that you add this file to your `.gitignore` file. - -## variables.tf -The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. diff --git a/examples/azure-vm-from-user-image/deploy.ci.sh b/examples/azure-vm-from-user-image/deploy.ci.sh deleted file mode 100755 index 37578ed7db12..000000000000 --- a/examples/azure-vm-from-user-image/deploy.ci.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash - -set -o errexit -o nounset - -docker run --rm -it \ - -e ARM_CLIENT_ID \ - -e ARM_CLIENT_SECRET \ - -e ARM_SUBSCRIPTION_ID \ - -e ARM_TENANT_ID \ - -v $(pwd):/data \ - --workdir=/data \ - --entrypoint "/bin/sh" \ - hashicorp/terraform:light \ - -c "/bin/terraform get; \ - /bin/terraform validate; \ - /bin/terraform plan -out=out.tfplan -var hostname=$KEY -var resource_group=$EXISTING_RESOURCE_GROUP -var admin_username=$KEY -var admin_password=$PASSWORD -var image_uri=$EXISTING_IMAGE_URI -var storage_account_name=$EXISTING_STORAGE_ACCOUNT_NAME; \ - /bin/terraform apply out.tfplan; \ - /bin/terraform show;" - -docker run --rm -it \ - azuresdk/azure-cli-python \ - sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID > /dev/null; \ - az vm show --name $KEY --resource-group permanent" - -# cleanup deployed azure resources via terraform -docker run --rm -it \ - -e ARM_CLIENT_ID \ - -e ARM_CLIENT_SECRET \ - -e ARM_SUBSCRIPTION_ID \ - -e ARM_TENANT_ID \ - -v $(pwd):/data \ - --workdir=/data \ - --entrypoint "/bin/sh" \ - hashicorp/terraform:light \ - -c "/bin/terraform destroy -force \ - -var hostname=$KEY \ - -var resource_group=$EXISTING_RESOURCE_GROUP \ - -var admin_username=$KEY \ - -var admin_password=$PASSWORD \ - -var image_uri=$EXISTING_IMAGE_URI \ - -var storage_account_name=$EXISTING_STORAGE_ACCOUNT_NAME \ - -target=azurerm_virtual_machine.vm \ - -target=azurerm_network_interface.nic \ - -target=azurerm_virtual_network.vnet \ - -target=azurerm_public_ip.pip;" diff --git a/examples/azure-vm-from-user-image/deploy.mac.sh b/examples/azure-vm-from-user-image/deploy.mac.sh deleted file mode 100755 index acd4ca7028fc..000000000000 --- a/examples/azure-vm-from-user-image/deploy.mac.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -set -o errexit -o nounset - -if docker -v; then - - # generate a unique string for CI deployment - export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) - export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) - export EXISTING_IMAGE_URI=https://permanentstor.blob.core.windows.net/permanent-vhds/permanent-osdisk1.vhd - export EXISTING_STORAGE_ACCOUNT_NAME=permanentstor - export EXISTING_RESOURCE_GROUP=permanent - - /bin/sh ./deploy.ci.sh - -else - echo "Docker is used to run terraform commands, please install before run: https://docs.docker.com/docker-for-mac/install/" -fi diff --git a/examples/azure-vm-from-user-image/main.tf b/examples/azure-vm-from-user-image/main.tf deleted file mode 100644 index 929a87fa7134..000000000000 --- a/examples/azure-vm-from-user-image/main.tf +++ /dev/null @@ -1,66 +0,0 @@ -resource "azurerm_resource_group" "rg" { - name = "${var.resource_group}" - location = "${var.location}" -} - -resource "azurerm_virtual_network" "vnet" { - name = "${var.hostname}vnet" - location = "${var.location}" - address_space = ["${var.address_space}"] - resource_group_name = "${azurerm_resource_group.rg.name}" -} - -resource "azurerm_subnet" "subnet" { - name = "${var.hostname}subnet" - virtual_network_name = "${azurerm_virtual_network.vnet.name}" - resource_group_name = "${azurerm_resource_group.rg.name}" - address_prefix = "${var.subnet_prefix}" -} - -resource "azurerm_network_interface" "nic" { - name = "${var.hostname}nic" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - - ip_configuration { - name = "${var.hostname}ipconfig" - subnet_id = "${azurerm_subnet.subnet.id}" - private_ip_address_allocation = "Dynamic" - public_ip_address_id = "${azurerm_public_ip.pip.id}" - } -} - -resource "azurerm_public_ip" "pip" { - name = "${var.hostname}-ip" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - public_ip_address_allocation = "dynamic" - domain_name_label = "${var.hostname}" -} - -resource "azurerm_virtual_machine" "vm" { - name = "${var.hostname}" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - vm_size = "${var.vm_size}" - network_interface_ids = ["${azurerm_network_interface.nic.id}"] - - storage_os_disk { - name = "${var.hostname}-osdisk1" - image_uri = "${var.image_uri}" - vhd_uri = "https://${var.storage_account_name}.blob.core.windows.net/vhds/${var.hostname}osdisk.vhd" - os_type = "${var.os_type}" - caching = "ReadWrite" - create_option = "FromImage" - } - - os_profile { - computer_name = "${var.hostname}" - admin_username = "${var.admin_username}" - admin_password = "${var.admin_password}" - } - - os_profile_linux_config { - disable_password_authentication = false - } -} diff --git a/examples/azure-vm-from-user-image/outputs.tf b/examples/azure-vm-from-user-image/outputs.tf deleted file mode 100644 index e0e255a9e01c..000000000000 --- a/examples/azure-vm-from-user-image/outputs.tf +++ /dev/null @@ -1,11 +0,0 @@ -output "hostname" { - value = "${var.hostname}" -} - -output "vm_fqdn" { - value = "${azurerm_public_ip.pip.fqdn}" -} - -output "sshCommand" { - value = "${concat("ssh ", var.admin_username, "@", azurerm_public_ip.pip.fqdn)}" -} diff --git a/examples/azure-vm-from-user-image/provider.tf b/examples/azure-vm-from-user-image/provider.tf deleted file mode 100644 index bdf0583f3259..000000000000 --- a/examples/azure-vm-from-user-image/provider.tf +++ /dev/null @@ -1,6 +0,0 @@ -# provider "azurerm" { -# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" -# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" -# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" -# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" -# } diff --git a/examples/azure-vm-from-user-image/terraform.tfvars b/examples/azure-vm-from-user-image/terraform.tfvars deleted file mode 100644 index 15da2a9c6ca7..000000000000 --- a/examples/azure-vm-from-user-image/terraform.tfvars +++ /dev/null @@ -1,13 +0,0 @@ -# resource_group = "myresourcegroup" -# image_uri = "https://DISK.blob.core.windows.net/vhds/ORIGINAL-VM.vhd" -# primary_blob_endpoint = "https://DISK.blob.core.windows.net/" -# location = "southcentralus" -# os_type = "linux" -# address_space = "10.0.0.0/16" -# subnet_prefix = "10.0.0.0/24" -# storage_account_name = "STOR-ACCT-NAME" -# storage_account_type = "Standard_LRS" -# vm_size = "Standard_DS1_v2" -# hostname = "HOSTNAME" -# admin_username = "vmadmin" -# admin_password = "YOURPASSWORDHERE" diff --git a/examples/azure-vm-from-user-image/variables.tf b/examples/azure-vm-from-user-image/variables.tf deleted file mode 100644 index b3e94928978d..000000000000 --- a/examples/azure-vm-from-user-image/variables.tf +++ /dev/null @@ -1,56 +0,0 @@ -variable "resource_group" { - description = "The name of the resource group in which the image to clone resides." - default = "myrg" -} - -variable "image_uri" { - description = "Specifies the image_uri in the form publisherName:offer:skus:version. image_uri can also specify the VHD uri of a custom VM image to clone." -} - -variable "os_type" { - description = "Specifies the operating system Type, valid values are windows, linux." - default = "linux" -} - -variable "location" { - description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." - default = "southcentralus" -} - -variable "address_space" { - description = "The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created." - default = "10.0.0.0/24" -} - -variable "subnet_prefix" { - description = "The address prefix to use for the subnet." - default = "10.0.0.0/24" -} - -variable "storage_account_name" { - description = "The name of the storage account in which the image from which you are cloning resides." -} - -variable "storage_account_type" { - description = "Defines the type of storage account to be created. Valid options are Standard_LRS, Standard_ZRS, Standard_GRS, Standard_RAGRS, Premium_LRS. Changing this is sometimes valid - see the Azure documentation for more information on which types of accounts can be converted into other types." - default = "Premium_LRS" -} - -variable "vm_size" { - description = "Specifies the size of the virtual machine. This must be the same as the vm image from which you are copying." - default = "Standard_DS1_v2" -} - -variable "hostname" { - description = "VM name referenced also in storage-related names. This is also used as the label for the Domain Name and to make up the FQDN. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system." -} - -variable "admin_username" { - description = "administrator user name" - default = "vmadmin" -} - -variable "admin_password" { - description = "administrator password (recommended to disable password auth)" - default = "T3rr@f0rmP@ssword" -} diff --git a/examples/azure-vm-simple-linux-managed-disk/README.md b/examples/azure-vm-simple-linux-managed-disk/README.md index 79b1d7ee9458..26afb451e81d 100644 --- a/examples/azure-vm-simple-linux-managed-disk/README.md +++ b/examples/azure-vm-simple-linux-managed-disk/README.md @@ -1,10 +1,6 @@ # Very simple deployment of a Linux VM [![Build Status](https://travis-ci.org/harijayms/terraform.svg?branch=topic-101-vm-simple-linux)](https://travis-ci.org/harijayms/terraform) - - - - -This template allows you to deploy a simple Linux VM using a few different options for the Ubuntu version, using the latest patched version. This will deploy a A1 size VM in the resource group location and return the FQDN of the VM. +This template allows you to deploy a simple Linux VM using a few different options for the Ubuntu version, using the latest patched version. This will deploy an A0 size VM in the resource group location and return the FQDN of the VM. This template takes a minimum amount of parameters and deploys a Linux VM, using the latest patched version. @@ -21,4 +17,4 @@ Azure requires that an application is added to Azure Active Directory to generat If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. ## variables.tf -The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. +The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. \ No newline at end of file diff --git a/examples/azure-vm-simple-linux-managed-disk/after_deploy.ci.sh b/examples/azure-vm-simple-linux-managed-disk/after_deploy.ci.sh deleted file mode 100755 index 245aba38045e..000000000000 --- a/examples/azure-vm-simple-linux-managed-disk/after_deploy.ci.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -set -o errexit -o nounset - -# cleanup deployed azure resources -docker run --rm -it \ - azuresdk/azure-cli-python \ - sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID; \ - az group delete -y -n $KEY" diff --git a/examples/azure-vm-simple-linux-managed-disk/deploy.mac.sh b/examples/azure-vm-simple-linux-managed-disk/deploy.mac.sh index 9c6563f07d71..dfc34c2be2fc 100755 --- a/examples/azure-vm-simple-linux-managed-disk/deploy.mac.sh +++ b/examples/azure-vm-simple-linux-managed-disk/deploy.mac.sh @@ -12,4 +12,4 @@ if docker -v; then else echo "Docker is used to run terraform commands, please install before run: https://docs.docker.com/docker-for-mac/install/" -fi +fi \ No newline at end of file diff --git a/examples/azure-vm-simple-linux-managed-disk/main.tf b/examples/azure-vm-simple-linux-managed-disk/main.tf index 8ef283c7819c..5dc9ce1cb086 100644 --- a/examples/azure-vm-simple-linux-managed-disk/main.tf +++ b/examples/azure-vm-simple-linux-managed-disk/main.tf @@ -1,3 +1,10 @@ +# provider "azurerm" { +# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" +# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" +# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" +# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" +# } + resource "azurerm_resource_group" "rg" { name = "${var.resource_group}" location = "${var.location}" @@ -34,7 +41,7 @@ resource "azurerm_public_ip" "pip" { name = "${var.rg_prefix}-ip" location = "${var.location}" resource_group_name = "${azurerm_resource_group.rg.name}" - public_ip_address_allocation = "dynamic" + public_ip_address_allocation = "Dynamic" domain_name_label = "${var.dns_name}" } @@ -95,7 +102,7 @@ resource "azurerm_virtual_machine" "vm" { } boot_diagnostics { - enabled = "true" + enabled = true storage_uri = "${azurerm_storage_account.stor.primary_blob_endpoint}" } } \ No newline at end of file diff --git a/examples/azure-vm-simple-linux-managed-disk/outputs.tf b/examples/azure-vm-simple-linux-managed-disk/outputs.tf index 9e3c2f0712bc..32c6294ceeab 100644 --- a/examples/azure-vm-simple-linux-managed-disk/outputs.tf +++ b/examples/azure-vm-simple-linux-managed-disk/outputs.tf @@ -6,6 +6,6 @@ output "vm_fqdn" { value = "${azurerm_public_ip.pip.fqdn}" } -output "sshCommand" { +output "ssh_command" { value = "ssh ${var.admin_username}@${azurerm_public_ip.pip.fqdn}" -} +} \ No newline at end of file diff --git a/examples/azure-vm-simple-linux-managed-disk/provider.tf b/examples/azure-vm-simple-linux-managed-disk/provider.tf deleted file mode 100644 index 79291f7ca895..000000000000 --- a/examples/azure-vm-simple-linux-managed-disk/provider.tf +++ /dev/null @@ -1,7 +0,0 @@ -# provider "azurerm" { -# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" -# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" -# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" -# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" -# } - diff --git a/examples/azure-vm-simple-linux-managed-disk/terraform.tfvars b/examples/azure-vm-simple-linux-managed-disk/terraform.tfvars deleted file mode 100644 index bee98e4e11bc..000000000000 --- a/examples/azure-vm-simple-linux-managed-disk/terraform.tfvars +++ /dev/null @@ -1,8 +0,0 @@ -# Replace with relevant values - -# resource_group = "myresourcegroup" -# rg_prefix = "rg" -# hostname = "myvm" -# dns_name = "mydnsname" -# location = "southcentralus" -# admin_password = "T3rr@f0rmP@ssword" diff --git a/examples/azure-vnet-two-subnets/README.md b/examples/azure-vnet-two-subnets/README.md index dc1c50277301..1f47743df524 100644 --- a/examples/azure-vnet-two-subnets/README.md +++ b/examples/azure-vnet-two-subnets/README.md @@ -1,8 +1,4 @@ -# Virtual Network with two Subnets - - - - +# Virtual Network with Two Subnets This template allows you to create a Virtual Network with two subnets. diff --git a/examples/azure-vnet-two-subnets/deploy.ci.sh b/examples/azure-vnet-two-subnets/deploy.ci.sh index ee119ea4269f..49fb955c7eab 100755 --- a/examples/azure-vnet-two-subnets/deploy.ci.sh +++ b/examples/azure-vnet-two-subnets/deploy.ci.sh @@ -38,4 +38,4 @@ docker run --rm -it \ --workdir=/data \ --entrypoint "/bin/sh" \ hashicorp/terraform:light \ - -c "/bin/terraform destroy -force -var resource_group=$KEY;" + -c "/bin/terraform destroy -force -var resource_group=$KEY;" \ No newline at end of file diff --git a/examples/azure-vnet-two-subnets/deploy.mac.sh b/examples/azure-vnet-two-subnets/deploy.mac.sh index 9c6563f07d71..dfc34c2be2fc 100755 --- a/examples/azure-vnet-two-subnets/deploy.mac.sh +++ b/examples/azure-vnet-two-subnets/deploy.mac.sh @@ -12,4 +12,4 @@ if docker -v; then else echo "Docker is used to run terraform commands, please install before run: https://docs.docker.com/docker-for-mac/install/" -fi +fi \ No newline at end of file diff --git a/examples/azure-vnet-two-subnets/main.tf b/examples/azure-vnet-two-subnets/main.tf index ef74357ce256..aee3593f3efa 100644 --- a/examples/azure-vnet-two-subnets/main.tf +++ b/examples/azure-vnet-two-subnets/main.tf @@ -1,3 +1,10 @@ +# provider "azurerm" { +# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" +# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" +# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" +# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" +# } + resource "azurerm_resource_group" "rg" { name = "${var.resource_group}" location = "${var.location}" diff --git a/examples/azure-vnet-two-subnets/provider.tf.example b/examples/azure-vnet-two-subnets/provider.tf.example deleted file mode 100644 index 327ceb55eefa..000000000000 --- a/examples/azure-vnet-two-subnets/provider.tf.example +++ /dev/null @@ -1,6 +0,0 @@ -provider "azurerm" { - subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" - client_id = "REPLACE-WITH-YOUR-CLIENT-ID" - client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" - tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" -} \ No newline at end of file diff --git a/helper/schema/serialize.go b/helper/schema/serialize.go index 3eb2d0075c4d..fe6d7504c748 100644 --- a/helper/schema/serialize.go +++ b/helper/schema/serialize.go @@ -85,6 +85,9 @@ func SerializeValueForHash(buf *bytes.Buffer, val interface{}, schema *Schema) { // to hash complex substructures when used in sets, and so the serialization // is not reversible. func SerializeResourceForHash(buf *bytes.Buffer, val interface{}, resource *Resource) { + if val == nil { + return + } sm := resource.Schema m := val.(map[string]interface{}) var keys []string diff --git a/helper/schema/set_test.go b/helper/schema/set_test.go index 87a9f72282ed..21f292954095 100644 --- a/helper/schema/set_test.go +++ b/helper/schema/set_test.go @@ -111,3 +111,20 @@ func TestSetUnion(t *testing.T) { func testSetInt(v interface{}) int { return v.(int) } + +func TestHashResource_nil(t *testing.T) { + resource := &Resource{ + Schema: map[string]*Schema{ + "name": { + Type: TypeString, + Optional: true, + }, + }, + } + f := HashResource(resource) + + idx := f(nil) + if idx != 0 { + t.Fatalf("Expected 0 when hashing nil, given: %d", idx) + } +} diff --git a/main.go b/main.go index b94de2ebc1c9..237581200e2e 100644 --- a/main.go +++ b/main.go @@ -258,6 +258,15 @@ func copyOutput(r io.Reader, doneCh chan<- struct{}) { if runtime.GOOS == "windows" { stdout = colorable.NewColorableStdout() stderr = colorable.NewColorableStderr() + + // colorable is not concurrency-safe when stdout and stderr are the + // same console, so we need to add some synchronization to ensure that + // we can't be concurrently writing to both stderr and stdout at + // once, or else we get intermingled writes that create gibberish + // in the console. + wrapped := synchronizedWriters(stdout, stderr) + stdout = wrapped[0] + stderr = wrapped[1] } var wg sync.WaitGroup diff --git a/synchronized_writers.go b/synchronized_writers.go new file mode 100644 index 000000000000..2533d1316c91 --- /dev/null +++ b/synchronized_writers.go @@ -0,0 +1,31 @@ +package main + +import ( + "io" + "sync" +) + +type synchronizedWriter struct { + io.Writer + mutex *sync.Mutex +} + +// synchronizedWriters takes a set of writers and returns wrappers that ensure +// that only one write can be outstanding at a time across the whole set. +func synchronizedWriters(targets ...io.Writer) []io.Writer { + mutex := &sync.Mutex{} + ret := make([]io.Writer, len(targets)) + for i, target := range targets { + ret[i] = &synchronizedWriter{ + Writer: target, + mutex: mutex, + } + } + return ret +} + +func (w *synchronizedWriter) Write(p []byte) (int, error) { + w.mutex.Lock() + defer w.mutex.Unlock() + return w.Writer.Write(p) +} diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index 3851e406bd9d..aeabcb0217b5 100644 --- a/terraform/context_plan_test.go +++ b/terraform/context_plan_test.go @@ -1871,6 +1871,107 @@ func TestContext2Plan_countIncreaseFromOneCorrupted(t *testing.T) { } } +// A common pattern in TF configs is to have a set of resources with the same +// count and to use count.index to create correspondences between them: +// +// foo_id = "${foo.bar.*.id[count.index]}" +// +// This test is for the situation where some instances already exist and the +// count is increased. In that case, we should see only the create diffs +// for the new instances and not any update diffs for the existing ones. +func TestContext2Plan_countIncreaseWithSplatReference(t *testing.T) { + m := testModule(t, "plan-count-splat-reference") + p := testProvider("aws") + p.DiffFn = testDiffFn + s := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.foo.0": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "name": "foo 0", + }, + }, + }, + "aws_instance.foo.1": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "name": "foo 1", + }, + }, + }, + "aws_instance.bar.0": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "foo_name": "foo 0", + }, + }, + }, + "aws_instance.bar.1": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "foo_name": "foo 1", + }, + }, + }, + }, + }, + }, + } + ctx := testContext2(t, &ContextOpts{ + Module: m, + Providers: map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + State: s, + }) + + plan, err := ctx.Plan() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := strings.TrimSpace(plan.String()) + expected := strings.TrimSpace(` +DIFF: + +CREATE: aws_instance.bar.2 + foo_name: "" => "foo 2" + type: "" => "aws_instance" +CREATE: aws_instance.foo.2 + name: "" => "foo 2" + type: "" => "aws_instance" + +STATE: + +aws_instance.bar.0: + ID = bar + foo_name = foo 0 +aws_instance.bar.1: + ID = bar + foo_name = foo 1 +aws_instance.foo.0: + ID = bar + name = foo 0 +aws_instance.foo.1: + ID = bar + name = foo 1 +`) + if actual != expected { + t.Fatalf("bad:\n%s", actual) + } +} + func TestContext2Plan_destroy(t *testing.T) { m := testModule(t, "plan-destroy") p := testProvider("aws") diff --git a/terraform/interpolate.go b/terraform/interpolate.go index 855548c05588..19dcf21f5103 100644 --- a/terraform/interpolate.go +++ b/terraform/interpolate.go @@ -594,10 +594,6 @@ func (i *Interpolater) computeResourceMultiVariable( } if singleAttr, ok := r.Primary.Attributes[v.Field]; ok { - if singleAttr == config.UnknownVariableValue { - return &unknownVariable, nil - } - values = append(values, singleAttr) continue } @@ -613,10 +609,6 @@ func (i *Interpolater) computeResourceMultiVariable( return nil, err } - if multiAttr == unknownVariable { - return &unknownVariable, nil - } - values = append(values, multiAttr) } diff --git a/terraform/interpolate_test.go b/terraform/interpolate_test.go index 6f1d2c34485d..60605fc33139 100644 --- a/terraform/interpolate_test.go +++ b/terraform/interpolate_test.go @@ -359,8 +359,81 @@ func TestInterpolater_resourceVariableMulti(t *testing.T) { } testInterpolate(t, i, scope, "aws_instance.web.*.foo", ast.Variable{ - Value: config.UnknownVariableValue, - Type: ast.TypeUnknown, + Type: ast.TypeList, + Value: []ast.Variable{ + { + Type: ast.TypeUnknown, + Value: config.UnknownVariableValue, + }, + }, + }) +} + +func TestInterpolater_resourceVariableMultiPartialUnknown(t *testing.T) { + lock := new(sync.RWMutex) + state := &State{ + Modules: []*ModuleState{ + &ModuleState{ + Path: rootModulePath, + Resources: map[string]*ResourceState{ + "aws_instance.web.0": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "foo": "1", + }, + }, + }, + "aws_instance.web.1": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "foo": config.UnknownVariableValue, + }, + }, + }, + "aws_instance.web.2": &ResourceState{ + Type: "aws_instance", + Primary: &InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "foo": "2", + }, + }, + }, + }, + }, + }, + } + + i := &Interpolater{ + Module: testModule(t, "interpolate-resource-variable-multi"), + State: state, + StateLock: lock, + } + + scope := &InterpolationScope{ + Path: rootModulePath, + } + + testInterpolate(t, i, scope, "aws_instance.web.*.foo", ast.Variable{ + Type: ast.TypeList, + Value: []ast.Variable{ + { + Type: ast.TypeString, + Value: "1", + }, + { + Type: ast.TypeUnknown, + Value: config.UnknownVariableValue, + }, + { + Type: ast.TypeString, + Value: "2", + }, + }, }) } @@ -408,8 +481,13 @@ func TestInterpolater_resourceVariableMultiList(t *testing.T) { } testInterpolate(t, i, scope, "aws_instance.web.*.ip", ast.Variable{ - Value: config.UnknownVariableValue, - Type: ast.TypeUnknown, + Type: ast.TypeList, + Value: []ast.Variable{ + { + Type: ast.TypeUnknown, + Value: config.UnknownVariableValue, + }, + }, }) } diff --git a/terraform/test-fixtures/interpolate-resource-variable-multi/main.tf b/terraform/test-fixtures/interpolate-resource-variable-multi/main.tf new file mode 100644 index 000000000000..b00b04eff9bb --- /dev/null +++ b/terraform/test-fixtures/interpolate-resource-variable-multi/main.tf @@ -0,0 +1,3 @@ +resource "aws_instance" "web" { + count = 3 +} diff --git a/terraform/test-fixtures/plan-count-splat-reference/main.tf b/terraform/test-fixtures/plan-count-splat-reference/main.tf new file mode 100644 index 000000000000..76834e2555c8 --- /dev/null +++ b/terraform/test-fixtures/plan-count-splat-reference/main.tf @@ -0,0 +1,9 @@ +resource "aws_instance" "foo" { + name = "foo ${count.index}" + count = 3 +} + +resource "aws_instance" "bar" { + foo_name = "${aws_instance.foo.*.name[count.index]}" + count = 3 +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/client.go b/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/client.go new file mode 100644 index 000000000000..916cd50435bc --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/client.go @@ -0,0 +1,59 @@ +// Package sql implements the Azure ARM Sql service API version 2014-04-01. +// +// Provides create, read, update and delete functionality for Azure SQL +// resources including servers, databases, elastic pools, recommendations, +// operations, and usage metrics. +package sql + +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Code generated by Microsoft (R) AutoRest Code Generator 0.17.0.0 +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +import ( + "github.com/Azure/go-autorest/autorest" +) + +const ( + // APIVersion is the version of the Sql + APIVersion = "2014-04-01" + + // DefaultBaseURI is the default URI used for the service Sql + DefaultBaseURI = "https://management.azure.com" +) + +// ManagementClient is the base client for Sql. +type ManagementClient struct { + autorest.Client + BaseURI string + APIVersion string + SubscriptionID string +} + +// New creates an instance of the ManagementClient client. +func New(subscriptionID string) ManagementClient { + return NewWithBaseURI(DefaultBaseURI, subscriptionID) +} + +// NewWithBaseURI creates an instance of the ManagementClient client. +func NewWithBaseURI(baseURI string, subscriptionID string) ManagementClient { + return ManagementClient{ + Client: autorest.NewClientWithUserAgent(UserAgent()), + BaseURI: baseURI, + APIVersion: APIVersion, + SubscriptionID: subscriptionID, + } +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/databases.go b/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/databases.go new file mode 100644 index 000000000000..70a13d619993 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/databases.go @@ -0,0 +1,934 @@ +package sql + +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Code generated by Microsoft (R) AutoRest Code Generator 0.17.0.0 +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +import ( + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "net/http" +) + +// DatabasesClient is the provides create, read, update and delete +// functionality for Azure SQL resources including servers, databases, +// elastic pools, recommendations, operations, and usage metrics. +type DatabasesClient struct { + ManagementClient +} + +// NewDatabasesClient creates an instance of the DatabasesClient client. +func NewDatabasesClient(subscriptionID string) DatabasesClient { + return NewDatabasesClientWithBaseURI(DefaultBaseURI, subscriptionID) +} + +// NewDatabasesClientWithBaseURI creates an instance of the DatabasesClient +// client. +func NewDatabasesClientWithBaseURI(baseURI string, subscriptionID string) DatabasesClient { + return DatabasesClient{NewWithBaseURI(baseURI, subscriptionID)} +} + +// CreateOrUpdate creates a new Azure SQL database or updates an existing +// Azure SQL database. Location is a required property in the request body, +// and it must be the same as the location of the SQL server. This method may +// poll for completion. Polling can be canceled by passing the cancel channel +// argument. The channel will be used to cancel polling and any outstanding +// HTTP requests. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. databaseName +// is the name of the Azure SQL database to be operated on (updated or +// created). parameters is the required parameters for creating or updating a +// database. +func (client DatabasesClient) CreateOrUpdate(resourceGroupName string, serverName string, databaseName string, parameters Database, cancel <-chan struct{}) (result autorest.Response, err error) { + req, err := client.CreateOrUpdatePreparer(resourceGroupName, serverName, databaseName, parameters, cancel) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "CreateOrUpdate", nil, "Failure preparing request") + } + + resp, err := client.CreateOrUpdateSender(req) + if err != nil { + result.Response = resp + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "CreateOrUpdate", resp, "Failure sending request") + } + + result, err = client.CreateOrUpdateResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.DatabasesClient", "CreateOrUpdate", resp, "Failure responding to request") + } + + return +} + +// CreateOrUpdatePreparer prepares the CreateOrUpdate request. +func (client DatabasesClient) CreateOrUpdatePreparer(resourceGroupName string, serverName string, databaseName string, parameters Database, cancel <-chan struct{}) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "databaseName": autorest.Encode("path", databaseName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsJSON(), + autorest.AsPut(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/databases/{databaseName}", pathParameters), + autorest.WithJSON(parameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{Cancel: cancel}) +} + +// CreateOrUpdateSender sends the CreateOrUpdate request. The method will close the +// http.Response Body if it receives an error. +func (client DatabasesClient) CreateOrUpdateSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, + req, + azure.DoPollForAsynchronous(client.PollingDelay)) +} + +// CreateOrUpdateResponder handles the response to the CreateOrUpdate request. The method always +// closes the http.Response Body. +func (client DatabasesClient) CreateOrUpdateResponder(resp *http.Response) (result autorest.Response, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated, http.StatusAccepted), + autorest.ByClosing()) + result.Response = resp + return +} + +// CreateOrUpdateTransparentDataEncryptionConfiguration creates or updates an +// Azure SQL Database Transparent Data Encryption Operation. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. databaseName +// is the name of the Azure SQL database for which setting the Transparent +// Data Encryption applies. parameters is the required parameters for +// creating or updating transparent data encryption. +func (client DatabasesClient) CreateOrUpdateTransparentDataEncryptionConfiguration(resourceGroupName string, serverName string, databaseName string, parameters TransparentDataEncryption) (result TransparentDataEncryption, err error) { + req, err := client.CreateOrUpdateTransparentDataEncryptionConfigurationPreparer(resourceGroupName, serverName, databaseName, parameters) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "CreateOrUpdateTransparentDataEncryptionConfiguration", nil, "Failure preparing request") + } + + resp, err := client.CreateOrUpdateTransparentDataEncryptionConfigurationSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "CreateOrUpdateTransparentDataEncryptionConfiguration", resp, "Failure sending request") + } + + result, err = client.CreateOrUpdateTransparentDataEncryptionConfigurationResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.DatabasesClient", "CreateOrUpdateTransparentDataEncryptionConfiguration", resp, "Failure responding to request") + } + + return +} + +// CreateOrUpdateTransparentDataEncryptionConfigurationPreparer prepares the CreateOrUpdateTransparentDataEncryptionConfiguration request. +func (client DatabasesClient) CreateOrUpdateTransparentDataEncryptionConfigurationPreparer(resourceGroupName string, serverName string, databaseName string, parameters TransparentDataEncryption) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "databaseName": autorest.Encode("path", databaseName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsJSON(), + autorest.AsPut(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/databases/{databaseName}/transparentDataEncryption/current", pathParameters), + autorest.WithJSON(parameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// CreateOrUpdateTransparentDataEncryptionConfigurationSender sends the CreateOrUpdateTransparentDataEncryptionConfiguration request. The method will close the +// http.Response Body if it receives an error. +func (client DatabasesClient) CreateOrUpdateTransparentDataEncryptionConfigurationSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// CreateOrUpdateTransparentDataEncryptionConfigurationResponder handles the response to the CreateOrUpdateTransparentDataEncryptionConfiguration request. The method always +// closes the http.Response Body. +func (client DatabasesClient) CreateOrUpdateTransparentDataEncryptionConfigurationResponder(resp *http.Response) (result TransparentDataEncryption, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// Delete deletes an Azure SQL database. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. databaseName +// is the name of the Azure SQL database to be deleted. +func (client DatabasesClient) Delete(resourceGroupName string, serverName string, databaseName string) (result autorest.Response, err error) { + req, err := client.DeletePreparer(resourceGroupName, serverName, databaseName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "Delete", nil, "Failure preparing request") + } + + resp, err := client.DeleteSender(req) + if err != nil { + result.Response = resp + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "Delete", resp, "Failure sending request") + } + + result, err = client.DeleteResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.DatabasesClient", "Delete", resp, "Failure responding to request") + } + + return +} + +// DeletePreparer prepares the Delete request. +func (client DatabasesClient) DeletePreparer(resourceGroupName string, serverName string, databaseName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "databaseName": autorest.Encode("path", databaseName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsDelete(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/databases/{databaseName}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// DeleteSender sends the Delete request. The method will close the +// http.Response Body if it receives an error. +func (client DatabasesClient) DeleteSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// DeleteResponder handles the response to the Delete request. The method always +// closes the http.Response Body. +func (client DatabasesClient) DeleteResponder(resp *http.Response) (result autorest.Response, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusNoContent), + autorest.ByClosing()) + result.Response = resp + return +} + +// Get gets information about an Azure SQL database. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. databaseName +// is the name of the Azure SQL database to be retrieved. expand is the comma +// separated list of child objects to expand in the response. Possible +// properties: serviceTierAdvisors, upgradeHint, transparentDataEncryption. +func (client DatabasesClient) Get(resourceGroupName string, serverName string, databaseName string, expand string) (result Database, err error) { + req, err := client.GetPreparer(resourceGroupName, serverName, databaseName, expand) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "Get", nil, "Failure preparing request") + } + + resp, err := client.GetSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "Get", resp, "Failure sending request") + } + + result, err = client.GetResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.DatabasesClient", "Get", resp, "Failure responding to request") + } + + return +} + +// GetPreparer prepares the Get request. +func (client DatabasesClient) GetPreparer(resourceGroupName string, serverName string, databaseName string, expand string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "databaseName": autorest.Encode("path", databaseName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + if len(expand) > 0 { + queryParameters["$expand"] = autorest.Encode("query", expand) + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/databases/{databaseName}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// GetSender sends the Get request. The method will close the +// http.Response Body if it receives an error. +func (client DatabasesClient) GetSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// GetResponder handles the response to the Get request. The method always +// closes the http.Response Body. +func (client DatabasesClient) GetResponder(resp *http.Response) (result Database, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// GetServiceTierAdvisor gets information about a service tier advisor. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. databaseName +// is the name of database. serviceTierAdvisorName is the name of service +// tier advisor. +func (client DatabasesClient) GetServiceTierAdvisor(resourceGroupName string, serverName string, databaseName string, serviceTierAdvisorName string) (result ServiceTierAdvisor, err error) { + req, err := client.GetServiceTierAdvisorPreparer(resourceGroupName, serverName, databaseName, serviceTierAdvisorName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "GetServiceTierAdvisor", nil, "Failure preparing request") + } + + resp, err := client.GetServiceTierAdvisorSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "GetServiceTierAdvisor", resp, "Failure sending request") + } + + result, err = client.GetServiceTierAdvisorResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.DatabasesClient", "GetServiceTierAdvisor", resp, "Failure responding to request") + } + + return +} + +// GetServiceTierAdvisorPreparer prepares the GetServiceTierAdvisor request. +func (client DatabasesClient) GetServiceTierAdvisorPreparer(resourceGroupName string, serverName string, databaseName string, serviceTierAdvisorName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "databaseName": autorest.Encode("path", databaseName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "serviceTierAdvisorName": autorest.Encode("path", serviceTierAdvisorName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/databases/{databaseName}/serviceTierAdvisors/{serviceTierAdvisorName}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// GetServiceTierAdvisorSender sends the GetServiceTierAdvisor request. The method will close the +// http.Response Body if it receives an error. +func (client DatabasesClient) GetServiceTierAdvisorSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// GetServiceTierAdvisorResponder handles the response to the GetServiceTierAdvisor request. The method always +// closes the http.Response Body. +func (client DatabasesClient) GetServiceTierAdvisorResponder(resp *http.Response) (result ServiceTierAdvisor, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// GetTransparentDataEncryptionConfiguration gets an Azure SQL Database +// Transparent Data Encryption Response. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. databaseName +// is the name of the Azure SQL database for which the Transparent Data +// Encryption applies. +func (client DatabasesClient) GetTransparentDataEncryptionConfiguration(resourceGroupName string, serverName string, databaseName string) (result TransparentDataEncryption, err error) { + req, err := client.GetTransparentDataEncryptionConfigurationPreparer(resourceGroupName, serverName, databaseName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "GetTransparentDataEncryptionConfiguration", nil, "Failure preparing request") + } + + resp, err := client.GetTransparentDataEncryptionConfigurationSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "GetTransparentDataEncryptionConfiguration", resp, "Failure sending request") + } + + result, err = client.GetTransparentDataEncryptionConfigurationResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.DatabasesClient", "GetTransparentDataEncryptionConfiguration", resp, "Failure responding to request") + } + + return +} + +// GetTransparentDataEncryptionConfigurationPreparer prepares the GetTransparentDataEncryptionConfiguration request. +func (client DatabasesClient) GetTransparentDataEncryptionConfigurationPreparer(resourceGroupName string, serverName string, databaseName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "databaseName": autorest.Encode("path", databaseName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/databases/{databaseName}/transparentDataEncryption/current", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// GetTransparentDataEncryptionConfigurationSender sends the GetTransparentDataEncryptionConfiguration request. The method will close the +// http.Response Body if it receives an error. +func (client DatabasesClient) GetTransparentDataEncryptionConfigurationSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// GetTransparentDataEncryptionConfigurationResponder handles the response to the GetTransparentDataEncryptionConfiguration request. The method always +// closes the http.Response Body. +func (client DatabasesClient) GetTransparentDataEncryptionConfigurationResponder(resp *http.Response) (result TransparentDataEncryption, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// ListByServer returns information about an Azure SQL database. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +func (client DatabasesClient) ListByServer(resourceGroupName string, serverName string) (result DatabaseListResult, err error) { + req, err := client.ListByServerPreparer(resourceGroupName, serverName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "ListByServer", nil, "Failure preparing request") + } + + resp, err := client.ListByServerSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "ListByServer", resp, "Failure sending request") + } + + result, err = client.ListByServerResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.DatabasesClient", "ListByServer", resp, "Failure responding to request") + } + + return +} + +// ListByServerPreparer prepares the ListByServer request. +func (client DatabasesClient) ListByServerPreparer(resourceGroupName string, serverName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/databases", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// ListByServerSender sends the ListByServer request. The method will close the +// http.Response Body if it receives an error. +func (client DatabasesClient) ListByServerSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// ListByServerResponder handles the response to the ListByServer request. The method always +// closes the http.Response Body. +func (client DatabasesClient) ListByServerResponder(resp *http.Response) (result DatabaseListResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// ListRestorePoints returns a list of Azure SQL database restore points. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. databaseName +// is the name of the Azure SQL database from which to retrieve available +// restore points. +func (client DatabasesClient) ListRestorePoints(resourceGroupName string, serverName string, databaseName string) (result RestorePointListResult, err error) { + req, err := client.ListRestorePointsPreparer(resourceGroupName, serverName, databaseName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "ListRestorePoints", nil, "Failure preparing request") + } + + resp, err := client.ListRestorePointsSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "ListRestorePoints", resp, "Failure sending request") + } + + result, err = client.ListRestorePointsResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.DatabasesClient", "ListRestorePoints", resp, "Failure responding to request") + } + + return +} + +// ListRestorePointsPreparer prepares the ListRestorePoints request. +func (client DatabasesClient) ListRestorePointsPreparer(resourceGroupName string, serverName string, databaseName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "databaseName": autorest.Encode("path", databaseName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/databases/{databaseName}/restorePoints", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// ListRestorePointsSender sends the ListRestorePoints request. The method will close the +// http.Response Body if it receives an error. +func (client DatabasesClient) ListRestorePointsSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// ListRestorePointsResponder handles the response to the ListRestorePoints request. The method always +// closes the http.Response Body. +func (client DatabasesClient) ListRestorePointsResponder(resp *http.Response) (result RestorePointListResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// ListServiceTierAdvisors returns information about service tier advisors for +// specified database. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. databaseName +// is the name of database. +func (client DatabasesClient) ListServiceTierAdvisors(resourceGroupName string, serverName string, databaseName string) (result ServiceTierAdvisorListResult, err error) { + req, err := client.ListServiceTierAdvisorsPreparer(resourceGroupName, serverName, databaseName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "ListServiceTierAdvisors", nil, "Failure preparing request") + } + + resp, err := client.ListServiceTierAdvisorsSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "ListServiceTierAdvisors", resp, "Failure sending request") + } + + result, err = client.ListServiceTierAdvisorsResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.DatabasesClient", "ListServiceTierAdvisors", resp, "Failure responding to request") + } + + return +} + +// ListServiceTierAdvisorsPreparer prepares the ListServiceTierAdvisors request. +func (client DatabasesClient) ListServiceTierAdvisorsPreparer(resourceGroupName string, serverName string, databaseName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "databaseName": autorest.Encode("path", databaseName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/databases/{databaseName}/serviceTierAdvisors", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// ListServiceTierAdvisorsSender sends the ListServiceTierAdvisors request. The method will close the +// http.Response Body if it receives an error. +func (client DatabasesClient) ListServiceTierAdvisorsSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// ListServiceTierAdvisorsResponder handles the response to the ListServiceTierAdvisors request. The method always +// closes the http.Response Body. +func (client DatabasesClient) ListServiceTierAdvisorsResponder(resp *http.Response) (result ServiceTierAdvisorListResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// ListTransparentDataEncryptionActivity returns an Azure SQL Database +// Transparent Data Encryption Activity Response. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. databaseName +// is the name of the Azure SQL database for which the Transparent Data +// Encryption applies. +func (client DatabasesClient) ListTransparentDataEncryptionActivity(resourceGroupName string, serverName string, databaseName string) (result TransparentDataEncryptionActivityListResult, err error) { + req, err := client.ListTransparentDataEncryptionActivityPreparer(resourceGroupName, serverName, databaseName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "ListTransparentDataEncryptionActivity", nil, "Failure preparing request") + } + + resp, err := client.ListTransparentDataEncryptionActivitySender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "ListTransparentDataEncryptionActivity", resp, "Failure sending request") + } + + result, err = client.ListTransparentDataEncryptionActivityResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.DatabasesClient", "ListTransparentDataEncryptionActivity", resp, "Failure responding to request") + } + + return +} + +// ListTransparentDataEncryptionActivityPreparer prepares the ListTransparentDataEncryptionActivity request. +func (client DatabasesClient) ListTransparentDataEncryptionActivityPreparer(resourceGroupName string, serverName string, databaseName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "databaseName": autorest.Encode("path", databaseName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/databases/{databaseName}/transparentDataEncryption/current/operationResults", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// ListTransparentDataEncryptionActivitySender sends the ListTransparentDataEncryptionActivity request. The method will close the +// http.Response Body if it receives an error. +func (client DatabasesClient) ListTransparentDataEncryptionActivitySender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// ListTransparentDataEncryptionActivityResponder handles the response to the ListTransparentDataEncryptionActivity request. The method always +// closes the http.Response Body. +func (client DatabasesClient) ListTransparentDataEncryptionActivityResponder(resp *http.Response) (result TransparentDataEncryptionActivityListResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// ListUsages returns information about Azure SQL database usages. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. databaseName +// is the name of the Azure SQL database. +func (client DatabasesClient) ListUsages(resourceGroupName string, serverName string, databaseName string) (result DatabaseMetricListResult, err error) { + req, err := client.ListUsagesPreparer(resourceGroupName, serverName, databaseName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "ListUsages", nil, "Failure preparing request") + } + + resp, err := client.ListUsagesSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "ListUsages", resp, "Failure sending request") + } + + result, err = client.ListUsagesResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.DatabasesClient", "ListUsages", resp, "Failure responding to request") + } + + return +} + +// ListUsagesPreparer prepares the ListUsages request. +func (client DatabasesClient) ListUsagesPreparer(resourceGroupName string, serverName string, databaseName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "databaseName": autorest.Encode("path", databaseName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/databases/{databaseName}/usages", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// ListUsagesSender sends the ListUsages request. The method will close the +// http.Response Body if it receives an error. +func (client DatabasesClient) ListUsagesSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// ListUsagesResponder handles the response to the ListUsages request. The method always +// closes the http.Response Body. +func (client DatabasesClient) ListUsagesResponder(resp *http.Response) (result DatabaseMetricListResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// PauseDataWarehouse pause an Azure SQL Data Warehouse database. This method +// may poll for completion. Polling can be canceled by passing the cancel +// channel argument. The channel will be used to cancel polling and any +// outstanding HTTP requests. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. databaseName +// is the name of the Azure SQL Data Warehouse database to pause. +func (client DatabasesClient) PauseDataWarehouse(resourceGroupName string, serverName string, databaseName string, cancel <-chan struct{}) (result autorest.Response, err error) { + req, err := client.PauseDataWarehousePreparer(resourceGroupName, serverName, databaseName, cancel) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "PauseDataWarehouse", nil, "Failure preparing request") + } + + resp, err := client.PauseDataWarehouseSender(req) + if err != nil { + result.Response = resp + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "PauseDataWarehouse", resp, "Failure sending request") + } + + result, err = client.PauseDataWarehouseResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.DatabasesClient", "PauseDataWarehouse", resp, "Failure responding to request") + } + + return +} + +// PauseDataWarehousePreparer prepares the PauseDataWarehouse request. +func (client DatabasesClient) PauseDataWarehousePreparer(resourceGroupName string, serverName string, databaseName string, cancel <-chan struct{}) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "databaseName": autorest.Encode("path", databaseName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsPost(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/databases/{databaseName}/pause", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{Cancel: cancel}) +} + +// PauseDataWarehouseSender sends the PauseDataWarehouse request. The method will close the +// http.Response Body if it receives an error. +func (client DatabasesClient) PauseDataWarehouseSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, + req, + azure.DoPollForAsynchronous(client.PollingDelay)) +} + +// PauseDataWarehouseResponder handles the response to the PauseDataWarehouse request. The method always +// closes the http.Response Body. +func (client DatabasesClient) PauseDataWarehouseResponder(resp *http.Response) (result autorest.Response, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusAccepted), + autorest.ByClosing()) + result.Response = resp + return +} + +// ResumeDataWarehouse resume an Azure SQL Data Warehouse database. This +// method may poll for completion. Polling can be canceled by passing the +// cancel channel argument. The channel will be used to cancel polling and +// any outstanding HTTP requests. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. databaseName +// is the name of the Azure SQL Data Warehouse database to resume. +func (client DatabasesClient) ResumeDataWarehouse(resourceGroupName string, serverName string, databaseName string, cancel <-chan struct{}) (result autorest.Response, err error) { + req, err := client.ResumeDataWarehousePreparer(resourceGroupName, serverName, databaseName, cancel) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "ResumeDataWarehouse", nil, "Failure preparing request") + } + + resp, err := client.ResumeDataWarehouseSender(req) + if err != nil { + result.Response = resp + return result, autorest.NewErrorWithError(err, "sql.DatabasesClient", "ResumeDataWarehouse", resp, "Failure sending request") + } + + result, err = client.ResumeDataWarehouseResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.DatabasesClient", "ResumeDataWarehouse", resp, "Failure responding to request") + } + + return +} + +// ResumeDataWarehousePreparer prepares the ResumeDataWarehouse request. +func (client DatabasesClient) ResumeDataWarehousePreparer(resourceGroupName string, serverName string, databaseName string, cancel <-chan struct{}) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "databaseName": autorest.Encode("path", databaseName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsPost(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/databases/{databaseName}/resume", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{Cancel: cancel}) +} + +// ResumeDataWarehouseSender sends the ResumeDataWarehouse request. The method will close the +// http.Response Body if it receives an error. +func (client DatabasesClient) ResumeDataWarehouseSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, + req, + azure.DoPollForAsynchronous(client.PollingDelay)) +} + +// ResumeDataWarehouseResponder handles the response to the ResumeDataWarehouse request. The method always +// closes the http.Response Body. +func (client DatabasesClient) ResumeDataWarehouseResponder(resp *http.Response) (result autorest.Response, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusAccepted, http.StatusOK), + autorest.ByClosing()) + result.Response = resp + return +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/elasticpools.go b/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/elasticpools.go new file mode 100644 index 000000000000..e68d6674ae0c --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/elasticpools.go @@ -0,0 +1,582 @@ +package sql + +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Code generated by Microsoft (R) AutoRest Code Generator 0.17.0.0 +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +import ( + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "net/http" +) + +// ElasticPoolsClient is the provides create, read, update and delete +// functionality for Azure SQL resources including servers, databases, +// elastic pools, recommendations, operations, and usage metrics. +type ElasticPoolsClient struct { + ManagementClient +} + +// NewElasticPoolsClient creates an instance of the ElasticPoolsClient client. +func NewElasticPoolsClient(subscriptionID string) ElasticPoolsClient { + return NewElasticPoolsClientWithBaseURI(DefaultBaseURI, subscriptionID) +} + +// NewElasticPoolsClientWithBaseURI creates an instance of the +// ElasticPoolsClient client. +func NewElasticPoolsClientWithBaseURI(baseURI string, subscriptionID string) ElasticPoolsClient { + return ElasticPoolsClient{NewWithBaseURI(baseURI, subscriptionID)} +} + +// CreateOrUpdate creates a new Azure SQL elastic pool or updates an existing +// Azure SQL elastic pool. This method may poll for completion. Polling can +// be canceled by passing the cancel channel argument. The channel will be +// used to cancel polling and any outstanding HTTP requests. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +// elasticPoolName is the name of the Azure SQL Elastic Pool to be operated +// on (Updated or created). parameters is the required parameters for +// creating or updating an Elastic Pool. +func (client ElasticPoolsClient) CreateOrUpdate(resourceGroupName string, serverName string, elasticPoolName string, parameters ElasticPool, cancel <-chan struct{}) (result autorest.Response, err error) { + req, err := client.CreateOrUpdatePreparer(resourceGroupName, serverName, elasticPoolName, parameters, cancel) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "CreateOrUpdate", nil, "Failure preparing request") + } + + resp, err := client.CreateOrUpdateSender(req) + if err != nil { + result.Response = resp + return result, autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "CreateOrUpdate", resp, "Failure sending request") + } + + result, err = client.CreateOrUpdateResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "CreateOrUpdate", resp, "Failure responding to request") + } + + return +} + +// CreateOrUpdatePreparer prepares the CreateOrUpdate request. +func (client ElasticPoolsClient) CreateOrUpdatePreparer(resourceGroupName string, serverName string, elasticPoolName string, parameters ElasticPool, cancel <-chan struct{}) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "elasticPoolName": autorest.Encode("path", elasticPoolName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsJSON(), + autorest.AsPut(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/elasticPools/{elasticPoolName}", pathParameters), + autorest.WithJSON(parameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{Cancel: cancel}) +} + +// CreateOrUpdateSender sends the CreateOrUpdate request. The method will close the +// http.Response Body if it receives an error. +func (client ElasticPoolsClient) CreateOrUpdateSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, + req, + azure.DoPollForAsynchronous(client.PollingDelay)) +} + +// CreateOrUpdateResponder handles the response to the CreateOrUpdate request. The method always +// closes the http.Response Body. +func (client ElasticPoolsClient) CreateOrUpdateResponder(resp *http.Response) (result autorest.Response, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated, http.StatusAccepted), + autorest.ByClosing()) + result.Response = resp + return +} + +// Delete deletes the Azure SQL elastic pool. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +// elasticPoolName is the name of the Azure SQL Elastic Pool to be deleted. +func (client ElasticPoolsClient) Delete(resourceGroupName string, serverName string, elasticPoolName string) (result autorest.Response, err error) { + req, err := client.DeletePreparer(resourceGroupName, serverName, elasticPoolName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "Delete", nil, "Failure preparing request") + } + + resp, err := client.DeleteSender(req) + if err != nil { + result.Response = resp + return result, autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "Delete", resp, "Failure sending request") + } + + result, err = client.DeleteResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "Delete", resp, "Failure responding to request") + } + + return +} + +// DeletePreparer prepares the Delete request. +func (client ElasticPoolsClient) DeletePreparer(resourceGroupName string, serverName string, elasticPoolName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "elasticPoolName": autorest.Encode("path", elasticPoolName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsDelete(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/elasticPools/{elasticPoolName}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// DeleteSender sends the Delete request. The method will close the +// http.Response Body if it receives an error. +func (client ElasticPoolsClient) DeleteSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// DeleteResponder handles the response to the Delete request. The method always +// closes the http.Response Body. +func (client ElasticPoolsClient) DeleteResponder(resp *http.Response) (result autorest.Response, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusNoContent), + autorest.ByClosing()) + result.Response = resp + return +} + +// Get gets information about an Azure SQL elastic pool. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +// elasticPoolName is the name of the Azure SQL Elastic Pool to be retrieved. +func (client ElasticPoolsClient) Get(resourceGroupName string, serverName string, elasticPoolName string) (result ElasticPool, err error) { + req, err := client.GetPreparer(resourceGroupName, serverName, elasticPoolName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "Get", nil, "Failure preparing request") + } + + resp, err := client.GetSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "Get", resp, "Failure sending request") + } + + result, err = client.GetResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "Get", resp, "Failure responding to request") + } + + return +} + +// GetPreparer prepares the Get request. +func (client ElasticPoolsClient) GetPreparer(resourceGroupName string, serverName string, elasticPoolName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "elasticPoolName": autorest.Encode("path", elasticPoolName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/elasticPools/{elasticPoolName}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// GetSender sends the Get request. The method will close the +// http.Response Body if it receives an error. +func (client ElasticPoolsClient) GetSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// GetResponder handles the response to the Get request. The method always +// closes the http.Response Body. +func (client ElasticPoolsClient) GetResponder(resp *http.Response) (result ElasticPool, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// GetDatabase gets information about an Azure SQL database inside of an Azure +// SQL elastic pool. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +// elasticPoolName is the name of the Azure SQL Elastic Pool to be retrieved. +// databaseName is the name of the Azure SQL database to be retrieved. +func (client ElasticPoolsClient) GetDatabase(resourceGroupName string, serverName string, elasticPoolName string, databaseName string) (result Database, err error) { + req, err := client.GetDatabasePreparer(resourceGroupName, serverName, elasticPoolName, databaseName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "GetDatabase", nil, "Failure preparing request") + } + + resp, err := client.GetDatabaseSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "GetDatabase", resp, "Failure sending request") + } + + result, err = client.GetDatabaseResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "GetDatabase", resp, "Failure responding to request") + } + + return +} + +// GetDatabasePreparer prepares the GetDatabase request. +func (client ElasticPoolsClient) GetDatabasePreparer(resourceGroupName string, serverName string, elasticPoolName string, databaseName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "databaseName": autorest.Encode("path", databaseName), + "elasticPoolName": autorest.Encode("path", elasticPoolName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/elasticPools/{elasticPoolName}/databases/{databaseName}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// GetDatabaseSender sends the GetDatabase request. The method will close the +// http.Response Body if it receives an error. +func (client ElasticPoolsClient) GetDatabaseSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// GetDatabaseResponder handles the response to the GetDatabase request. The method always +// closes the http.Response Body. +func (client ElasticPoolsClient) GetDatabaseResponder(resp *http.Response) (result Database, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// ListActivity returns information about Azure SQL elastic pool activities. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +// elasticPoolName is the name of the Azure SQL Elastic Pool for which to get +// the current activity. +func (client ElasticPoolsClient) ListActivity(resourceGroupName string, serverName string, elasticPoolName string) (result ElasticPoolActivityListResult, err error) { + req, err := client.ListActivityPreparer(resourceGroupName, serverName, elasticPoolName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "ListActivity", nil, "Failure preparing request") + } + + resp, err := client.ListActivitySender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "ListActivity", resp, "Failure sending request") + } + + result, err = client.ListActivityResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "ListActivity", resp, "Failure responding to request") + } + + return +} + +// ListActivityPreparer prepares the ListActivity request. +func (client ElasticPoolsClient) ListActivityPreparer(resourceGroupName string, serverName string, elasticPoolName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "elasticPoolName": autorest.Encode("path", elasticPoolName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/elasticPools/{elasticPoolName}/elasticPoolActivity", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// ListActivitySender sends the ListActivity request. The method will close the +// http.Response Body if it receives an error. +func (client ElasticPoolsClient) ListActivitySender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// ListActivityResponder handles the response to the ListActivity request. The method always +// closes the http.Response Body. +func (client ElasticPoolsClient) ListActivityResponder(resp *http.Response) (result ElasticPoolActivityListResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// ListByServer returns information about Azure SQL elastic pools. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +func (client ElasticPoolsClient) ListByServer(resourceGroupName string, serverName string) (result ElasticPoolListResult, err error) { + req, err := client.ListByServerPreparer(resourceGroupName, serverName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "ListByServer", nil, "Failure preparing request") + } + + resp, err := client.ListByServerSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "ListByServer", resp, "Failure sending request") + } + + result, err = client.ListByServerResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "ListByServer", resp, "Failure responding to request") + } + + return +} + +// ListByServerPreparer prepares the ListByServer request. +func (client ElasticPoolsClient) ListByServerPreparer(resourceGroupName string, serverName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/elasticPools", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// ListByServerSender sends the ListByServer request. The method will close the +// http.Response Body if it receives an error. +func (client ElasticPoolsClient) ListByServerSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// ListByServerResponder handles the response to the ListByServer request. The method always +// closes the http.Response Body. +func (client ElasticPoolsClient) ListByServerResponder(resp *http.Response) (result ElasticPoolListResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// ListDatabaseActivity returns information about activity on Azure SQL +// databases inside of an Azure SQL elastic pool. +// +// elasticPoolName is the name of the Azure SQL Elastic Pool. +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +func (client ElasticPoolsClient) ListDatabaseActivity(elasticPoolName string, resourceGroupName string, serverName string) (result ElasticPoolDatabaseActivityListResult, err error) { + req, err := client.ListDatabaseActivityPreparer(elasticPoolName, resourceGroupName, serverName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "ListDatabaseActivity", nil, "Failure preparing request") + } + + resp, err := client.ListDatabaseActivitySender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "ListDatabaseActivity", resp, "Failure sending request") + } + + result, err = client.ListDatabaseActivityResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "ListDatabaseActivity", resp, "Failure responding to request") + } + + return +} + +// ListDatabaseActivityPreparer prepares the ListDatabaseActivity request. +func (client ElasticPoolsClient) ListDatabaseActivityPreparer(elasticPoolName string, resourceGroupName string, serverName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "elasticPoolName": autorest.Encode("path", elasticPoolName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/elasticPools/{elasticPoolName}/elasticPoolDatabaseActivity", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// ListDatabaseActivitySender sends the ListDatabaseActivity request. The method will close the +// http.Response Body if it receives an error. +func (client ElasticPoolsClient) ListDatabaseActivitySender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// ListDatabaseActivityResponder handles the response to the ListDatabaseActivity request. The method always +// closes the http.Response Body. +func (client ElasticPoolsClient) ListDatabaseActivityResponder(resp *http.Response) (result ElasticPoolDatabaseActivityListResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// ListDatabases returns information about an Azure SQL database inside of an +// Azure SQL elastic pool. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +// elasticPoolName is the name of the Azure SQL Elastic Pool to be retrieved. +func (client ElasticPoolsClient) ListDatabases(resourceGroupName string, serverName string, elasticPoolName string) (result DatabaseListResult, err error) { + req, err := client.ListDatabasesPreparer(resourceGroupName, serverName, elasticPoolName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "ListDatabases", nil, "Failure preparing request") + } + + resp, err := client.ListDatabasesSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "ListDatabases", resp, "Failure sending request") + } + + result, err = client.ListDatabasesResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.ElasticPoolsClient", "ListDatabases", resp, "Failure responding to request") + } + + return +} + +// ListDatabasesPreparer prepares the ListDatabases request. +func (client ElasticPoolsClient) ListDatabasesPreparer(resourceGroupName string, serverName string, elasticPoolName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "elasticPoolName": autorest.Encode("path", elasticPoolName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/elasticPools/{elasticPoolName}/databases", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// ListDatabasesSender sends the ListDatabases request. The method will close the +// http.Response Body if it receives an error. +func (client ElasticPoolsClient) ListDatabasesSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// ListDatabasesResponder handles the response to the ListDatabases request. The method always +// closes the http.Response Body. +func (client ElasticPoolsClient) ListDatabasesResponder(resp *http.Response) (result DatabaseListResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/models.go b/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/models.go new file mode 100644 index 000000000000..497d7defb9c3 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/models.go @@ -0,0 +1,810 @@ +package sql + +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Code generated by Microsoft (R) AutoRest Code Generator 0.17.0.0 +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +import ( + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/date" + "github.com/satori/uuid" +) + +// CreateMode enumerates the values for create mode. +type CreateMode string + +const ( + // Copy specifies the copy state for create mode. + Copy CreateMode = "Copy" + // Default specifies the default state for create mode. + Default CreateMode = "Default" + // NonReadableSecondary specifies the non readable secondary state for + // create mode. + NonReadableSecondary CreateMode = "NonReadableSecondary" + // OnlineSecondary specifies the online secondary state for create mode. + OnlineSecondary CreateMode = "OnlineSecondary" + // PointInTimeRestore specifies the point in time restore state for create + // mode. + PointInTimeRestore CreateMode = "PointInTimeRestore" + // Recovery specifies the recovery state for create mode. + Recovery CreateMode = "Recovery" + // Restore specifies the restore state for create mode. + Restore CreateMode = "Restore" +) + +// DatabaseEditions enumerates the values for database editions. +type DatabaseEditions string + +const ( + // Basic specifies the basic state for database editions. + Basic DatabaseEditions = "Basic" + // Business specifies the business state for database editions. + Business DatabaseEditions = "Business" + // DataWarehouse specifies the data warehouse state for database editions. + DataWarehouse DatabaseEditions = "DataWarehouse" + // Free specifies the free state for database editions. + Free DatabaseEditions = "Free" + // Premium specifies the premium state for database editions. + Premium DatabaseEditions = "Premium" + // Standard specifies the standard state for database editions. + Standard DatabaseEditions = "Standard" + // Stretch specifies the stretch state for database editions. + Stretch DatabaseEditions = "Stretch" + // Web specifies the web state for database editions. + Web DatabaseEditions = "Web" +) + +// ElasticPoolEditions enumerates the values for elastic pool editions. +type ElasticPoolEditions string + +const ( + // ElasticPoolEditionsBasic specifies the elastic pool editions basic + // state for elastic pool editions. + ElasticPoolEditionsBasic ElasticPoolEditions = "Basic" + // ElasticPoolEditionsPremium specifies the elastic pool editions premium + // state for elastic pool editions. + ElasticPoolEditionsPremium ElasticPoolEditions = "Premium" + // ElasticPoolEditionsStandard specifies the elastic pool editions + // standard state for elastic pool editions. + ElasticPoolEditionsStandard ElasticPoolEditions = "Standard" +) + +// ElasticPoolState enumerates the values for elastic pool state. +type ElasticPoolState string + +const ( + // Creating specifies the creating state for elastic pool state. + Creating ElasticPoolState = "Creating" + // Disabled specifies the disabled state for elastic pool state. + Disabled ElasticPoolState = "Disabled" + // Ready specifies the ready state for elastic pool state. + Ready ElasticPoolState = "Ready" +) + +// RecommendedIndexActions enumerates the values for recommended index actions. +type RecommendedIndexActions string + +const ( + // Create specifies the create state for recommended index actions. + Create RecommendedIndexActions = "Create" + // Drop specifies the drop state for recommended index actions. + Drop RecommendedIndexActions = "Drop" + // Rebuild specifies the rebuild state for recommended index actions. + Rebuild RecommendedIndexActions = "Rebuild" +) + +// RecommendedIndexStates enumerates the values for recommended index states. +type RecommendedIndexStates string + +const ( + // Active specifies the active state for recommended index states. + Active RecommendedIndexStates = "Active" + // Blocked specifies the blocked state for recommended index states. + Blocked RecommendedIndexStates = "Blocked" + // Executing specifies the executing state for recommended index states. + Executing RecommendedIndexStates = "Executing" + // Expired specifies the expired state for recommended index states. + Expired RecommendedIndexStates = "Expired" + // Ignored specifies the ignored state for recommended index states. + Ignored RecommendedIndexStates = "Ignored" + // Pending specifies the pending state for recommended index states. + Pending RecommendedIndexStates = "Pending" + // PendingRevert specifies the pending revert state for recommended index + // states. + PendingRevert RecommendedIndexStates = "Pending Revert" + // Reverted specifies the reverted state for recommended index states. + Reverted RecommendedIndexStates = "Reverted" + // Reverting specifies the reverting state for recommended index states. + Reverting RecommendedIndexStates = "Reverting" + // Success specifies the success state for recommended index states. + Success RecommendedIndexStates = "Success" + // Verifying specifies the verifying state for recommended index states. + Verifying RecommendedIndexStates = "Verifying" +) + +// RecommendedIndexTypes enumerates the values for recommended index types. +type RecommendedIndexTypes string + +const ( + // CLUSTERED specifies the clustered state for recommended index types. + CLUSTERED RecommendedIndexTypes = "CLUSTERED" + // CLUSTEREDCOLUMNSTORE specifies the clusteredcolumnstore state for + // recommended index types. + CLUSTEREDCOLUMNSTORE RecommendedIndexTypes = "CLUSTERED COLUMNSTORE" + // COLUMNSTORE specifies the columnstore state for recommended index types. + COLUMNSTORE RecommendedIndexTypes = "COLUMNSTORE" + // NONCLUSTERED specifies the nonclustered state for recommended index + // types. + NONCLUSTERED RecommendedIndexTypes = "NONCLUSTERED" +) + +// RestorePointTypes enumerates the values for restore point types. +type RestorePointTypes string + +const ( + // CONTINUOUS specifies the continuous state for restore point types. + CONTINUOUS RestorePointTypes = "CONTINUOUS" + // DISCRETE specifies the discrete state for restore point types. + DISCRETE RestorePointTypes = "DISCRETE" +) + +// ServerVersion enumerates the values for server version. +type ServerVersion string + +const ( + // OneTwoFullStopZero specifies the one two full stop zero state for + // server version. + OneTwoFullStopZero ServerVersion = "12.0" + // TwoFullStopZero specifies the two full stop zero state for server + // version. + TwoFullStopZero ServerVersion = "2.0" +) + +// ServiceObjectiveName enumerates the values for service objective name. +type ServiceObjectiveName string + +const ( + // ServiceObjectiveNameBasic specifies the service objective name basic + // state for service objective name. + ServiceObjectiveNameBasic ServiceObjectiveName = "Basic" + // ServiceObjectiveNameP1 specifies the service objective name p1 state + // for service objective name. + ServiceObjectiveNameP1 ServiceObjectiveName = "P1" + // ServiceObjectiveNameP2 specifies the service objective name p2 state + // for service objective name. + ServiceObjectiveNameP2 ServiceObjectiveName = "P2" + // ServiceObjectiveNameP3 specifies the service objective name p3 state + // for service objective name. + ServiceObjectiveNameP3 ServiceObjectiveName = "P3" + // ServiceObjectiveNameS0 specifies the service objective name s0 state + // for service objective name. + ServiceObjectiveNameS0 ServiceObjectiveName = "S0" + // ServiceObjectiveNameS1 specifies the service objective name s1 state + // for service objective name. + ServiceObjectiveNameS1 ServiceObjectiveName = "S1" + // ServiceObjectiveNameS2 specifies the service objective name s2 state + // for service objective name. + ServiceObjectiveNameS2 ServiceObjectiveName = "S2" + // ServiceObjectiveNameS3 specifies the service objective name s3 state + // for service objective name. + ServiceObjectiveNameS3 ServiceObjectiveName = "S3" +) + +// TableType enumerates the values for table type. +type TableType string + +const ( + // BaseTable specifies the base table state for table type. + BaseTable TableType = "BaseTable" + // View specifies the view state for table type. + View TableType = "View" +) + +// TargetDatabaseEditions enumerates the values for target database editions. +type TargetDatabaseEditions string + +const ( + // TargetDatabaseEditionsBasic specifies the target database editions + // basic state for target database editions. + TargetDatabaseEditionsBasic TargetDatabaseEditions = "Basic" + // TargetDatabaseEditionsDataWarehouse specifies the target database + // editions data warehouse state for target database editions. + TargetDatabaseEditionsDataWarehouse TargetDatabaseEditions = "DataWarehouse" + // TargetDatabaseEditionsFree specifies the target database editions free + // state for target database editions. + TargetDatabaseEditionsFree TargetDatabaseEditions = "Free" + // TargetDatabaseEditionsPremium specifies the target database editions + // premium state for target database editions. + TargetDatabaseEditionsPremium TargetDatabaseEditions = "Premium" + // TargetDatabaseEditionsStandard specifies the target database editions + // standard state for target database editions. + TargetDatabaseEditionsStandard TargetDatabaseEditions = "Standard" + // TargetDatabaseEditionsStretch specifies the target database editions + // stretch state for target database editions. + TargetDatabaseEditionsStretch TargetDatabaseEditions = "Stretch" +) + +// TargetElasticPoolEditions enumerates the values for target elastic pool +// editions. +type TargetElasticPoolEditions string + +const ( + // TargetElasticPoolEditionsBasic specifies the target elastic pool + // editions basic state for target elastic pool editions. + TargetElasticPoolEditionsBasic TargetElasticPoolEditions = "Basic" + // TargetElasticPoolEditionsPremium specifies the target elastic pool + // editions premium state for target elastic pool editions. + TargetElasticPoolEditionsPremium TargetElasticPoolEditions = "Premium" + // TargetElasticPoolEditionsStandard specifies the target elastic pool + // editions standard state for target elastic pool editions. + TargetElasticPoolEditionsStandard TargetElasticPoolEditions = "Standard" +) + +// TransparentDataEncryptionActivityStates enumerates the values for +// transparent data encryption activity states. +type TransparentDataEncryptionActivityStates string + +const ( + // Decrypting specifies the decrypting state for transparent data + // encryption activity states. + Decrypting TransparentDataEncryptionActivityStates = "Decrypting" + // Encrypting specifies the encrypting state for transparent data + // encryption activity states. + Encrypting TransparentDataEncryptionActivityStates = "Encrypting" +) + +// TransparentDataEncryptionStates enumerates the values for transparent data +// encryption states. +type TransparentDataEncryptionStates string + +const ( + // TransparentDataEncryptionStatesDisabled specifies the transparent data + // encryption states disabled state for transparent data encryption + // states. + TransparentDataEncryptionStatesDisabled TransparentDataEncryptionStates = "Disabled" + // TransparentDataEncryptionStatesEnabled specifies the transparent data + // encryption states enabled state for transparent data encryption states. + TransparentDataEncryptionStatesEnabled TransparentDataEncryptionStates = "Enabled" +) + +// Column is represents an Azure SQL Database table column. +type Column struct { + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` + *ColumnProperties `json:"properties,omitempty"` +} + +// ColumnProperties is represents the properties of an Azure SQL Database +// table column. +type ColumnProperties struct { + ColumnType *string `json:"columnType,omitempty"` +} + +// Database is represents an Azure SQL Database. +type Database struct { + autorest.Response `json:"-"` + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` + *DatabaseProperties `json:"properties,omitempty"` +} + +// DatabaseListResult is represents the response to a List Azure SQL Database +// request. +type DatabaseListResult struct { + autorest.Response `json:"-"` + Value *[]Database `json:"value,omitempty"` +} + +// DatabaseMetric is represents Azure SQL Database metrics. +type DatabaseMetric struct { + ResourceName *string `json:"resourceName,omitempty"` + DisplayName *string `json:"displayName,omitempty"` + CurrentValue *float64 `json:"currentValue,omitempty"` + Limit *float64 `json:"limit,omitempty"` + Unit *string `json:"unit,omitempty"` + NextResetTime *date.Time `json:"nextResetTime,omitempty"` +} + +// DatabaseMetricListResult is represents the response to a List Azure SQL +// Database metrics request. +type DatabaseMetricListResult struct { + autorest.Response `json:"-"` + Value *[]DatabaseMetric `json:"value,omitempty"` +} + +// DatabaseProperties is represents the properties of an Azure SQL Database. +type DatabaseProperties struct { + Collation *string `json:"collation,omitempty"` + CreationDate *date.Time `json:"creationDate,omitempty"` + ContainmentState *int64 `json:"containmentState,omitempty"` + CurrentServiceObjectiveID *uuid.UUID `json:"currentServiceObjectiveId,omitempty"` + DatabaseID *string `json:"databaseId,omitempty"` + EarliestRestoreDate *date.Time `json:"earliestRestoreDate,omitempty"` + CreateMode CreateMode `json:"createMode,omitempty"` + SourceDatabaseID *string `json:"sourceDatabaseId,omitempty"` + Edition DatabaseEditions `json:"edition,omitempty"` + MaxSizeBytes *string `json:"maxSizeBytes,omitempty"` + RequestedServiceObjectiveID *uuid.UUID `json:"requestedServiceObjectiveId,omitempty"` + RequestedServiceObjectiveName ServiceObjectiveName `json:"requestedServiceObjectiveName,omitempty"` + ServiceLevelObjective ServiceObjectiveName `json:"serviceLevelObjective,omitempty"` + Status *string `json:"status,omitempty"` + ElasticPoolName *string `json:"elasticPoolName,omitempty"` + DefaultSecondaryLocation *string `json:"defaultSecondaryLocation,omitempty"` + ServiceTierAdvisors *[]ServiceTierAdvisor `json:"serviceTierAdvisors,omitempty"` + UpgradeHint *UpgradeHint `json:"upgradeHint,omitempty"` + Schemas *[]Schema `json:"schemas,omitempty"` + TransparentDataEncryption *[]TransparentDataEncryption `json:"transparentDataEncryption,omitempty"` + RecommendedIndex *[]RecommendedIndex `json:"recommendedIndex,omitempty"` +} + +// ElasticPool is represents an Azure SQL Database elastic pool. +type ElasticPool struct { + autorest.Response `json:"-"` + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` + *ElasticPoolProperties `json:"properties,omitempty"` +} + +// ElasticPoolActivity is represents the activity on an Azure SQL Elastic Pool. +type ElasticPoolActivity struct { + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` + *ElasticPoolActivityProperties `json:"properties,omitempty"` +} + +// ElasticPoolActivityListResult is represents the response to a List Azure +// SQL Elastic Pool Activity request. +type ElasticPoolActivityListResult struct { + autorest.Response `json:"-"` + Value *[]ElasticPoolActivity `json:"value,omitempty"` +} + +// ElasticPoolActivityProperties is represents the properties of an Azure SQL +// Elastic Pool. +type ElasticPoolActivityProperties struct { + EndTime *date.Time `json:"endTime,omitempty"` + ErrorCode *int32 `json:"errorCode,omitempty"` + ErrorMessage *string `json:"errorMessage,omitempty"` + ErrorSeverity *int32 `json:"errorSeverity,omitempty"` + Operation *string `json:"operation,omitempty"` + OperationID *string `json:"operationId,omitempty"` + PercentComplete *int32 `json:"percentComplete,omitempty"` + RequestedDatabaseDtuMax *int32 `json:"requestedDatabaseDtuMax,omitempty"` + RequestedDatabaseDtuMin *int32 `json:"requestedDatabaseDtuMin,omitempty"` + RequestedDtu *int32 `json:"requestedDtu,omitempty"` + RequestedElasticPoolName *string `json:"requestedElasticPoolName,omitempty"` + RequestedStorageLimitInGB *int64 `json:"requestedStorageLimitInGB,omitempty"` + ElasticPoolName *string `json:"elasticPoolName,omitempty"` + ServerName *string `json:"serverName,omitempty"` + StartTime *date.Time `json:"startTime,omitempty"` + State *string `json:"state,omitempty"` +} + +// ElasticPoolDatabaseActivity is represents the activity on an Azure SQL +// Elastic Pool. +type ElasticPoolDatabaseActivity struct { + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` + *ElasticPoolDatabaseActivityProperties `json:"properties,omitempty"` +} + +// ElasticPoolDatabaseActivityListResult is represents the response to a List +// Azure SQL Elastic Pool Database Activity request. +type ElasticPoolDatabaseActivityListResult struct { + autorest.Response `json:"-"` + Value *[]ElasticPoolDatabaseActivity `json:"value,omitempty"` +} + +// ElasticPoolDatabaseActivityProperties is represents the properties of an +// Azure SQL Elastic Pool Database Activity. +type ElasticPoolDatabaseActivityProperties struct { + DatabaseName *string `json:"databaseName,omitempty"` + EndTime *date.Time `json:"endTime,omitempty"` + ErrorCode *int32 `json:"errorCode,omitempty"` + ErrorMessage *string `json:"errorMessage,omitempty"` + ErrorSeverity *int32 `json:"errorSeverity,omitempty"` + Operation *string `json:"operation,omitempty"` + OperationID *string `json:"operationId,omitempty"` + PercentComplete *int32 `json:"percentComplete,omitempty"` + RequestedElasticPoolName *string `json:"requestedElasticPoolName,omitempty"` + CurrentElasticPoolName *string `json:"currentElasticPoolName,omitempty"` + CurrentServiceObjective *string `json:"currentServiceObjective,omitempty"` + RequestedServiceObjective *string `json:"requestedServiceObjective,omitempty"` + ServerName *string `json:"serverName,omitempty"` + StartTime *date.Time `json:"startTime,omitempty"` + State *string `json:"state,omitempty"` +} + +// ElasticPoolListResult is represents the response to a List Azure SQL +// Elastic Pool request. +type ElasticPoolListResult struct { + autorest.Response `json:"-"` + Value *[]ElasticPool `json:"value,omitempty"` +} + +// ElasticPoolProperties is represents the properties of an Azure SQL Elastic +// Pool. +type ElasticPoolProperties struct { + CreationDate *date.Time `json:"creationDate,omitempty"` + State ElasticPoolState `json:"state,omitempty"` + Edition ElasticPoolEditions `json:"edition,omitempty"` + Dtu *int32 `json:"dtu,omitempty"` + DatabaseDtuMax *int32 `json:"databaseDtuMax,omitempty"` + DatabaseDtuMin *int32 `json:"databaseDtuMin,omitempty"` + StorageMB *int32 `json:"storageMB,omitempty"` +} + +// OperationImpact is represents impact of an operation, both in absolute and +// relative terms. +type OperationImpact struct { + Name *string `json:"name,omitempty"` + Unit *string `json:"unit,omitempty"` + ChangeValueAbsolute *float64 `json:"changeValueAbsolute,omitempty"` + ChangeValueRelative *float64 `json:"changeValueRelative,omitempty"` +} + +// RecommendedDatabaseProperties is represents the properties of a recommended +// Azure SQL Database being upgraded. +type RecommendedDatabaseProperties struct { + Name *string `json:"Name,omitempty"` + TargetEdition TargetDatabaseEditions `json:"TargetEdition,omitempty"` + TargetServiceLevelObjective *string `json:"TargetServiceLevelObjective,omitempty"` +} + +// RecommendedElasticPool is represents an Azure SQL Recommended Elastic Pool. +type RecommendedElasticPool struct { + autorest.Response `json:"-"` + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` + *RecommendedElasticPoolProperties `json:"properties,omitempty"` +} + +// RecommendedElasticPoolListMetricsResult is represents the response to a +// List Azure SQL Recommended Elastic Pool metrics request. +type RecommendedElasticPoolListMetricsResult struct { + autorest.Response `json:"-"` + Value *[]RecommendedElasticPoolMetric `json:"value,omitempty"` +} + +// RecommendedElasticPoolListResult is represents the response to a List Azure +// SQL Recommended Elastic Pool request. +type RecommendedElasticPoolListResult struct { + autorest.Response `json:"-"` + Value *[]RecommendedElasticPool `json:"value,omitempty"` +} + +// RecommendedElasticPoolMetric is represents Azure SQL recommended elastic +// pool metric. +type RecommendedElasticPoolMetric struct { + DateTime *date.Time `json:"dateTime,omitempty"` + Dtu *float64 `json:"dtu,omitempty"` + SizeGB *float64 `json:"sizeGB,omitempty"` +} + +// RecommendedElasticPoolProperties is represents the properties of an Azure +// SQL Recommended Elastic Pool. +type RecommendedElasticPoolProperties struct { + DatabaseEdition ElasticPoolEditions `json:"databaseEdition,omitempty"` + Dtu *float64 `json:"dtu,omitempty"` + DatabaseDtuMin *float64 `json:"databaseDtuMin,omitempty"` + DatabaseDtuMax *float64 `json:"databaseDtuMax,omitempty"` + StorageMB *float64 `json:"storageMB,omitempty"` + ObservationPeriodStart *date.Time `json:"observationPeriodStart,omitempty"` + ObservationPeriodEnd *date.Time `json:"observationPeriodEnd,omitempty"` + MaxObservedDtu *float64 `json:"maxObservedDtu,omitempty"` + MaxObservedStorageMB *float64 `json:"maxObservedStorageMB,omitempty"` + Databases *[]Database `json:"databases,omitempty"` + Metrics *[]RecommendedElasticPoolMetric `json:"metrics,omitempty"` +} + +// RecommendedIndex is represents an Azure SQL Database recommended index. +type RecommendedIndex struct { + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` + *RecommendedIndexProperties `json:"properties,omitempty"` +} + +// RecommendedIndexProperties is represents the properties of an Azure SQL +// Database recommended index. +type RecommendedIndexProperties struct { + Action RecommendedIndexActions `json:"action,omitempty"` + State RecommendedIndexStates `json:"state,omitempty"` + Created *date.Time `json:"created,omitempty"` + LastModified *date.Time `json:"lastModified,omitempty"` + IndexType RecommendedIndexTypes `json:"indexType,omitempty"` + Schema *string `json:"schema,omitempty"` + Table *string `json:"table,omitempty"` + Columns *[]string `json:"columns,omitempty"` + IncludedColumns *[]string `json:"includedColumns,omitempty"` + IndexScript *string `json:"indexScript,omitempty"` + EstimatedImpact *[]OperationImpact `json:"estimatedImpact,omitempty"` + ReportedImpact *[]OperationImpact `json:"reportedImpact,omitempty"` +} + +// Resource is resource properties +type Resource struct { + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` +} + +// RestorePoint is represents an Azure SQL Database restore point. +type RestorePoint struct { + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` + *RestorePointProperties `json:"properties,omitempty"` +} + +// RestorePointListResult is represents the response to a List Azure SQL +// Database restore points request. +type RestorePointListResult struct { + autorest.Response `json:"-"` + Value *[]RestorePoint `json:"value,omitempty"` +} + +// RestorePointProperties is represents the properties of an Azure SQL +// Database restore point. +type RestorePointProperties struct { + RestorePointType RestorePointTypes `json:"restorePointType,omitempty"` + RestorePointCreationDate *date.Time `json:"restorePointCreationDate,omitempty"` + EarliestRestoreDate *date.Time `json:"earliestRestoreDate,omitempty"` +} + +// Schema is represents an Azure SQL Database schema. +type Schema struct { + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` + *SchemaProperties `json:"properties,omitempty"` +} + +// SchemaProperties is represents the properties of an Azure SQL Database +// schema. +type SchemaProperties struct { + Tables *[]Table `json:"tables,omitempty"` +} + +// Server is represents an Azure SQL server. +type Server struct { + autorest.Response `json:"-"` + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` + *ServerProperties `json:"properties,omitempty"` +} + +// ServerListResult is represents the response to a Get Azure SQL server +// request. +type ServerListResult struct { + autorest.Response `json:"-"` + Value *[]Server `json:"value,omitempty"` +} + +// ServerMetric is represents Azure SQL server metrics. +type ServerMetric struct { + ResourceName *string `json:"resourceName,omitempty"` + DisplayName *string `json:"displayName,omitempty"` + CurrentValue *float64 `json:"currentValue,omitempty"` + Limit *float64 `json:"limit,omitempty"` + Unit *string `json:"unit,omitempty"` + NextResetTime *date.Time `json:"nextResetTime,omitempty"` +} + +// ServerMetricListResult is represents the response to a List Azure SQL +// server metrics request. +type ServerMetricListResult struct { + autorest.Response `json:"-"` + Value *[]ServerMetric `json:"value,omitempty"` +} + +// ServerProperties is represents the properties of an Azure SQL server. +type ServerProperties struct { + FullyQualifiedDomainName *string `json:"fullyQualifiedDomainName,omitempty"` + Version ServerVersion `json:"version,omitempty"` + AdministratorLogin *string `json:"administratorLogin,omitempty"` + AdministratorLoginPassword *string `json:"administratorLoginPassword,omitempty"` +} + +// ServiceObjective is represents an Azure SQL Database Service Objective. +type ServiceObjective struct { + autorest.Response `json:"-"` + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + *ServiceObjectiveProperties `json:"properties,omitempty"` +} + +// ServiceObjectiveListResult is represents the response to a Get Azure SQL +// Database Service Objectives request. +type ServiceObjectiveListResult struct { + autorest.Response `json:"-"` + Value *[]ServiceObjective `json:"value,omitempty"` +} + +// ServiceObjectiveProperties is represents the properties of an Azure SQL +// Database Service Objective. +type ServiceObjectiveProperties struct { + ServiceObjectiveName *string `json:"serviceObjectiveName,omitempty"` + IsDefault *bool `json:"isDefault,omitempty"` + IsSystem *bool `json:"isSystem,omitempty"` + Description *string `json:"description,omitempty"` + Enabled *bool `json:"enabled,omitempty"` +} + +// ServiceTierAdvisor is represents a Service Tier Advisor. +type ServiceTierAdvisor struct { + autorest.Response `json:"-"` + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + *ServiceTierAdvisorProperties `json:"properties,omitempty"` +} + +// ServiceTierAdvisorListResult is represents the response to a list service +// tier advisor request. +type ServiceTierAdvisorListResult struct { + autorest.Response `json:"-"` + Value *[]ServiceTierAdvisor `json:"value,omitempty"` +} + +// ServiceTierAdvisorProperties is represents the properties of a Service Tier +// Advisor. +type ServiceTierAdvisorProperties struct { + ObservationPeriodStart *date.Time `json:"observationPeriodStart,omitempty"` + ObservationPeriodEnd *date.Time `json:"observationPeriodEnd,omitempty"` + ActiveTimeRatio *float64 `json:"activeTimeRatio,omitempty"` + MinDtu *float64 `json:"minDtu,omitempty"` + AvgDtu *float64 `json:"avgDtu,omitempty"` + MaxDtu *float64 `json:"maxDtu,omitempty"` + MaxSizeInGB *float64 `json:"maxSizeInGB,omitempty"` + ServiceLevelObjectiveUsageMetrics *[]SloUsageMetric `json:"serviceLevelObjectiveUsageMetrics,omitempty"` + CurrentServiceLevelObjective *uuid.UUID `json:"currentServiceLevelObjective,omitempty"` + CurrentServiceLevelObjectiveID *uuid.UUID `json:"currentServiceLevelObjectiveId,omitempty"` + UsageBasedRecommendationServiceLevelObjective *string `json:"usageBasedRecommendationServiceLevelObjective,omitempty"` + UsageBasedRecommendationServiceLevelObjectiveID *uuid.UUID `json:"usageBasedRecommendationServiceLevelObjectiveId,omitempty"` + DatabaseSizeBasedRecommendationServiceLevelObjective *string `json:"databaseSizeBasedRecommendationServiceLevelObjective,omitempty"` + DatabaseSizeBasedRecommendationServiceLevelObjectiveID *uuid.UUID `json:"databaseSizeBasedRecommendationServiceLevelObjectiveId,omitempty"` + DisasterPlanBasedRecommendationServiceLevelObjective *string `json:"disasterPlanBasedRecommendationServiceLevelObjective,omitempty"` + DisasterPlanBasedRecommendationServiceLevelObjectiveID *uuid.UUID `json:"disasterPlanBasedRecommendationServiceLevelObjectiveId,omitempty"` + OverallRecommendationServiceLevelObjective *string `json:"overallRecommendationServiceLevelObjective,omitempty"` + OverallRecommendationServiceLevelObjectiveID *uuid.UUID `json:"overallRecommendationServiceLevelObjectiveId,omitempty"` + Confidence *float64 `json:"confidence,omitempty"` +} + +// SloUsageMetric is represents a Slo Usage Metric. +type SloUsageMetric struct { + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` + ServiceLevelObjective ServiceObjectiveName `json:"serviceLevelObjective,omitempty"` + ServiceLevelObjectiveID *uuid.UUID `json:"serviceLevelObjectiveId,omitempty"` + InRangeTimeRatio *float64 `json:"inRangeTimeRatio,omitempty"` +} + +// SubResource is subresource properties +type SubResource struct { + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` +} + +// Table is represents an Azure SQL Database table. +type Table struct { + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` + *TableProperties `json:"properties,omitempty"` +} + +// TableProperties is represents the properties of an Azure SQL Database table. +type TableProperties struct { + TableType TableType `json:"tableType,omitempty"` + Columns *[]Column `json:"columns,omitempty"` + RecommendedIndexes *[]RecommendedIndex `json:"recommendedIndexes,omitempty"` +} + +// TransparentDataEncryption is represents an Azure SQL Database Transparent +// Data Encryption . +type TransparentDataEncryption struct { + autorest.Response `json:"-"` + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + *TransparentDataEncryptionProperties `json:"properties,omitempty"` +} + +// TransparentDataEncryptionActivity is represents an Azure SQL Database +// Transparent Data Encryption Scan. +type TransparentDataEncryptionActivity struct { + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + *TransparentDataEncryptionActivityProperties `json:"properties,omitempty"` +} + +// TransparentDataEncryptionActivityListResult is represents the response to a +// List Azure SQL Database Transparent Data Encryption Activity request. +type TransparentDataEncryptionActivityListResult struct { + autorest.Response `json:"-"` + Value *[]TransparentDataEncryptionActivity `json:"value,omitempty"` +} + +// TransparentDataEncryptionActivityProperties is represents the properties of +// an Azure SQL Database Transparent Data Encryption Scan. +type TransparentDataEncryptionActivityProperties struct { + Status TransparentDataEncryptionActivityStates `json:"status,omitempty"` + PercentComplete *float64 `json:"percentComplete,omitempty"` +} + +// TransparentDataEncryptionProperties is represents the properties of an +// Azure SQL Database Transparent Data Encryption. +type TransparentDataEncryptionProperties struct { + Status TransparentDataEncryptionStates `json:"status,omitempty"` +} + +// UpgradeHint is represents a Upgrade Hint. +type UpgradeHint struct { + Name *string `json:"name,omitempty"` + ID *string `json:"id,omitempty"` + Type *string `json:"type,omitempty"` + Location *string `json:"location,omitempty"` + Tags *map[string]*string `json:"tags,omitempty"` + TargetServiceLevelObjective *string `json:"targetServiceLevelObjective,omitempty"` + TargetServiceLevelObjectiveID *uuid.UUID `json:"targetServiceLevelObjectiveId,omitempty"` +} + +// UpgradeRecommendedElasticPoolProperties is represents the properties of a +// Azure SQL Recommended Elastic Pool being upgraded. +type UpgradeRecommendedElasticPoolProperties struct { + Name *string `json:"Name,omitempty"` + Edition TargetElasticPoolEditions `json:"Edition,omitempty"` + Dtu *int32 `json:"Dtu,omitempty"` + StorageMb *int32 `json:"StorageMb,omitempty"` + DatabaseDtuMin *int32 `json:"DatabaseDtuMin,omitempty"` + DatabaseDtuMax *int32 `json:"DatabaseDtuMax,omitempty"` + DatabaseCollection *[]string `json:"DatabaseCollection,omitempty"` + IncludeAllDatabases *bool `json:"IncludeAllDatabases,omitempty"` +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/recommendedelasticpools.go b/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/recommendedelasticpools.go new file mode 100644 index 000000000000..2dbe6611e769 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/recommendedelasticpools.go @@ -0,0 +1,380 @@ +package sql + +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Code generated by Microsoft (R) AutoRest Code Generator 0.17.0.0 +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +import ( + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "net/http" +) + +// RecommendedElasticPoolsClient is the provides create, read, update and +// delete functionality for Azure SQL resources including servers, databases, +// elastic pools, recommendations, operations, and usage metrics. +type RecommendedElasticPoolsClient struct { + ManagementClient +} + +// NewRecommendedElasticPoolsClient creates an instance of the +// RecommendedElasticPoolsClient client. +func NewRecommendedElasticPoolsClient(subscriptionID string) RecommendedElasticPoolsClient { + return NewRecommendedElasticPoolsClientWithBaseURI(DefaultBaseURI, subscriptionID) +} + +// NewRecommendedElasticPoolsClientWithBaseURI creates an instance of the +// RecommendedElasticPoolsClient client. +func NewRecommendedElasticPoolsClientWithBaseURI(baseURI string, subscriptionID string) RecommendedElasticPoolsClient { + return RecommendedElasticPoolsClient{NewWithBaseURI(baseURI, subscriptionID)} +} + +// Get gets information about an Azure SQL Recommended Elastic Pool. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +// recommendedElasticPoolName is the name of the Azure SQL Recommended +// Elastic Pool to be retrieved. +func (client RecommendedElasticPoolsClient) Get(resourceGroupName string, serverName string, recommendedElasticPoolName string) (result RecommendedElasticPool, err error) { + req, err := client.GetPreparer(resourceGroupName, serverName, recommendedElasticPoolName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.RecommendedElasticPoolsClient", "Get", nil, "Failure preparing request") + } + + resp, err := client.GetSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.RecommendedElasticPoolsClient", "Get", resp, "Failure sending request") + } + + result, err = client.GetResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.RecommendedElasticPoolsClient", "Get", resp, "Failure responding to request") + } + + return +} + +// GetPreparer prepares the Get request. +func (client RecommendedElasticPoolsClient) GetPreparer(resourceGroupName string, serverName string, recommendedElasticPoolName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "recommendedElasticPoolName": autorest.Encode("path", recommendedElasticPoolName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/recommendedElasticPools/{recommendedElasticPoolName}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// GetSender sends the Get request. The method will close the +// http.Response Body if it receives an error. +func (client RecommendedElasticPoolsClient) GetSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// GetResponder handles the response to the Get request. The method always +// closes the http.Response Body. +func (client RecommendedElasticPoolsClient) GetResponder(resp *http.Response) (result RecommendedElasticPool, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// GetDatabases gets information about an Azure SQL database inside of an +// Azure SQL Recommended Elastic Pool. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +// recommendedElasticPoolName is the name of the Azure SQL Elastic Pool to be +// retrieved. databaseName is the name of the Azure SQL database to be +// retrieved. +func (client RecommendedElasticPoolsClient) GetDatabases(resourceGroupName string, serverName string, recommendedElasticPoolName string, databaseName string) (result Database, err error) { + req, err := client.GetDatabasesPreparer(resourceGroupName, serverName, recommendedElasticPoolName, databaseName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.RecommendedElasticPoolsClient", "GetDatabases", nil, "Failure preparing request") + } + + resp, err := client.GetDatabasesSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.RecommendedElasticPoolsClient", "GetDatabases", resp, "Failure sending request") + } + + result, err = client.GetDatabasesResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.RecommendedElasticPoolsClient", "GetDatabases", resp, "Failure responding to request") + } + + return +} + +// GetDatabasesPreparer prepares the GetDatabases request. +func (client RecommendedElasticPoolsClient) GetDatabasesPreparer(resourceGroupName string, serverName string, recommendedElasticPoolName string, databaseName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "databaseName": autorest.Encode("path", databaseName), + "recommendedElasticPoolName": autorest.Encode("path", recommendedElasticPoolName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/recommendedElasticPools/{recommendedElasticPoolName}/databases/{databaseName}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// GetDatabasesSender sends the GetDatabases request. The method will close the +// http.Response Body if it receives an error. +func (client RecommendedElasticPoolsClient) GetDatabasesSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// GetDatabasesResponder handles the response to the GetDatabases request. The method always +// closes the http.Response Body. +func (client RecommendedElasticPoolsClient) GetDatabasesResponder(resp *http.Response) (result Database, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// List returns information about Azure SQL Recommended Elastic Pools. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +func (client RecommendedElasticPoolsClient) List(resourceGroupName string, serverName string) (result RecommendedElasticPoolListResult, err error) { + req, err := client.ListPreparer(resourceGroupName, serverName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.RecommendedElasticPoolsClient", "List", nil, "Failure preparing request") + } + + resp, err := client.ListSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.RecommendedElasticPoolsClient", "List", resp, "Failure sending request") + } + + result, err = client.ListResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.RecommendedElasticPoolsClient", "List", resp, "Failure responding to request") + } + + return +} + +// ListPreparer prepares the List request. +func (client RecommendedElasticPoolsClient) ListPreparer(resourceGroupName string, serverName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/recommendedElasticPools", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// ListSender sends the List request. The method will close the +// http.Response Body if it receives an error. +func (client RecommendedElasticPoolsClient) ListSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// ListResponder handles the response to the List request. The method always +// closes the http.Response Body. +func (client RecommendedElasticPoolsClient) ListResponder(resp *http.Response) (result RecommendedElasticPoolListResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// ListDatabases returns information about an Azure SQL database inside of an +// Azure SQL Recommended Elastic Pool. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +// recommendedElasticPoolName is the name of the Azure SQL Recommended +// Elastic Pool to be retrieved. +func (client RecommendedElasticPoolsClient) ListDatabases(resourceGroupName string, serverName string, recommendedElasticPoolName string) (result DatabaseListResult, err error) { + req, err := client.ListDatabasesPreparer(resourceGroupName, serverName, recommendedElasticPoolName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.RecommendedElasticPoolsClient", "ListDatabases", nil, "Failure preparing request") + } + + resp, err := client.ListDatabasesSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.RecommendedElasticPoolsClient", "ListDatabases", resp, "Failure sending request") + } + + result, err = client.ListDatabasesResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.RecommendedElasticPoolsClient", "ListDatabases", resp, "Failure responding to request") + } + + return +} + +// ListDatabasesPreparer prepares the ListDatabases request. +func (client RecommendedElasticPoolsClient) ListDatabasesPreparer(resourceGroupName string, serverName string, recommendedElasticPoolName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "recommendedElasticPoolName": autorest.Encode("path", recommendedElasticPoolName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/recommendedElasticPools/{recommendedElasticPoolName}/databases", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// ListDatabasesSender sends the ListDatabases request. The method will close the +// http.Response Body if it receives an error. +func (client RecommendedElasticPoolsClient) ListDatabasesSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// ListDatabasesResponder handles the response to the ListDatabases request. The method always +// closes the http.Response Body. +func (client RecommendedElasticPoolsClient) ListDatabasesResponder(resp *http.Response) (result DatabaseListResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// ListMetrics returns information about an recommended elastic pool metrics. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +// recommendedElasticPoolName is the name of the Azure SQL Recommended +// Elastic Pool to be retrieved. +func (client RecommendedElasticPoolsClient) ListMetrics(resourceGroupName string, serverName string, recommendedElasticPoolName string) (result RecommendedElasticPoolListMetricsResult, err error) { + req, err := client.ListMetricsPreparer(resourceGroupName, serverName, recommendedElasticPoolName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.RecommendedElasticPoolsClient", "ListMetrics", nil, "Failure preparing request") + } + + resp, err := client.ListMetricsSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.RecommendedElasticPoolsClient", "ListMetrics", resp, "Failure sending request") + } + + result, err = client.ListMetricsResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.RecommendedElasticPoolsClient", "ListMetrics", resp, "Failure responding to request") + } + + return +} + +// ListMetricsPreparer prepares the ListMetrics request. +func (client RecommendedElasticPoolsClient) ListMetricsPreparer(resourceGroupName string, serverName string, recommendedElasticPoolName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "recommendedElasticPoolName": autorest.Encode("path", recommendedElasticPoolName), + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/recommendedElasticPools/{recommendedElasticPoolName}/metrics", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// ListMetricsSender sends the ListMetrics request. The method will close the +// http.Response Body if it receives an error. +func (client RecommendedElasticPoolsClient) ListMetricsSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// ListMetricsResponder handles the response to the ListMetrics request. The method always +// closes the http.Response Body. +func (client RecommendedElasticPoolsClient) ListMetricsResponder(resp *http.Response) (result RecommendedElasticPoolListMetricsResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/servers.go b/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/servers.go new file mode 100644 index 000000000000..11e4a35e4faf --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/servers.go @@ -0,0 +1,553 @@ +package sql + +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Code generated by Microsoft (R) AutoRest Code Generator 0.17.0.0 +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +import ( + "github.com/Azure/go-autorest/autorest" + "github.com/Azure/go-autorest/autorest/azure" + "net/http" +) + +// ServersClient is the provides create, read, update and delete functionality +// for Azure SQL resources including servers, databases, elastic pools, +// recommendations, operations, and usage metrics. +type ServersClient struct { + ManagementClient +} + +// NewServersClient creates an instance of the ServersClient client. +func NewServersClient(subscriptionID string) ServersClient { + return NewServersClientWithBaseURI(DefaultBaseURI, subscriptionID) +} + +// NewServersClientWithBaseURI creates an instance of the ServersClient client. +func NewServersClientWithBaseURI(baseURI string, subscriptionID string) ServersClient { + return ServersClient{NewWithBaseURI(baseURI, subscriptionID)} +} + +// CreateOrUpdate creates a new Azure SQL server. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. parameters is +// the required parameters for creating or updating a server. +func (client ServersClient) CreateOrUpdate(resourceGroupName string, serverName string, parameters Server) (result Server, err error) { + req, err := client.CreateOrUpdatePreparer(resourceGroupName, serverName, parameters) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.ServersClient", "CreateOrUpdate", nil, "Failure preparing request") + } + + resp, err := client.CreateOrUpdateSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.ServersClient", "CreateOrUpdate", resp, "Failure sending request") + } + + result, err = client.CreateOrUpdateResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.ServersClient", "CreateOrUpdate", resp, "Failure responding to request") + } + + return +} + +// CreateOrUpdatePreparer prepares the CreateOrUpdate request. +func (client ServersClient) CreateOrUpdatePreparer(resourceGroupName string, serverName string, parameters Server) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsJSON(), + autorest.AsPut(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}", pathParameters), + autorest.WithJSON(parameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// CreateOrUpdateSender sends the CreateOrUpdate request. The method will close the +// http.Response Body if it receives an error. +func (client ServersClient) CreateOrUpdateSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// CreateOrUpdateResponder handles the response to the CreateOrUpdate request. The method always +// closes the http.Response Body. +func (client ServersClient) CreateOrUpdateResponder(resp *http.Response) (result Server, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// Delete deletes a SQL server. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +func (client ServersClient) Delete(resourceGroupName string, serverName string) (result autorest.Response, err error) { + req, err := client.DeletePreparer(resourceGroupName, serverName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.ServersClient", "Delete", nil, "Failure preparing request") + } + + resp, err := client.DeleteSender(req) + if err != nil { + result.Response = resp + return result, autorest.NewErrorWithError(err, "sql.ServersClient", "Delete", resp, "Failure sending request") + } + + result, err = client.DeleteResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.ServersClient", "Delete", resp, "Failure responding to request") + } + + return +} + +// DeletePreparer prepares the Delete request. +func (client ServersClient) DeletePreparer(resourceGroupName string, serverName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsDelete(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// DeleteSender sends the Delete request. The method will close the +// http.Response Body if it receives an error. +func (client ServersClient) DeleteSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// DeleteResponder handles the response to the Delete request. The method always +// closes the http.Response Body. +func (client ServersClient) DeleteResponder(resp *http.Response) (result autorest.Response, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusNoContent), + autorest.ByClosing()) + result.Response = resp + return +} + +// GetByResourceGroup gets information about an Azure SQL server. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +func (client ServersClient) GetByResourceGroup(resourceGroupName string, serverName string) (result Server, err error) { + req, err := client.GetByResourceGroupPreparer(resourceGroupName, serverName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.ServersClient", "GetByResourceGroup", nil, "Failure preparing request") + } + + resp, err := client.GetByResourceGroupSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.ServersClient", "GetByResourceGroup", resp, "Failure sending request") + } + + result, err = client.GetByResourceGroupResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.ServersClient", "GetByResourceGroup", resp, "Failure responding to request") + } + + return +} + +// GetByResourceGroupPreparer prepares the GetByResourceGroup request. +func (client ServersClient) GetByResourceGroupPreparer(resourceGroupName string, serverName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// GetByResourceGroupSender sends the GetByResourceGroup request. The method will close the +// http.Response Body if it receives an error. +func (client ServersClient) GetByResourceGroupSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// GetByResourceGroupResponder handles the response to the GetByResourceGroup request. The method always +// closes the http.Response Body. +func (client ServersClient) GetByResourceGroupResponder(resp *http.Response) (result Server, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// GetServiceObjective gets information about an Azure SQL database Service +// Objective. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +// serviceObjectiveName is the name of the service objective to retrieve. +func (client ServersClient) GetServiceObjective(resourceGroupName string, serverName string, serviceObjectiveName string) (result ServiceObjective, err error) { + req, err := client.GetServiceObjectivePreparer(resourceGroupName, serverName, serviceObjectiveName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.ServersClient", "GetServiceObjective", nil, "Failure preparing request") + } + + resp, err := client.GetServiceObjectiveSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.ServersClient", "GetServiceObjective", resp, "Failure sending request") + } + + result, err = client.GetServiceObjectiveResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.ServersClient", "GetServiceObjective", resp, "Failure responding to request") + } + + return +} + +// GetServiceObjectivePreparer prepares the GetServiceObjective request. +func (client ServersClient) GetServiceObjectivePreparer(resourceGroupName string, serverName string, serviceObjectiveName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "serviceObjectiveName": autorest.Encode("path", serviceObjectiveName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/serviceObjectives/{serviceObjectiveName}", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// GetServiceObjectiveSender sends the GetServiceObjective request. The method will close the +// http.Response Body if it receives an error. +func (client ServersClient) GetServiceObjectiveSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// GetServiceObjectiveResponder handles the response to the GetServiceObjective request. The method always +// closes the http.Response Body. +func (client ServersClient) GetServiceObjectiveResponder(resp *http.Response) (result ServiceObjective, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// List returns information about an Azure SQL server. +func (client ServersClient) List() (result ServerListResult, err error) { + req, err := client.ListPreparer() + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.ServersClient", "List", nil, "Failure preparing request") + } + + resp, err := client.ListSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.ServersClient", "List", resp, "Failure sending request") + } + + result, err = client.ListResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.ServersClient", "List", resp, "Failure responding to request") + } + + return +} + +// ListPreparer prepares the List request. +func (client ServersClient) ListPreparer() (*http.Request, error) { + pathParameters := map[string]interface{}{ + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/providers/Microsoft.Sql/servers", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// ListSender sends the List request. The method will close the +// http.Response Body if it receives an error. +func (client ServersClient) ListSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// ListResponder handles the response to the List request. The method always +// closes the http.Response Body. +func (client ServersClient) ListResponder(resp *http.Response) (result ServerListResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// ListByResourceGroup returns information about an Azure SQL server. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. +func (client ServersClient) ListByResourceGroup(resourceGroupName string) (result ServerListResult, err error) { + req, err := client.ListByResourceGroupPreparer(resourceGroupName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.ServersClient", "ListByResourceGroup", nil, "Failure preparing request") + } + + resp, err := client.ListByResourceGroupSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.ServersClient", "ListByResourceGroup", resp, "Failure sending request") + } + + result, err = client.ListByResourceGroupResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.ServersClient", "ListByResourceGroup", resp, "Failure responding to request") + } + + return +} + +// ListByResourceGroupPreparer prepares the ListByResourceGroup request. +func (client ServersClient) ListByResourceGroupPreparer(resourceGroupName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// ListByResourceGroupSender sends the ListByResourceGroup request. The method will close the +// http.Response Body if it receives an error. +func (client ServersClient) ListByResourceGroupSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// ListByResourceGroupResponder handles the response to the ListByResourceGroup request. The method always +// closes the http.Response Body. +func (client ServersClient) ListByResourceGroupResponder(resp *http.Response) (result ServerListResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// ListServiceObjectives returns information about Azure SQL database Service +// Objectives. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +func (client ServersClient) ListServiceObjectives(resourceGroupName string, serverName string) (result ServiceObjectiveListResult, err error) { + req, err := client.ListServiceObjectivesPreparer(resourceGroupName, serverName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.ServersClient", "ListServiceObjectives", nil, "Failure preparing request") + } + + resp, err := client.ListServiceObjectivesSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.ServersClient", "ListServiceObjectives", resp, "Failure sending request") + } + + result, err = client.ListServiceObjectivesResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.ServersClient", "ListServiceObjectives", resp, "Failure responding to request") + } + + return +} + +// ListServiceObjectivesPreparer prepares the ListServiceObjectives request. +func (client ServersClient) ListServiceObjectivesPreparer(resourceGroupName string, serverName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/serviceObjectives", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// ListServiceObjectivesSender sends the ListServiceObjectives request. The method will close the +// http.Response Body if it receives an error. +func (client ServersClient) ListServiceObjectivesSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// ListServiceObjectivesResponder handles the response to the ListServiceObjectives request. The method always +// closes the http.Response Body. +func (client ServersClient) ListServiceObjectivesResponder(resp *http.Response) (result ServiceObjectiveListResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} + +// ListUsages returns information about Azure SQL server usage. +// +// resourceGroupName is the name of the resource group that contains the +// resource. You can obtain this value from the Azure Resource Manager API or +// the portal. serverName is the name of the Azure SQL server. +func (client ServersClient) ListUsages(resourceGroupName string, serverName string) (result ServerMetricListResult, err error) { + req, err := client.ListUsagesPreparer(resourceGroupName, serverName) + if err != nil { + return result, autorest.NewErrorWithError(err, "sql.ServersClient", "ListUsages", nil, "Failure preparing request") + } + + resp, err := client.ListUsagesSender(req) + if err != nil { + result.Response = autorest.Response{Response: resp} + return result, autorest.NewErrorWithError(err, "sql.ServersClient", "ListUsages", resp, "Failure sending request") + } + + result, err = client.ListUsagesResponder(resp) + if err != nil { + err = autorest.NewErrorWithError(err, "sql.ServersClient", "ListUsages", resp, "Failure responding to request") + } + + return +} + +// ListUsagesPreparer prepares the ListUsages request. +func (client ServersClient) ListUsagesPreparer(resourceGroupName string, serverName string) (*http.Request, error) { + pathParameters := map[string]interface{}{ + "resourceGroupName": autorest.Encode("path", resourceGroupName), + "serverName": autorest.Encode("path", serverName), + "subscriptionId": autorest.Encode("path", client.SubscriptionID), + } + + queryParameters := map[string]interface{}{ + "api-version": client.APIVersion, + } + + preparer := autorest.CreatePreparer( + autorest.AsGet(), + autorest.WithBaseURL(client.BaseURI), + autorest.WithPathParameters("/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Sql/servers/{serverName}/usages", pathParameters), + autorest.WithQueryParameters(queryParameters)) + return preparer.Prepare(&http.Request{}) +} + +// ListUsagesSender sends the ListUsages request. The method will close the +// http.Response Body if it receives an error. +func (client ServersClient) ListUsagesSender(req *http.Request) (*http.Response, error) { + return autorest.SendWithSender(client, req) +} + +// ListUsagesResponder handles the response to the ListUsages request. The method always +// closes the http.Response Body. +func (client ServersClient) ListUsagesResponder(resp *http.Response) (result ServerMetricListResult, err error) { + err = autorest.Respond( + resp, + client.ByInspecting(), + azure.WithErrorUnlessStatusCode(http.StatusOK), + autorest.ByUnmarshallingJSON(&result), + autorest.ByClosing()) + result.Response = autorest.Response{Response: resp} + return +} diff --git a/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/version.go b/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/version.go new file mode 100644 index 000000000000..2fabb4531291 --- /dev/null +++ b/vendor/github.com/Azure/azure-sdk-for-go/arm/sql/version.go @@ -0,0 +1,60 @@ +package sql + +// Copyright (c) Microsoft and contributors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Code generated by Microsoft (R) AutoRest Code Generator 1.0.1.0 +// Changes may cause incorrect behavior and will be lost if the code is +// regenerated. + +import ( + "bytes" + "fmt" + "strings" +) + +const ( + major = "8" + minor = "1" + patch = "0" + tag = "beta" + userAgentFormat = "Azure-SDK-For-Go/%s arm-%s/%s" +) + +// cached results of UserAgent and Version to prevent repeated operations. +var ( + userAgent string + version string +) + +// UserAgent returns the UserAgent string to use when sending http.Requests. +func UserAgent() string { + if userAgent == "" { + userAgent = fmt.Sprintf(userAgentFormat, Version(), "sql", "2014-04-01") + } + return userAgent +} + +// Version returns the semantic version (see http://semver.org) of the client. +func Version() string { + if version == "" { + versionBuilder := bytes.NewBufferString(fmt.Sprintf("%s.%s.%s", major, minor, patch)) + if tag != "" { + versionBuilder.WriteRune('-') + versionBuilder.WriteString(strings.TrimPrefix(tag, "-")) + } + version = string(versionBuilder.Bytes()) + } + return version +} diff --git a/vendor/github.com/hashicorp/hcl/appveyor.yml b/vendor/github.com/hashicorp/hcl/appveyor.yml index 3c8cdf8e977d..4db0b7112728 100644 --- a/vendor/github.com/hashicorp/hcl/appveyor.yml +++ b/vendor/github.com/hashicorp/hcl/appveyor.yml @@ -4,7 +4,7 @@ clone_folder: c:\gopath\src\github.com\hashicorp\hcl environment: GOPATH: c:\gopath init: - - git config --global core.autocrlf true + - git config --global core.autocrlf false install: - cmd: >- echo %Path% diff --git a/vendor/github.com/hashicorp/hcl/hcl/parser/parser.go b/vendor/github.com/hashicorp/hcl/hcl/parser/parser.go index 6e54bed9740a..b4881806e783 100644 --- a/vendor/github.com/hashicorp/hcl/hcl/parser/parser.go +++ b/vendor/github.com/hashicorp/hcl/hcl/parser/parser.go @@ -3,6 +3,7 @@ package parser import ( + "bytes" "errors" "fmt" "strings" @@ -36,6 +37,11 @@ func newParser(src []byte) *Parser { // Parse returns the fully parsed source and returns the abstract syntax tree. func Parse(src []byte) (*ast.File, error) { + // normalize all line endings + // since the scanner and output only work with "\n" line endings, we may + // end up with dangling "\r" characters in the parsed data. + src = bytes.Replace(src, []byte("\r\n"), []byte("\n"), -1) + p := newParser(src) return p.Parse() } diff --git a/vendor/github.com/hashicorp/hcl/hcl/printer/printer.go b/vendor/github.com/hashicorp/hcl/hcl/printer/printer.go index a296fc851a8e..6617ab8e7a26 100644 --- a/vendor/github.com/hashicorp/hcl/hcl/printer/printer.go +++ b/vendor/github.com/hashicorp/hcl/hcl/printer/printer.go @@ -62,6 +62,5 @@ func Format(src []byte) ([]byte, error) { // Add trailing newline to result buf.WriteString("\n") - return buf.Bytes(), nil } diff --git a/vendor/github.com/hashicorp/hcl/json/parser/parser.go b/vendor/github.com/hashicorp/hcl/json/parser/parser.go index 6f4608530062..125a5f07298c 100644 --- a/vendor/github.com/hashicorp/hcl/json/parser/parser.go +++ b/vendor/github.com/hashicorp/hcl/json/parser/parser.go @@ -147,7 +147,7 @@ func (p *Parser) objectKey() ([]*ast.ObjectKey, error) { // Done return keys, nil case token.ILLEGAL: - fmt.Println("illegal") + return nil, errors.New("illegal") default: return nil, fmt.Errorf("expected: STRING got: %s", p.tok.Type) } diff --git a/vendor/github.com/hashicorp/hil/ast/literal.go b/vendor/github.com/hashicorp/hil/ast/literal.go index 8149d495dd83..da6014fee2ba 100644 --- a/vendor/github.com/hashicorp/hil/ast/literal.go +++ b/vendor/github.com/hashicorp/hil/ast/literal.go @@ -77,3 +77,12 @@ func (n *LiteralNode) String() string { func (n *LiteralNode) Type(Scope) (Type, error) { return n.Typex, nil } + +// IsUnknown returns true either if the node's value is itself unknown +// of if it is a collection containing any unknown elements, deeply. +func (n *LiteralNode) IsUnknown() bool { + return IsUnknown(Variable{ + Type: n.Typex, + Value: n.Value, + }) +} diff --git a/vendor/github.com/hashicorp/hil/ast/variables_helper.go b/vendor/github.com/hashicorp/hil/ast/variables_helper.go index 40aa534cf679..06bd18de2ac2 100644 --- a/vendor/github.com/hashicorp/hil/ast/variables_helper.go +++ b/vendor/github.com/hashicorp/hil/ast/variables_helper.go @@ -3,54 +3,61 @@ package ast import "fmt" func VariableListElementTypesAreHomogenous(variableName string, list []Variable) (Type, error) { - listTypes := make(map[Type]struct{}) + if len(list) == 0 { + return TypeInvalid, fmt.Errorf("list %q does not have any elements so cannot determine type.", variableName) + } + + elemType := TypeUnknown for _, v := range list { - // Allow unknown if v.Type == TypeUnknown { continue } - if _, ok := listTypes[v.Type]; ok { + if elemType == TypeUnknown { + elemType = v.Type continue } - listTypes[v.Type] = struct{}{} - } - if len(listTypes) != 1 && len(list) != 0 { - return TypeInvalid, fmt.Errorf("list %q does not have homogenous types. found %s", variableName, reportTypes(listTypes)) - } + if v.Type != elemType { + return TypeInvalid, fmt.Errorf( + "list %q does not have homogenous types. found %s and then %s", + variableName, + elemType, v.Type, + ) + } - if len(list) > 0 { - return list[0].Type, nil + elemType = v.Type } - return TypeInvalid, fmt.Errorf("list %q does not have any elements so cannot determine type.", variableName) + return elemType, nil } func VariableMapValueTypesAreHomogenous(variableName string, vmap map[string]Variable) (Type, error) { - valueTypes := make(map[Type]struct{}) + if len(vmap) == 0 { + return TypeInvalid, fmt.Errorf("map %q does not have any elements so cannot determine type.", variableName) + } + + elemType := TypeUnknown for _, v := range vmap { - // Allow unknown if v.Type == TypeUnknown { continue } - if _, ok := valueTypes[v.Type]; ok { + if elemType == TypeUnknown { + elemType = v.Type continue } - valueTypes[v.Type] = struct{}{} - } - - if len(valueTypes) != 1 && len(vmap) != 0 { - return TypeInvalid, fmt.Errorf("map %q does not have homogenous value types. found %s", variableName, reportTypes(valueTypes)) - } + if v.Type != elemType { + return TypeInvalid, fmt.Errorf( + "map %q does not have homogenous types. found %s and then %s", + variableName, + elemType, v.Type, + ) + } - // For loop here is an easy way to get a single key, we return immediately. - for _, v := range vmap { - return v.Type, nil + elemType = v.Type } - // This means the map is empty - return TypeInvalid, fmt.Errorf("map %q does not have any elements so cannot determine type.", variableName) + return elemType, nil } diff --git a/vendor/github.com/hashicorp/hil/check_types.go b/vendor/github.com/hashicorp/hil/check_types.go index a8ca44e06f6a..7a191e877093 100644 --- a/vendor/github.com/hashicorp/hil/check_types.go +++ b/vendor/github.com/hashicorp/hil/check_types.go @@ -98,10 +98,6 @@ func (v *TypeCheck) visit(raw ast.Node) ast.Node { pos.Column, pos.Line, err) } - if v.StackPeek() == ast.TypeUnknown { - v.err = errExitUnknown - } - return result } @@ -116,6 +112,14 @@ func (tc *typeCheckArithmetic) TypeCheck(v *TypeCheck) (ast.Node, error) { exprs[len(tc.n.Exprs)-1-i] = v.StackPop() } + // If any operand is unknown then our result is automatically unknown + for _, ty := range exprs { + if ty == ast.TypeUnknown { + v.StackPush(ast.TypeUnknown) + return tc.n, nil + } + } + switch tc.n.Op { case ast.ArithmeticOpLogicalAnd, ast.ArithmeticOpLogicalOr: return tc.checkLogical(v, exprs) @@ -333,6 +337,11 @@ func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) { continue } + if args[i] == ast.TypeUnknown { + v.StackPush(ast.TypeUnknown) + return tc.n, nil + } + if args[i] != expected { cn := v.ImplicitConversion(args[i], expected, tc.n.Args[i]) if cn != nil { @@ -350,6 +359,11 @@ func (tc *typeCheckCall) TypeCheck(v *TypeCheck) (ast.Node, error) { if function.Variadic && function.VariadicType != ast.TypeAny { args = args[len(function.ArgTypes):] for i, t := range args { + if t == ast.TypeUnknown { + v.StackPush(ast.TypeUnknown) + return tc.n, nil + } + if t != function.VariadicType { realI := i + len(function.ArgTypes) cn := v.ImplicitConversion( @@ -384,6 +398,11 @@ func (tc *typeCheckConditional) TypeCheck(v *TypeCheck) (ast.Node, error) { trueType := v.StackPop() condType := v.StackPop() + if condType == ast.TypeUnknown { + v.StackPush(ast.TypeUnknown) + return tc.n, nil + } + if condType != ast.TypeBool { cn := v.ImplicitConversion(condType, ast.TypeBool, tc.n.CondExpr) if cn == nil { @@ -457,6 +476,13 @@ func (tc *typeCheckOutput) TypeCheck(v *TypeCheck) (ast.Node, error) { types[len(n.Exprs)-1-i] = v.StackPop() } + for _, ty := range types { + if ty == ast.TypeUnknown { + v.StackPush(ast.TypeUnknown) + return tc.n, nil + } + } + // If there is only one argument and it is a list, we evaluate to a list if len(types) == 1 { switch t := types[0]; t { @@ -469,7 +495,14 @@ func (tc *typeCheckOutput) TypeCheck(v *TypeCheck) (ast.Node, error) { } // Otherwise, all concat args must be strings, so validate that + resultType := ast.TypeString for i, t := range types { + + if t == ast.TypeUnknown { + resultType = ast.TypeUnknown + continue + } + if t != ast.TypeString { cn := v.ImplicitConversion(t, ast.TypeString, n.Exprs[i]) if cn != nil { @@ -482,8 +515,8 @@ func (tc *typeCheckOutput) TypeCheck(v *TypeCheck) (ast.Node, error) { } } - // This always results in type string - v.StackPush(ast.TypeString) + // This always results in type string, unless there are unknowns + v.StackPush(resultType) return n, nil } @@ -509,13 +542,6 @@ func (tc *typeCheckVariableAccess) TypeCheck(v *TypeCheck) (ast.Node, error) { "unknown variable accessed: %s", tc.n.Name) } - // Check if the variable contains any unknown types. If so, then - // mark it as unknown. - if ast.IsUnknown(variable) { - v.StackPush(ast.TypeUnknown) - return tc.n, nil - } - // Add the type to the stack v.StackPush(variable.Type) @@ -530,6 +556,11 @@ func (tc *typeCheckIndex) TypeCheck(v *TypeCheck) (ast.Node, error) { keyType := v.StackPop() targetType := v.StackPop() + if keyType == ast.TypeUnknown || targetType == ast.TypeUnknown { + v.StackPush(ast.TypeUnknown) + return tc.n, nil + } + // Ensure we have a VariableAccess as the target varAccessNode, ok := tc.n.Target.(*ast.VariableAccess) if !ok { diff --git a/vendor/github.com/hashicorp/hil/eval.go b/vendor/github.com/hashicorp/hil/eval.go index 5c7afb024161..27820769e81a 100644 --- a/vendor/github.com/hashicorp/hil/eval.go +++ b/vendor/github.com/hashicorp/hil/eval.go @@ -54,6 +54,14 @@ func Eval(root ast.Node, config *EvalConfig) (EvaluationResult, error) { return InvalidResult, err } + // If the result contains any nested unknowns then the result as a whole + // is unknown, so that callers only have to deal with "entirely known" + // or "entirely unknown" as outcomes. + if ast.IsUnknown(ast.Variable{Type: outputType, Value: output}) { + outputType = ast.TypeUnknown + output = UnknownValue + } + switch outputType { case ast.TypeList: val, err := VariableToInterface(ast.Variable{ @@ -264,6 +272,10 @@ func (v *evalCall) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, e args := make([]interface{}, len(v.Args)) for i, _ := range v.Args { node := stack.Pop().(*ast.LiteralNode) + if node.IsUnknown() { + // If any arguments are unknown then the result is automatically unknown + return UnknownValue, ast.TypeUnknown, nil + } args[len(v.Args)-1-i] = node.Value } @@ -286,6 +298,11 @@ func (v *evalConditional) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast. trueLit := stack.Pop().(*ast.LiteralNode) condLit := stack.Pop().(*ast.LiteralNode) + if condLit.IsUnknown() { + // If our conditional is unknown then our result is also unknown + return UnknownValue, ast.TypeUnknown, nil + } + if condLit.Value.(bool) { return trueLit.Value, trueLit.Typex, nil } else { @@ -301,6 +318,17 @@ func (v *evalIndex) Eval(scope ast.Scope, stack *ast.Stack) (interface{}, ast.Ty variableName := v.Index.Target.(*ast.VariableAccess).Name + if key.IsUnknown() { + // If our key is unknown then our result is also unknown + return UnknownValue, ast.TypeUnknown, nil + } + + // For target, we'll accept collections containing unknown values but + // we still need to catch when the collection itself is unknown, shallowly. + if target.Typex == ast.TypeUnknown { + return UnknownValue, ast.TypeUnknown, nil + } + switch target.Typex { case ast.TypeList: return v.evalListIndex(variableName, target.Value, key.Value) @@ -377,8 +405,22 @@ func (v *evalOutput) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, // The expressions should all be on the stack in reverse // order. So pop them off, reverse their order, and concatenate. nodes := make([]*ast.LiteralNode, 0, len(v.Exprs)) + haveUnknown := false for range v.Exprs { - nodes = append(nodes, stack.Pop().(*ast.LiteralNode)) + n := stack.Pop().(*ast.LiteralNode) + nodes = append(nodes, n) + + // If we have any unknowns then the whole result is unknown + // (we must deal with this first, because the type checker can + // skip type conversions in the presence of unknowns, and thus + // any of our other nodes may be incorrectly typed.) + if n.IsUnknown() { + haveUnknown = true + } + } + + if haveUnknown { + return UnknownValue, ast.TypeUnknown, nil } // Special case the single list and map @@ -396,6 +438,14 @@ func (v *evalOutput) Eval(s ast.Scope, stack *ast.Stack) (interface{}, ast.Type, // Otherwise concatenate the strings var buf bytes.Buffer for i := len(nodes) - 1; i >= 0; i-- { + if nodes[i].Typex != ast.TypeString { + return nil, ast.TypeInvalid, fmt.Errorf( + "invalid output with %s value at index %d: %#v", + nodes[i].Typex, + i, + nodes[i].Value, + ) + } buf.WriteString(nodes[i].Value.(string)) } @@ -418,11 +468,5 @@ func (v *evalVariableAccess) Eval(scope ast.Scope, _ *ast.Stack) (interface{}, a "unknown variable accessed: %s", v.Name) } - // Check if the variable contains any unknown types. If so, then - // mark it as unknown and return that type. - if ast.IsUnknown(variable) { - return nil, ast.TypeUnknown, nil - } - return variable.Value, variable.Type, nil } diff --git a/vendor/github.com/profitbricks/profitbricks-sdk-go/nic.go b/vendor/github.com/profitbricks/profitbricks-sdk-go/nic.go index 1d082a64d142..86571ad01cce 100644 --- a/vendor/github.com/profitbricks/profitbricks-sdk-go/nic.go +++ b/vendor/github.com/profitbricks/profitbricks-sdk-go/nic.go @@ -22,7 +22,7 @@ type NicProperties struct { Name string `json:"name,omitempty"` Mac string `json:"mac,omitempty"` Ips []string `json:"ips,omitempty"` - Dhcp bool `json:"dhcp,omitempty"` + Dhcp bool `json:"dhcp"` Lan int `json:"lan,omitempty"` FirewallActive bool `json:"firewallActive,omitempty"` Nat bool `json:"nat,omitempty"` diff --git a/vendor/github.com/profitbricks/profitbricks-sdk-go/request.go b/vendor/github.com/profitbricks/profitbricks-sdk-go/request.go index 023ae1a4a24a..98cbbffd96dc 100644 --- a/vendor/github.com/profitbricks/profitbricks-sdk-go/request.go +++ b/vendor/github.com/profitbricks/profitbricks-sdk-go/request.go @@ -3,6 +3,7 @@ package profitbricks import ( "encoding/json" "net/http" + "time" ) type RequestStatus struct { @@ -26,6 +27,55 @@ type RequestTarget struct { Status string `json:"status,omitempty"` } +type Requests struct { + Id string `json:"id,omitempty"` + Type_ string `json:"type,omitempty"` + Href string `json:"href,omitempty"` + Items []Request `json:"items,omitempty"` + Response string `json:"Response,omitempty"` + Headers *http.Header `json:"headers,omitempty"` + StatusCode int `json:"headers,omitempty"` +} + +type Request struct { + ID string `json:"id"` + Type string `json:"type"` + Href string `json:"href"` + Metadata struct { + CreatedDate time.Time `json:"createdDate"` + CreatedBy string `json:"createdBy"` + Etag string `json:"etag"` + RequestStatus struct { + ID string `json:"id"` + Type string `json:"type"` + Href string `json:"href"` + } `json:"requestStatus"` + } `json:"metadata"` + Properties struct { + Method string `json:"method"` + Headers interface{} `json:"headers"` + Body interface{} `json:"body"` + URL string `json:"url"` + } `json:"properties"` + Response string `json:"Response,omitempty"` + Headers *http.Header `json:"headers,omitempty"` + StatusCode int `json:"headers,omitempty"` +} + +func ListRequests() Requests { + url := mk_url("/requests") + `?depth=` + Depth + req, _ := http.NewRequest("GET", url, nil) + req.Header.Add("Content-Type", FullHeader) + return toRequests(do(req)) +} + +func GetRequest(req_id string) Request { + url := mk_url("/requests/" + req_id) + `?depth=` + Depth + req, _ := http.NewRequest("GET", url, nil) + req.Header.Add("Content-Type", FullHeader) + return toRequest(do(req)) +} + func GetRequestStatus(path string) RequestStatus { url := mk_url(path) + `?depth=` + Depth req, _ := http.NewRequest("GET", url, nil) @@ -41,3 +91,22 @@ func toRequestStatus(resp Resp) RequestStatus { server.StatusCode = resp.StatusCode return server } + +func toRequests(resp Resp) Requests { + var server Requests + json.Unmarshal(resp.Body, &server) + server.Response = string(resp.Body) + server.Headers = &resp.Headers + server.StatusCode = resp.StatusCode + return server +} + +func toRequest(resp Resp) Request { + var server Request + json.Unmarshal(resp.Body, &server) + server.Response = string(resp.Body) + server.Headers = &resp.Headers + server.StatusCode = resp.StatusCode + return server +} + diff --git a/vendor/google.golang.org/api/compute/v1/compute-api.json b/vendor/google.golang.org/api/compute/v1/compute-api.json index 391300e3cfc6..1a90deafc8dc 100644 --- a/vendor/google.golang.org/api/compute/v1/compute-api.json +++ b/vendor/google.golang.org/api/compute/v1/compute-api.json @@ -1,11 +1,11 @@ { "kind": "discovery#restDescription", - "etag": "\"tbys6C40o18GZwyMen5GMkdK-3s/BIPueDp4_YHmOXWnzCh7vT7JOHQ\"", + "etag": "\"YWOzh2SDasdU84ArJnpYek-OMdg/Jd_rZl9yiuNZcWvcgDDlnKBhwlY\"", "discoveryVersion": "v1", "id": "compute:v1", "name": "compute", "version": "v1", - "revision": "20161123", + "revision": "20170329", "title": "Compute Engine API", "description": "Creates and runs virtual machines on Google Cloud Platform.", "ownerDomain": "google.com", @@ -103,7 +103,7 @@ }, "name": { "type": "string", - "description": "Name of this access configuration." + "description": "The name of this access configuration. The default and recommended name is External NAT but you can use any arbitrary string you would like. For example, My external IP or Network Access." }, "natIP": { "type": "string", @@ -282,6 +282,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -302,6 +303,7 @@ "", "", "", + "", "" ] }, @@ -410,12 +412,7 @@ "enumDescriptions": [ "", "" - ], - "annotations": { - "required": [ - "compute.instances.insert" - ] - } + ] } } }, @@ -596,6 +593,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -616,6 +614,7 @@ "", "", "", + "", "" ] }, @@ -794,6 +793,79 @@ } } }, + "BackendBucket": { + "id": "BackendBucket", + "type": "object", + "description": "A BackendBucket resource. This resource defines a Cloud Storage bucket.", + "properties": { + "bucketName": { + "type": "string", + "description": "Cloud Storage bucket name." + }, + "creationTimestamp": { + "type": "string", + "description": "[Output Only] Creation timestamp in RFC3339 text format." + }, + "description": { + "type": "string", + "description": "An optional textual description of the resource; provided by the client when the resource is created." + }, + "enableCdn": { + "type": "boolean", + "description": "If true, enable Cloud CDN for this BackendBucket." + }, + "id": { + "type": "string", + "description": "[Output Only] Unique identifier for the resource; defined by the server.", + "format": "uint64" + }, + "kind": { + "type": "string", + "description": "Type of the resource.", + "default": "compute#backendBucket" + }, + "name": { + "type": "string", + "description": "Name of the resource. Provided by the client when the resource is created. The name must be 1-63 characters long, and comply with RFC1035. Specifically, the name must be 1-63 characters long and match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash.", + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?" + }, + "selfLink": { + "type": "string", + "description": "[Output Only] Server-defined URL for the resource." + } + } + }, + "BackendBucketList": { + "id": "BackendBucketList", + "type": "object", + "description": "Contains a list of BackendBucket resources.", + "properties": { + "id": { + "type": "string", + "description": "[Output Only] Unique identifier for the resource; defined by the server." + }, + "items": { + "type": "array", + "description": "A list of BackendBucket resources.", + "items": { + "$ref": "BackendBucket" + } + }, + "kind": { + "type": "string", + "description": "Type of resource.", + "default": "compute#backendBucketList" + }, + "nextPageToken": { + "type": "string", + "description": "[Output Only] A token used to continue a truncated list request." + }, + "selfLink": { + "type": "string", + "description": "[Output Only] Server-defined URL for this resource." + } + } + }, "BackendService": { "id": "BackendService", "type": "object", @@ -811,6 +883,10 @@ "$ref": "Backend" } }, + "cdnPolicy": { + "$ref": "BackendServiceCdnPolicy", + "description": "Cloud CDN configuration for this BackendService." + }, "connectionDraining": { "$ref": "ConnectionDraining" }, @@ -877,7 +953,7 @@ }, "protocol": { "type": "string", - "description": "The protocol this BackendService uses to communicate with backends.\n\nPossible values are HTTP, HTTPS, HTTP2, TCP and SSL. The default is HTTP.\n\nFor internal load balancing, the possible values are TCP and UDP, and the default is TCP.", + "description": "The protocol this BackendService uses to communicate with backends.\n\nPossible values are HTTP, HTTPS, TCP, and SSL. The default is HTTP.\n\nFor internal load balancing, the possible values are TCP and UDP, and the default is TCP.", "enum": [ "HTTP", "HTTPS", @@ -958,6 +1034,17 @@ } } }, + "BackendServiceCdnPolicy": { + "id": "BackendServiceCdnPolicy", + "type": "object", + "description": "Message containing Cloud CDN configuration for a backend service.", + "properties": { + "cacheKeyPolicy": { + "$ref": "CacheKeyPolicy", + "description": "The CacheKeyPolicy for this CdnPolicy." + } + } + }, "BackendServiceGroupHealth": { "id": "BackendServiceGroupHealth", "type": "object", @@ -998,7 +1085,7 @@ }, "nextPageToken": { "type": "string", - "description": "[Output Only] A token used to continue a truncated list request." + "description": "[Output Only] This token allows you to get the next page of results for list requests. If the number of results is larger than maxResults, use the nextPageToken as a value for the query parameter pageToken in the next list request. Subsequent list requests will have their own nextPageToken to continue paging through the results." }, "selfLink": { "type": "string", @@ -1038,6 +1125,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -1058,6 +1146,7 @@ "", "", "", + "", "" ] }, @@ -1099,6 +1188,39 @@ } } }, + "CacheKeyPolicy": { + "id": "CacheKeyPolicy", + "type": "object", + "description": "Message containing what to include in the cache key for a request for Cloud CDN.", + "properties": { + "includeHost": { + "type": "boolean", + "description": "If true, requests to different hosts will be cached separately." + }, + "includeProtocol": { + "type": "boolean", + "description": "If true, http and https requests will be cached separately." + }, + "includeQueryString": { + "type": "boolean", + "description": "If true, include query string parameters in the cache key according to query_string_whitelist and query_string_blacklist. If neither is set, the entire query string will be included. If false, the query string will be excluded from the cache key entirely." + }, + "queryStringBlacklist": { + "type": "array", + "description": "Names of query string parameters to exclude in cache keys. All other parameters will be included. Either specify query_string_whitelist or query_string_blacklist, not both. '&' and '=' will be percent encoded and not treated as delimiters.", + "items": { + "type": "string" + } + }, + "queryStringWhitelist": { + "type": "array", + "description": "Names of query string parameters to include in cache keys. All other parameters will be excluded. Either specify query_string_whitelist or query_string_blacklist, not both. '&' and '=' will be percent encoded and not treated as delimiters.", + "items": { + "type": "string" + } + } + } + }, "ConnectionDraining": { "id": "ConnectionDraining", "type": "object", @@ -1322,7 +1444,7 @@ }, "nextPageToken": { "type": "string", - "description": "[Output Only] This token allows you to get the next page of results for list requests. If the number of results is larger than maxResults, use the nextPageToken as a value for the query parameter pageToken in the next list request. Subsequent list requests will have their own nextPageToken to continue paging through the results." + "description": "[Output Only] This token allows you to get the next page of results for list requests. If the number of results is larger than maxResults, use the nextPageToken as a value for the query parameter pageToken in the next list request. Subsequent list requests will have their own nextPageToken to continue paging through the results. Acceptable values are 0 to 500, inclusive. (Default: 500)" }, "selfLink": { "type": "string", @@ -1337,11 +1459,11 @@ "properties": { "id": { "type": "string", - "description": "[Output Only] The unique identifier for the resource. This identifier is defined by the server." + "description": "[Output Only] Unique identifier for the resource; defined by the server." }, "items": { "type": "array", - "description": "[Output Only] A list of persistent disks.", + "description": "A list of Disk resources.", "items": { "$ref": "Disk" } @@ -1353,7 +1475,7 @@ }, "nextPageToken": { "type": "string", - "description": "[Output Only] This token allows you to get the next page of results for list requests. If the number of results is larger than maxResults, use the nextPageToken as a value for the query parameter pageToken in the next list request. Subsequent list requests will have their own nextPageToken to continue paging through the results." + "description": "This token allows you to get the next page of results for list requests. If the number of results is larger than maxResults, use the nextPageToken as a value for the query parameter pageToken in the next list request. Subsequent list requests will have their own nextPageToken to continue paging through the results." }, "selfLink": { "type": "string", @@ -1520,6 +1642,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -1540,6 +1663,7 @@ "", "", "", + "", "" ] }, @@ -1611,6 +1735,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -1631,6 +1756,7 @@ "", "", "", + "", "" ] }, @@ -1723,7 +1849,7 @@ }, "sourceRanges": { "type": "array", - "description": "If source ranges are specified, the firewall will apply only to traffic that has source IP address in these ranges. These ranges must be expressed in CIDR format. One or both of sourceRanges and sourceTags may be set. If both properties are set, the firewall will apply to traffic that has source IP address within sourceRanges OR the source IP that belongs to a tag listed in the sourceTags property. The connection does not need to match both properties for the firewall to apply.", + "description": "If source ranges are specified, the firewall will apply only to traffic that has source IP address in these ranges. These ranges must be expressed in CIDR format. One or both of sourceRanges and sourceTags may be set. If both properties are set, the firewall will apply to traffic that has source IP address within sourceRanges OR the source IP that belongs to a tag listed in the sourceTags property. The connection does not need to match both properties for the firewall to apply. Only IPv4 is supported.", "items": { "type": "string" } @@ -1782,14 +1908,15 @@ "properties": { "IPAddress": { "type": "string", - "description": "The IP address that this forwarding rule is serving on behalf of.\n\nFor global forwarding rules, the address must be a global IP; for regional forwarding rules, the address must live in the same region as the forwarding rule. By default, this field is empty and an ephemeral IP from the same scope (global or regional) will be assigned.\n\nWhen the load balancing scheme is INTERNAL, this can only be an RFC 1918 IP address belonging to the network/subnetwork configured for the forwarding rule. A reserved address cannot be used. If the field is empty, the IP address will be automatically allocated from the internal IP range of the subnetwork or network configured for this forwarding rule." + "description": "The IP address that this forwarding rule is serving on behalf of.\n\nFor global forwarding rules, the address must be a global IP. For regional forwarding rules, the address must live in the same region as the forwarding rule. By default, this field is empty and an ephemeral IP from the same scope (global or regional) will be assigned.\n\nWhen the load balancing scheme is INTERNAL, this can only be an RFC 1918 IP address belonging to the network/subnetwork configured for the forwarding rule. A reserved address cannot be used. If the field is empty, the IP address will be automatically allocated from the internal IP range of the subnetwork or network configured for this forwarding rule. Only IPv4 is supported." }, "IPProtocol": { "type": "string", - "description": "The IP protocol to which this rule applies. Valid options are TCP, UDP, ESP, AH, SCTP or ICMP.\n\nWhen the load balancing scheme is INTERNAL\u003c/code, only TCP and UDP are valid.", + "description": "The IP protocol to which this rule applies. Valid options are TCP, UDP, ESP, AH, SCTP or ICMP.\n\nWhen the load balancing scheme is INTERNAL, only TCP and UDP are valid.", "enum": [ "AH", "ESP", + "ICMP", "SCTP", "TCP", "UDP" @@ -1799,6 +1926,7 @@ "", "", "", + "", "" ] }, @@ -1826,7 +1954,7 @@ }, "loadBalancingScheme": { "type": "string", - "description": "This signifies what the ForwardingRule will be used for and can only take the following values: INTERNAL EXTERNAL The value of INTERNAL means that this will be used for Internal Network Load Balancing (TCP, UDP). The value of EXTERNAL means that this will be used for External Load Balancing (HTTP(S) LB, External TCP/UDP LB, SSL Proxy)", + "description": "This signifies what the ForwardingRule will be used for and can only take the following values: INTERNAL, EXTERNAL The value of INTERNAL means that this will be used for Internal Network Load Balancing (TCP, UDP). The value of EXTERNAL means that this will be used for External Load Balancing (HTTP(S) LB, External TCP/UDP LB, SSL Proxy)", "enum": [ "EXTERNAL", "INTERNAL", @@ -1853,7 +1981,7 @@ }, "ports": { "type": "array", - "description": "This field is not used for external load balancing.\n\nWhen the load balancing scheme is INTERNAL, a single port or a comma separated list of ports can be configured. Only packets addressed to these ports will be forwarded to the backends configured with this forwarding rule. If the port list is not provided then all ports are allowed to pass through.\n\nYou may specify a maximum of up to 5 ports.", + "description": "This field is not used for external load balancing.\n\nWhen the load balancing scheme is INTERNAL, a single port or a comma separated list of ports can be configured. Only packets addressed to these ports will be forwarded to the backends configured with this forwarding rule.\n\nYou may specify a maximum of up to 5 ports.", "items": { "type": "string" } @@ -1872,7 +2000,7 @@ }, "target": { "type": "string", - "description": "The URL of the target resource to receive the matched traffic. For regional forwarding rules, this target must live in the same region as the forwarding rule. For global forwarding rules, this target must be a global TargetHttpProxy or TargetHttpsProxy resource. The forwarded traffic must be of a type appropriate to the target object. For example, TargetHttpProxy requires HTTP traffic, and TargetHttpsProxy requires HTTPS traffic.\n\nThis field is not used for internal load balancing." + "description": "The URL of the target resource to receive the matched traffic. For regional forwarding rules, this target must live in the same region as the forwarding rule. For global forwarding rules, this target must be a global load balancing resource. The forwarded traffic must be of a type appropriate to the target object.\n\nThis field is not used for internal load balancing." } } }, @@ -1970,6 +2098,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -1990,6 +2119,7 @@ "", "", "", + "", "" ] }, @@ -2049,7 +2179,7 @@ }, "port": { "type": "integer", - "description": "The TCP port number for the health check request. The default value is 80.", + "description": "The TCP port number for the health check request. The default value is 80. Valid values are 1 through 65535.", "format": "int32" }, "portName": { @@ -2084,7 +2214,7 @@ }, "port": { "type": "integer", - "description": "The TCP port number for the health check request. The default value is 443.", + "description": "The TCP port number for the health check request. The default value is 443. Valid values are 1 through 65535.", "format": "int32" }, "portName": { @@ -2709,7 +2839,13 @@ }, "name": { "type": "string", - "description": "The name of the resource, provided by the client when initially creating the resource. The resource name must be 1-63 characters long, and comply with RFC1035. Specifically, the name must be 1-63 characters long and match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash." + "description": "The name of the resource, provided by the client when initially creating the resource. The resource name must be 1-63 characters long, and comply with RFC1035. Specifically, the name must be 1-63 characters long and match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash.", + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "annotations": { + "required": [ + "compute.instances.insert" + ] + } }, "networkInterfaces": { "type": "array", @@ -2720,7 +2856,7 @@ }, "scheduling": { "$ref": "Scheduling", - "description": "Scheduling options for this instance." + "description": "Sets the scheduling options for this instance." }, "selfLink": { "type": "string", @@ -2728,7 +2864,7 @@ }, "serviceAccounts": { "type": "array", - "description": "A list of service accounts, with their specified scopes, authorized for this instance. Service accounts generate access tokens that can be accessed through the metadata server and used to authenticate applications on the instance. See Service Accounts for more information.", + "description": "A list of service accounts, with their specified scopes, authorized for this instance. Only one service account per VM instance is supported.\n\nService accounts generate access tokens that can be accessed through the metadata server and used to authenticate applications on the instance. See Service Accounts for more information.", "items": { "$ref": "ServiceAccount" } @@ -3225,6 +3361,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -3245,6 +3382,7 @@ "", "", "", + "", "" ] }, @@ -3407,6 +3545,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -3427,6 +3566,7 @@ "", "", "", + "", "" ] }, @@ -3732,6 +3872,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -3752,6 +3893,7 @@ "", "", "", + "", "" ] }, @@ -3790,6 +3932,23 @@ } } }, + "InstancesSetServiceAccountRequest": { + "id": "InstancesSetServiceAccountRequest", + "type": "object", + "properties": { + "email": { + "type": "string", + "description": "Email address of the service account." + }, + "scopes": { + "type": "array", + "description": "The list of scopes to be made available for this service account.", + "items": { + "type": "string" + } + } + } + }, "InstancesStartWithEncryptionKeyRequest": { "id": "InstancesStartWithEncryptionKeyRequest", "type": "object", @@ -4012,6 +4171,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -4032,6 +4192,7 @@ "", "", "", + "", "" ] }, @@ -4295,18 +4456,18 @@ "$ref": "AccessConfig" } }, + "kind": { + "type": "string", + "description": "[Output Only] Type of the resource. Always compute#networkInterface for network interfaces.", + "default": "compute#networkInterface" + }, "name": { "type": "string", "description": "[Output Only] The name of the network interface, generated by the server. For network devices, these are eth0, eth1, etc." }, "network": { "type": "string", - "description": "URL of the network resource for this instance. This is required for creating an instance but optional when creating a firewall rule. If not specified when creating a firewall rule, the default network is used:\n\nglobal/networks/default \n\nIf you specify this property, you can specify the network as a full or partial URL. For example, the following are all valid URLs: \n- https://www.googleapis.com/compute/v1/projects/project/global/networks/network \n- projects/project/global/networks/network \n- global/networks/default", - "annotations": { - "required": [ - "compute.instances.insert" - ] - } + "description": "URL of the network resource for this instance. When creating an instance, if neither the network nor the subnetwork is specified, the default network global/networks/default is used; if the network is not specified but the subnetwork is specified, the network is inferred.\n\nThis field is optional when creating a firewall rule. If not specified when creating a firewall rule, the default network global/networks/default is used.\n\nIf you specify this property, you can specify the network as a full or partial URL. For example, the following are all valid URLs: \n- https://www.googleapis.com/compute/v1/projects/project/global/networks/network \n- projects/project/global/networks/network \n- global/networks/default" }, "networkIP": { "type": "string", @@ -4360,7 +4521,7 @@ }, "creationTimestamp": { "type": "string", - "description": "[Output Only] Creation timestamp in RFC3339 text format." + "description": "[Deprecated] This field is deprecated." }, "description": { "type": "string", @@ -4499,6 +4660,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -4519,6 +4681,7 @@ "", "", "", + "", "" ] }, @@ -4646,6 +4809,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -4666,6 +4830,7 @@ "", "", "", + "", "" ] }, @@ -4812,8 +4977,10 @@ "description": "[Output Only] Name of the quota metric.", "enum": [ "AUTOSCALERS", + "BACKEND_BUCKETS", "BACKEND_SERVICES", "CPUS", + "CPUS_ALL_REGIONS", "DISKS_TOTAL_GB", "FIREWALLS", "FORWARDING_RULES", @@ -4842,7 +5009,6 @@ "TARGET_POOLS", "TARGET_SSL_PROXIES", "TARGET_VPN_GATEWAYS", - "TOTAL_CPUS", "URL_MAPS", "VPN_TUNNELS" ], @@ -4880,6 +5046,7 @@ "", "", "", + "", "" ] }, @@ -5052,7 +5219,7 @@ "properties": { "instances": { "type": "array", - "description": "The names of one or more instances to abandon.", + "description": "The URLs of one or more instances to abandon. This can be a full URL or a partial URL, such as zones/[ZONE]/instances/[INSTANCE_NAME].", "items": { "type": "string" } @@ -5065,7 +5232,7 @@ "properties": { "instances": { "type": "array", - "description": "The names of one or more instances to delete.", + "description": "The URLs of one or more instances to delete. This can be a full URL or a partial URL, such as zones/[ZONE]/instances/[INSTANCE_NAME].", "items": { "type": "string" } @@ -5241,7 +5408,7 @@ "Route": { "id": "Route", "type": "object", - "description": "Represents a Route resource. A route specifies how certain packets should be handled by the network. Routes are associated with instances by tags and the set of routes for a particular instance is called its routing table.\n\nFor each packet leaving a instance, the system searches that instance's routing table for a single best matching route. Routes match packets by destination IP address, preferring smaller or more specific ranges over larger ones. If there is a tie, the system selects the route with the smallest priority value. If there is still a tie, it uses the layer three and four packet headers to select just one of the remaining matching routes. The packet is then forwarded as specified by the nextHop field of the winning route - either to another instance destination, a instance gateway or a Google Compute Engine-operated gateway.\n\nPackets that do not match any route in the sending instance's routing table are dropped.", + "description": "Represents a Route resource. A route specifies how certain packets should be handled by the network. Routes are associated with instances by tags and the set of routes for a particular instance is called its routing table.\n\nFor each packet leaving a instance, the system searches that instance's routing table for a single best matching route. Routes match packets by destination IP address, preferring smaller or more specific ranges over larger ones. If there is a tie, the system selects the route with the smallest priority value. If there is still a tie, it uses the layer three and four packet headers to select just one of the remaining matching routes. The packet is then forwarded as specified by the nextHop field of the winning route - either to another instance destination, an instance gateway, or a Google Compute Engine-operated gateway.\n\nPackets that do not match any route in the sending instance's routing table are dropped.", "properties": { "creationTimestamp": { "type": "string", @@ -5253,7 +5420,7 @@ }, "destRange": { "type": "string", - "description": "The destination range of outgoing packets that this route applies to.", + "description": "The destination range of outgoing packets that this route applies to. Only IPv4 is supported.", "annotations": { "required": [ "compute.routes.insert" @@ -5299,7 +5466,7 @@ }, "nextHopIp": { "type": "string", - "description": "The network IP address of an instance that should handle matching packets." + "description": "The network IP address of an instance that should handle matching packets. Only IPv4 is supported." }, "nextHopNetwork": { "type": "string", @@ -5358,6 +5525,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -5378,6 +5546,7 @@ "", "", "", + "", "" ] }, @@ -5566,7 +5735,7 @@ }, "ipAddress": { "type": "string", - "description": "IP address of the interface inside Google Cloud Platform." + "description": "IP address of the interface inside Google Cloud Platform. Only IPv4 is supported." }, "name": { "type": "string", @@ -5580,7 +5749,7 @@ }, "peerIpAddress": { "type": "string", - "description": "IP address of the BGP interface outside Google cloud." + "description": "IP address of the BGP interface outside Google cloud. Only IPv4 is supported." } } }, @@ -5594,7 +5763,7 @@ }, "linkedVpnTunnel": { "type": "string", - "description": "URI of linked VPN tunnel. It must be in the same region as the router. Each interface can have at most one linked resource." + "description": "URI of the linked VPN tunnel. It must be in the same region as the router. Each interface can have at most one linked resource and it could either be a VPN Tunnel or an interconnect attachment." }, "name": { "type": "string", @@ -5773,6 +5942,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -5793,6 +5963,7 @@ "", "", "", + "", "" ] }, @@ -5827,7 +5998,7 @@ "properties": { "port": { "type": "integer", - "description": "The TCP port number for the health check request. The default value is 443.", + "description": "The TCP port number for the health check request. The default value is 443. Valid values are 1 through 65535.", "format": "int32" }, "portName": { @@ -5863,7 +6034,7 @@ "properties": { "automaticRestart": { "type": "boolean", - "description": "Specifies whether the instance should be automatically restarted if it is terminated by Compute Engine (not terminated by a user). You can only set the automatic restart option for standard instances. Preemptible instances cannot be automatically restarted." + "description": "Specifies whether the instance should be automatically restarted if it is terminated by Compute Engine (not terminated by a user). You can only set the automatic restart option for standard instances. Preemptible instances cannot be automatically restarted.\n\nBy default, this is set to true so an instance is automatically restarted if it is terminated by Compute Engine." }, "onHostMaintenance": { "type": "string", @@ -5879,7 +6050,7 @@ }, "preemptible": { "type": "boolean", - "description": "Whether the instance is preemptible." + "description": "Defines whether the instance is preemptible. This can only be set during instance creation, it cannot be set or changed after the instance has been created." } } }, @@ -5908,7 +6079,7 @@ }, "start": { "type": "string", - "description": "[Output Only] The starting byte position of the output that was returned. This should match the start parameter sent with the request. If the serial console output exceeds the size of the buffer, older output will be overwritten by newer content and the start values will be mismatched.", + "description": "The starting byte position of the output that was returned. This should match the start parameter sent with the request. If the serial console output exceeds the size of the buffer, older output will be overwritten by newer content and the start values will be mismatched.", "format": "int64" } } @@ -6156,7 +6327,7 @@ }, "ipCidrRange": { "type": "string", - "description": "The range of internal addresses that are owned by this subnetwork. Provide this property when you create the subnetwork. For example, 10.0.0.0/8 or 192.168.0.0/16. Ranges must be unique and non-overlapping within a network." + "description": "The range of internal addresses that are owned by this subnetwork. Provide this property when you create the subnetwork. For example, 10.0.0.0/8 or 192.168.0.0/16. Ranges must be unique and non-overlapping within a network. Only IPv4 is supported." }, "kind": { "type": "string", @@ -6172,6 +6343,10 @@ "type": "string", "description": "The URL of the network to which this subnetwork belongs, provided by the client when initially creating the subnetwork. Only networks that are in the distributed mode can have subnetworks." }, + "privateIpGoogleAccess": { + "type": "boolean", + "description": "Whether the VMs in this subnet can access Google services without assigned external IP addresses." + }, "region": { "type": "string", "description": "URL of the region where the Subnetwork resides." @@ -6286,6 +6461,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -6306,6 +6482,7 @@ "", "", "", + "", "" ] }, @@ -6334,13 +6511,22 @@ } } }, + "SubnetworksSetPrivateIpGoogleAccessRequest": { + "id": "SubnetworksSetPrivateIpGoogleAccessRequest", + "type": "object", + "properties": { + "privateIpGoogleAccess": { + "type": "boolean" + } + } + }, "TCPHealthCheck": { "id": "TCPHealthCheck", "type": "object", "properties": { "port": { "type": "integer", - "description": "The TCP port number for the health check request. The default value is 80.", + "description": "The TCP port number for the health check request. The default value is 80. Valid values are 1 through 65535.", "format": "int32" }, "portName": { @@ -6692,6 +6878,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -6712,6 +6899,7 @@ "", "", "", + "", "" ] }, @@ -6743,7 +6931,7 @@ "TargetPool": { "id": "TargetPool", "type": "object", - "description": "A TargetPool resource. This resource defines a pool of instances, associated HttpHealthCheck resources, and the fallback target pool.", + "description": "A TargetPool resource. This resource defines a pool of instances, an associated HttpHealthCheck resource, and the fallback target pool.", "properties": { "backupPool": { "type": "string", @@ -6764,7 +6952,7 @@ }, "healthChecks": { "type": "array", - "description": "A list of URLs to the HttpHealthCheck resource. A member instance in this pool is considered healthy if and only if all specified health checks pass. An empty list means all member instances will be considered healthy at all times.", + "description": "The URL of the HttpHealthCheck resource. A member instance in this pool is considered healthy if and only if the health checks pass. An empty list means all member instances will be considered healthy at all times. Only HttpHealthChecks are supported. Only one health check may be specified.", "items": { "type": "string" } @@ -6904,7 +7092,7 @@ "properties": { "healthChecks": { "type": "array", - "description": "A list of HttpHealthCheck resources to add to the target pool.", + "description": "The HttpHealthCheck to add to the target pool.", "items": { "$ref": "HealthCheckReference" } @@ -6982,6 +7170,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -7002,6 +7191,7 @@ "", "", "", + "", "" ] }, @@ -7344,6 +7534,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -7364,6 +7555,7 @@ "", "", "", + "", "" ] }, @@ -7631,7 +7823,7 @@ }, "localTrafficSelector": { "type": "array", - "description": "Local traffic selector to use when establishing the VPN tunnel with peer VPN gateway. The value should be a CIDR formatted string, for example: 192.168.0.0/16. The ranges should be disjoint.", + "description": "Local traffic selector to use when establishing the VPN tunnel with peer VPN gateway. The value should be a CIDR formatted string, for example: 192.168.0.0/16. The ranges should be disjoint. Only IPv4 is supported.", "items": { "type": "string" } @@ -7648,7 +7840,7 @@ }, "peerIp": { "type": "string", - "description": "IP address of the peer VPN gateway." + "description": "IP address of the peer VPN gateway. Only IPv4 is supported." }, "region": { "type": "string", @@ -7656,7 +7848,7 @@ }, "remoteTrafficSelector": { "type": "array", - "description": "Remote traffic selectors to use when establishing the VPN tunnel with peer VPN gateway. The value should be a CIDR formatted string, for example: 192.168.0.0/16. The ranges should be disjoint.", + "description": "Remote traffic selectors to use when establishing the VPN tunnel with peer VPN gateway. The value should be a CIDR formatted string, for example: 192.168.0.0/16. The ranges should be disjoint. Only IPv4 is supported.", "items": { "type": "string" } @@ -7814,6 +8006,7 @@ "NOT_CRITICAL_ERROR", "NO_RESULTS_ON_PAGE", "REQUIRED_TOS_AGREEMENT", + "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING", "RESOURCE_NOT_DELETED", "SINGLE_INSTANCE_PROPERTY_TEMPLATE", "UNREACHABLE" @@ -7834,6 +8027,7 @@ "", "", "", + "", "" ] }, @@ -7963,11 +8157,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -8132,11 +8325,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -8194,11 +8386,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -8363,11 +8554,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -8416,8 +8606,7 @@ "parameters": { "autoscaler": { "type": "string", - "description": "Name of the autoscaler to update.", - "required": true, + "description": "Name of the autoscaler to patch.", "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "query" }, @@ -8438,8 +8627,7 @@ }, "parameterOrder": [ "project", - "zone", - "autoscaler" + "zone" ], "request": { "$ref": "Autoscaler" @@ -8449,7 +8637,8 @@ }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/compute" + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly" ] }, "update": { @@ -8496,6 +8685,227 @@ } } }, + "backendBuckets": { + "methods": { + "delete": { + "id": "compute.backendBuckets.delete", + "path": "{project}/global/backendBuckets/{backendBucket}", + "httpMethod": "DELETE", + "description": "Deletes the specified BackendBucket resource.", + "parameters": { + "backendBucket": { + "type": "string", + "description": "Name of the BackendBucket resource to delete.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + }, + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + } + }, + "parameterOrder": [ + "project", + "backendBucket" + ], + "response": { + "$ref": "Operation" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute" + ] + }, + "get": { + "id": "compute.backendBuckets.get", + "path": "{project}/global/backendBuckets/{backendBucket}", + "httpMethod": "GET", + "description": "Returns the specified BackendBucket resource. Get a list of available backend buckets by making a list() request.", + "parameters": { + "backendBucket": { + "type": "string", + "description": "Name of the BackendBucket resource to return.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + }, + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + } + }, + "parameterOrder": [ + "project", + "backendBucket" + ], + "response": { + "$ref": "BackendBucket" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly" + ] + }, + "insert": { + "id": "compute.backendBuckets.insert", + "path": "{project}/global/backendBuckets", + "httpMethod": "POST", + "description": "Creates a BackendBucket resource in the specified project using the data included in the request.", + "parameters": { + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + } + }, + "parameterOrder": [ + "project" + ], + "request": { + "$ref": "BackendBucket" + }, + "response": { + "$ref": "Operation" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute" + ] + }, + "list": { + "id": "compute.backendBuckets.list", + "path": "{project}/global/backendBuckets", + "httpMethod": "GET", + "description": "Retrieves the list of BackendBucket resources available to the specified project.", + "parameters": { + "filter": { + "type": "string", + "description": "Sets a filter expression for filtering listed resources, in the form filter={expression}. Your {expression} must be in the format: field_name comparison_string literal_string.\n\nThe field_name is the name of the field you want to compare. Only atomic field types are supported (string, number, boolean). The comparison_string must be either eq (equals) or ne (not equals). The literal_string is the string value to filter to. The literal value must be valid for the type of field you are filtering by (string, number, boolean). For string fields, the literal value is interpreted as a regular expression using RE2 syntax. The literal value must match the entire field.\n\nFor example, to filter for instances that do not have a name of example-instance, you would use filter=name ne example-instance.\n\nYou can filter on nested fields. For example, you could filter on instances that have set the scheduling.automaticRestart field to true. Use filtering on nested fields to take advantage of labels to organize and search for results based on label values.\n\nTo filter on multiple expressions, provide each separate expression within parentheses. For example, (scheduling.automaticRestart eq true) (zone eq us-central1-f). Multiple expressions are treated as AND expressions, meaning that resources must match all expressions to pass the filters.", + "location": "query" + }, + "maxResults": { + "type": "integer", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", + "default": "500", + "format": "uint32", + "minimum": "0", + "location": "query" + }, + "orderBy": { + "type": "string", + "description": "Sorts list results by a certain order. By default, results are returned in alphanumerical order based on the resource name.\n\nYou can also sort results in descending order based on the creation timestamp using orderBy=\"creationTimestamp desc\". This sorts results based on the creationTimestamp field in reverse chronological order (newest result first). Use this to sort resources like operations so that the newest operation is returned first.\n\nCurrently, only sorting by name or creationTimestamp desc is supported.", + "location": "query" + }, + "pageToken": { + "type": "string", + "description": "Specifies a page token to use. Set pageToken to the nextPageToken returned by a previous list request to get the next page of results.", + "location": "query" + }, + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + } + }, + "parameterOrder": [ + "project" + ], + "response": { + "$ref": "BackendBucketList" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly" + ] + }, + "patch": { + "id": "compute.backendBuckets.patch", + "path": "{project}/global/backendBuckets/{backendBucket}", + "httpMethod": "PATCH", + "description": "Updates the specified BackendBucket resource with the data included in the request. This method supports patch semantics.", + "parameters": { + "backendBucket": { + "type": "string", + "description": "Name of the BackendBucket resource to patch.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + }, + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + } + }, + "parameterOrder": [ + "project", + "backendBucket" + ], + "request": { + "$ref": "BackendBucket" + }, + "response": { + "$ref": "Operation" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly" + ] + }, + "update": { + "id": "compute.backendBuckets.update", + "path": "{project}/global/backendBuckets/{backendBucket}", + "httpMethod": "PUT", + "description": "Updates the specified BackendBucket resource with the data included in the request.", + "parameters": { + "backendBucket": { + "type": "string", + "description": "Name of the BackendBucket resource to update.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + }, + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + } + }, + "parameterOrder": [ + "project", + "backendBucket" + ], + "request": { + "$ref": "BackendBucket" + }, + "response": { + "$ref": "Operation" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute" + ] + } + } + }, "backendServices": { "methods": { "aggregatedList": { @@ -8511,11 +8921,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -8692,11 +9101,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -8733,11 +9141,11 @@ "id": "compute.backendServices.patch", "path": "{project}/global/backendServices/{backendService}", "httpMethod": "PATCH", - "description": "Updates the specified BackendService resource with the data included in the request. There are several restrictions and guidelines to keep in mind when updating a backend service. Read Restrictions and Guidelines for more information. This method supports patch semantics.", + "description": "Patches the specified BackendService resource with the data included in the request. There are several restrictions and guidelines to keep in mind when updating a backend service. Read Restrictions and Guidelines for more information. This method supports patch semantics.", "parameters": { "backendService": { "type": "string", - "description": "Name of the BackendService resource to update.", + "description": "Name of the BackendService resource to patch.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -8762,7 +9170,8 @@ }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/compute" + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly" ] }, "update": { @@ -8818,11 +9227,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -8910,11 +9318,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -8972,11 +9379,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -9022,6 +9428,10 @@ "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" }, + "guestFlush": { + "type": "boolean", + "location": "query" + }, "project": { "type": "string", "description": "Project ID for this request.", @@ -9189,11 +9599,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -9390,11 +9799,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -9431,7 +9839,7 @@ "id": "compute.firewalls.patch", "path": "{project}/global/firewalls/{firewall}", "httpMethod": "PATCH", - "description": "Updates the specified firewall rule with the data included in the request. This method supports patch semantics.", + "description": "Updates the specified firewall rule with the data included in the request. Using PUT method, can only update following fields of firewall rule: allowed, description, sourceRanges, sourceTags, targetTags. This method supports patch semantics.", "parameters": { "firewall": { "type": "string", @@ -9467,7 +9875,7 @@ "id": "compute.firewalls.update", "path": "{project}/global/firewalls/{firewall}", "httpMethod": "PUT", - "description": "Updates the specified firewall rule with the data included in the request.", + "description": "Updates the specified firewall rule with the data included in the request. Using PUT method, can only update following fields of firewall rule: allowed, description, sourceRanges, sourceTags, targetTags.", "parameters": { "firewall": { "type": "string", @@ -9516,11 +9924,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -9685,11 +10092,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -9886,11 +10292,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -10035,11 +10440,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -10125,11 +10529,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -10239,11 +10642,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -10388,11 +10790,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -10433,7 +10834,7 @@ "parameters": { "healthCheck": { "type": "string", - "description": "Name of the HealthCheck resource to update.", + "description": "Name of the HealthCheck resource to patch.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -10458,7 +10859,8 @@ }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/compute" + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly" ] }, "update": { @@ -10609,11 +11011,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -10654,7 +11055,7 @@ "parameters": { "httpHealthCheck": { "type": "string", - "description": "Name of the HttpHealthCheck resource to update.", + "description": "Name of the HttpHealthCheck resource to patch.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -10679,7 +11080,8 @@ }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/compute" + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly" ] }, "update": { @@ -10830,11 +11232,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -10875,7 +11276,7 @@ "parameters": { "httpsHealthCheck": { "type": "string", - "description": "Name of the HttpsHealthCheck resource to update.", + "description": "Name of the HttpsHealthCheck resource to patch.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -10900,7 +11301,8 @@ }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/compute" + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly" ] }, "update": { @@ -11124,11 +11526,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -11169,7 +11570,7 @@ "id": "compute.instanceGroupManagers.abandonInstances", "path": "{project}/zones/{zone}/instanceGroupManagers/{instanceGroupManager}/abandonInstances", "httpMethod": "POST", - "description": "Schedules a group action to remove the specified instances from the managed instance group. Abandoning an instance does not delete the instance, but it does remove the instance from any target pools that are applied by the managed instance group. This method reduces the targetSize of the managed instance group by the number of instances that you abandon. This operation is marked as DONE when the action is scheduled even if the instances have not yet been removed from the group. You must separately verify the status of the abandoning action with the listmanagedinstances method.", + "description": "Schedules a group action to remove the specified instances from the managed instance group. Abandoning an instance does not delete the instance, but it does remove the instance from any target pools that are applied by the managed instance group. This method reduces the targetSize of the managed instance group by the number of instances that you abandon. This operation is marked as DONE when the action is scheduled even if the instances have not yet been removed from the group. You must separately verify the status of the abandoning action with the listmanagedinstances method.\n\nYou can specify a maximum of 1000 instances with this method per request.", "parameters": { "instanceGroupManager": { "type": "string", @@ -11220,11 +11621,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -11300,7 +11700,7 @@ "id": "compute.instanceGroupManagers.deleteInstances", "path": "{project}/zones/{zone}/instanceGroupManagers/{instanceGroupManager}/deleteInstances", "httpMethod": "POST", - "description": "Schedules a group action to delete the specified instances in the managed instance group. The instances are also removed from any target pools of which they were a member. This method reduces the targetSize of the managed instance group by the number of instances that you delete. This operation is marked as DONE when the action is scheduled even if the instances are still being deleted. You must separately verify the status of the deleting action with the listmanagedinstances method.", + "description": "Schedules a group action to delete the specified instances in the managed instance group. The instances are also removed from any target pools of which they were a member. This method reduces the targetSize of the managed instance group by the number of instances that you delete. This operation is marked as DONE when the action is scheduled even if the instances are still being deleted. You must separately verify the status of the deleting action with the listmanagedinstances method.\n\nYou can specify a maximum of 1000 instances with this method per request.", "parameters": { "instanceGroupManager": { "type": "string", @@ -11382,7 +11782,7 @@ "id": "compute.instanceGroupManagers.insert", "path": "{project}/zones/{zone}/instanceGroupManagers", "httpMethod": "POST", - "description": "Creates a managed instance group using the information that you specify in the request. After the group is created, it schedules an action to create instances in the group using the specified instance template. This operation is marked as DONE when the group is created even if the instances in the group have not yet been created. You must separately verify the status of the individual instances with the listmanagedinstances method.", + "description": "Creates a managed instance group using the information that you specify in the request. After the group is created, it schedules an action to create instances in the group using the specified instance template. This operation is marked as DONE when the group is created even if the instances in the group have not yet been created. You must separately verify the status of the individual instances with the listmanagedinstances method.\n\nA managed instance group can have up to 1000 VM instances per group.", "parameters": { "project": { "type": "string", @@ -11426,11 +11826,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -11491,7 +11890,6 @@ "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "order_by": { @@ -11534,7 +11932,7 @@ "id": "compute.instanceGroupManagers.recreateInstances", "path": "{project}/zones/{zone}/instanceGroupManagers/{instanceGroupManager}/recreateInstances", "httpMethod": "POST", - "description": "Schedules a group action to recreate the specified instances in the managed instance group. The instances are deleted and recreated using the current instance template for the managed instance group. This operation is marked as DONE when the action is scheduled even if the instances have not yet been recreated. You must separately verify the status of the recreating action with the listmanagedinstances method.", + "description": "Schedules a group action to recreate the specified instances in the managed instance group. The instances are deleted and recreated using the current instance template for the managed instance group. This operation is marked as DONE when the action is scheduled even if the instances have not yet been recreated. You must separately verify the status of the recreating action with the listmanagedinstances method.\n\nYou can specify a maximum of 1000 instances with this method per request.", "parameters": { "instanceGroupManager": { "type": "string", @@ -11762,11 +12160,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -11926,11 +12323,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -11989,11 +12385,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -12233,11 +12628,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -12338,11 +12732,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -12636,7 +13029,7 @@ }, "start": { "type": "string", - "description": "For the initial request, leave this field unspecified. For subsequent calls, this field should be set to the next value that was returned in the previous call.", + "description": "Returns output starting from a specific byte position. Use this to page through output when the output is too large to return in a single request. For the initial request, leave this field unspecified. For subsequent calls, this field should be set to the next value returned in the previous call.", "format": "int64", "location": "query" }, @@ -12711,11 +13104,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -12760,7 +13152,7 @@ "id": "compute.instances.reset", "path": "{project}/zones/{zone}/instances/{instance}/reset", "httpMethod": "POST", - "description": "Performs a hard reset on the instance.", + "description": "Performs a reset on the instance. For more information, see Resetting an instance.", "parameters": { "instance": { "type": "string", @@ -12985,6 +13377,50 @@ "https://www.googleapis.com/auth/compute" ] }, + "setServiceAccount": { + "id": "compute.instances.setServiceAccount", + "path": "{project}/zones/{zone}/instances/{instance}/setServiceAccount", + "httpMethod": "POST", + "description": "Sets the service account on the instance. For more information, read Changing the service account and access scopes for an instance.", + "parameters": { + "instance": { + "type": "string", + "description": "Name of the instance resource to start.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + }, + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + }, + "zone": { + "type": "string", + "description": "The name of the zone for this request.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + } + }, + "parameterOrder": [ + "project", + "zone", + "instance" + ], + "request": { + "$ref": "InstancesSetServiceAccountRequest" + }, + "response": { + "$ref": "Operation" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute" + ] + }, "setTags": { "id": "compute.instances.setTags", "path": "{project}/zones/{zone}/instances/{instance}/setTags", @@ -13210,11 +13646,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -13302,11 +13737,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -13459,11 +13893,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -13810,11 +14243,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -13863,8 +14295,7 @@ "parameters": { "autoscaler": { "type": "string", - "description": "Name of the autoscaler to update.", - "required": true, + "description": "Name of the autoscaler to patch.", "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "query" }, @@ -13885,8 +14316,7 @@ }, "parameterOrder": [ "project", - "region", - "autoscaler" + "region" ], "request": { "$ref": "Autoscaler" @@ -13896,7 +14326,8 @@ }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/compute" + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly" ] }, "update": { @@ -14121,11 +14552,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -14174,7 +14604,7 @@ "parameters": { "backendService": { "type": "string", - "description": "Name of the BackendService resource to update.", + "description": "Name of the BackendService resource to patch.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -14207,7 +14637,8 @@ }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/compute" + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly" ] }, "update": { @@ -14262,7 +14693,7 @@ "id": "compute.regionInstanceGroupManagers.abandonInstances", "path": "{project}/regions/{region}/instanceGroupManagers/{instanceGroupManager}/abandonInstances", "httpMethod": "POST", - "description": "Schedules a group action to remove the specified instances from the managed instance group. Abandoning an instance does not delete the instance, but it does remove the instance from any target pools that are applied by the managed instance group. This method reduces the targetSize of the managed instance group by the number of instances that you abandon. This operation is marked as DONE when the action is scheduled even if the instances have not yet been removed from the group. You must separately verify the status of the abandoning action with the listmanagedinstances method.", + "description": "Schedules a group action to remove the specified instances from the managed instance group. Abandoning an instance does not delete the instance, but it does remove the instance from any target pools that are applied by the managed instance group. This method reduces the targetSize of the managed instance group by the number of instances that you abandon. This operation is marked as DONE when the action is scheduled even if the instances have not yet been removed from the group. You must separately verify the status of the abandoning action with the listmanagedinstances method.\n\nYou can specify a maximum of 1000 instances with this method per request.", "parameters": { "instanceGroupManager": { "type": "string", @@ -14343,7 +14774,7 @@ "id": "compute.regionInstanceGroupManagers.deleteInstances", "path": "{project}/regions/{region}/instanceGroupManagers/{instanceGroupManager}/deleteInstances", "httpMethod": "POST", - "description": "Schedules a group action to delete the specified instances in the managed instance group. The instances are also removed from any target pools of which they were a member. This method reduces the targetSize of the managed instance group by the number of instances that you delete. This operation is marked as DONE when the action is scheduled even if the instances are still being deleted. You must separately verify the status of the deleting action with the listmanagedinstances method.", + "description": "Schedules a group action to delete the specified instances in the managed instance group. The instances are also removed from any target pools of which they were a member. This method reduces the targetSize of the managed instance group by the number of instances that you delete. This operation is marked as DONE when the action is scheduled even if the instances are still being deleted. You must separately verify the status of the deleting action with the listmanagedinstances method.\n\nYou can specify a maximum of 1000 instances with this method per request.", "parameters": { "instanceGroupManager": { "type": "string", @@ -14425,7 +14856,7 @@ "id": "compute.regionInstanceGroupManagers.insert", "path": "{project}/regions/{region}/instanceGroupManagers", "httpMethod": "POST", - "description": "Creates a managed instance group using the information that you specify in the request. After the group is created, it schedules an action to create instances in the group using the specified instance template. This operation is marked as DONE when the group is created even if the instances in the group have not yet been created. You must separately verify the status of the individual instances with the listmanagedinstances method.", + "description": "Creates a managed instance group using the information that you specify in the request. After the group is created, it schedules an action to create instances in the group using the specified instance template. This operation is marked as DONE when the group is created even if the instances in the group have not yet been created. You must separately verify the status of the individual instances with the listmanagedinstances method.\n\nA regional managed instance group can contain up to 2000 instances.", "parameters": { "project": { "type": "string", @@ -14469,11 +14900,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -14534,7 +14964,6 @@ "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "order_by": { @@ -14577,7 +15006,7 @@ "id": "compute.regionInstanceGroupManagers.recreateInstances", "path": "{project}/regions/{region}/instanceGroupManagers/{instanceGroupManager}/recreateInstances", "httpMethod": "POST", - "description": "Schedules a group action to recreate the specified instances in the managed instance group. The instances are deleted and recreated using the current instance template for the managed instance group. This operation is marked as DONE when the action is scheduled even if the instances have not yet been recreated. You must separately verify the status of the recreating action with the listmanagedinstances method.", + "description": "Schedules a group action to recreate the specified instances in the managed instance group. The instances are deleted and recreated using the current instance template for the managed instance group. This operation is marked as DONE when the action is scheduled even if the instances have not yet been recreated. You must separately verify the status of the recreating action with the listmanagedinstances method.\n\nYou can specify a maximum of 1000 instances with this method per request.", "parameters": { "instanceGroupManager": { "type": "string", @@ -14804,11 +15233,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -14867,11 +15295,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -15054,11 +15481,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -15150,11 +15576,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -15204,11 +15629,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -15415,11 +15839,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -15464,7 +15887,7 @@ "id": "compute.routers.patch", "path": "{project}/regions/{region}/routers/{router}", "httpMethod": "PATCH", - "description": "Updates the specified Router resource with the data included in the request. This method supports patch semantics.", + "description": "Patches the specified Router resource with the data included in the request. This method supports patch semantics.", "parameters": { "project": { "type": "string", @@ -15482,7 +15905,7 @@ }, "router": { "type": "string", - "description": "Name of the Router resource to update.", + "description": "Name of the Router resource to patch.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -15501,7 +15924,8 @@ }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/compute" + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly" ] }, "preview": { @@ -15705,11 +16129,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -15826,11 +16249,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -15975,11 +16397,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -16029,11 +16450,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -16242,11 +16662,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -16286,6 +16705,50 @@ "https://www.googleapis.com/auth/compute", "https://www.googleapis.com/auth/compute.readonly" ] + }, + "setPrivateIpGoogleAccess": { + "id": "compute.subnetworks.setPrivateIpGoogleAccess", + "path": "{project}/regions/{region}/subnetworks/{subnetwork}/setPrivateIpGoogleAccess", + "httpMethod": "POST", + "description": "Set whether VMs in this subnet can access Google services without assigning external IP addresses through Cloudpath.", + "parameters": { + "project": { + "type": "string", + "description": "Project ID for this request.", + "required": true, + "pattern": "(?:(?:[-a-z0-9]{1,63}\\.)*(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?):)?(?:[0-9]{1,19}|(?:[a-z0-9](?:[-a-z0-9]{0,61}[a-z0-9])?))", + "location": "path" + }, + "region": { + "type": "string", + "description": "Name of the region scoping this request.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + }, + "subnetwork": { + "type": "string", + "description": "Name of the Subnetwork resource.", + "required": true, + "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", + "location": "path" + } + }, + "parameterOrder": [ + "project", + "region", + "subnetwork" + ], + "request": { + "$ref": "SubnetworksSetPrivateIpGoogleAccessRequest" + }, + "response": { + "$ref": "Operation" + }, + "scopes": [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/compute" + ] } } }, @@ -16399,11 +16862,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -16584,11 +17046,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -16710,11 +17171,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -16879,11 +17339,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -17029,11 +17488,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -17243,11 +17701,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -17538,11 +17995,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -17700,11 +18156,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -17869,11 +18324,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -18062,11 +18516,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -18103,7 +18556,7 @@ "id": "compute.urlMaps.patch", "path": "{project}/global/urlMaps/{urlMap}", "httpMethod": "PATCH", - "description": "Updates the specified UrlMap resource with the data included in the request. This method supports patch semantics.", + "description": "Patches the specified UrlMap resource with the data included in the request. This method supports patch semantics.", "parameters": { "project": { "type": "string", @@ -18114,7 +18567,7 @@ }, "urlMap": { "type": "string", - "description": "Name of the UrlMap resource to update.", + "description": "Name of the UrlMap resource to patch.", "required": true, "pattern": "[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?", "location": "path" @@ -18132,7 +18585,8 @@ }, "scopes": [ "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/compute" + "https://www.googleapis.com/auth/compute", + "https://www.googleapis.com/auth/compute.readonly" ] }, "update": { @@ -18224,11 +18678,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -18393,11 +18846,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -18535,11 +18987,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { @@ -18631,11 +19082,10 @@ }, "maxResults": { "type": "integer", - "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests.", + "description": "The maximum number of results per page that should be returned. If the number of available results is larger than maxResults, Compute Engine returns a nextPageToken that can be used to get the next page of results in subsequent list requests. Acceptable values are 0 to 500, inclusive. (Default: 500)", "default": "500", "format": "uint32", "minimum": "0", - "maximum": "500", "location": "query" }, "orderBy": { diff --git a/vendor/google.golang.org/api/compute/v1/compute-gen.go b/vendor/google.golang.org/api/compute/v1/compute-gen.go index 424e855e23b0..a2242d45ac08 100644 --- a/vendor/google.golang.org/api/compute/v1/compute-gen.go +++ b/vendor/google.golang.org/api/compute/v1/compute-gen.go @@ -73,6 +73,7 @@ func New(client *http.Client) (*Service, error) { s := &Service{client: client, BasePath: basePath} s.Addresses = NewAddressesService(s) s.Autoscalers = NewAutoscalersService(s) + s.BackendBuckets = NewBackendBucketsService(s) s.BackendServices = NewBackendServicesService(s) s.DiskTypes = NewDiskTypesService(s) s.Disks = NewDisksService(s) @@ -126,6 +127,8 @@ type Service struct { Autoscalers *AutoscalersService + BackendBuckets *BackendBucketsService + BackendServices *BackendServicesService DiskTypes *DiskTypesService @@ -234,6 +237,15 @@ type AutoscalersService struct { s *Service } +func NewBackendBucketsService(s *Service) *BackendBucketsService { + rs := &BackendBucketsService{s: s} + return rs +} + +type BackendBucketsService struct { + s *Service +} + func NewBackendServicesService(s *Service) *BackendServicesService { rs := &BackendServicesService{s: s} return rs @@ -610,7 +622,9 @@ type AccessConfig struct { // for access configs. Kind string `json:"kind,omitempty"` - // Name: Name of this access configuration. + // Name: The name of this access configuration. The default and + // recommended name is External NAT but you can use any arbitrary string + // you would like. For example, My external IP or Network Access. Name string `json:"name,omitempty"` // NatIP: An external IP address associated with this instance. Specify @@ -881,6 +895,7 @@ type AddressesScopedListWarning struct { // "NOT_CRITICAL_ERROR" // "NO_RESULTS_ON_PAGE" // "REQUIRED_TOS_AGREEMENT" + // "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING" // "RESOURCE_NOT_DELETED" // "SINGLE_INSTANCE_PROPERTY_TEMPLATE" // "UNREACHABLE" @@ -1419,6 +1434,7 @@ type AutoscalersScopedListWarning struct { // "NOT_CRITICAL_ERROR" // "NO_RESULTS_ON_PAGE" // "REQUIRED_TOS_AGREEMENT" + // "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING" // "RESOURCE_NOT_DELETED" // "SINGLE_INSTANCE_PROPERTY_TEMPLATE" // "UNREACHABLE" @@ -1858,6 +1874,115 @@ func (s *Backend) UnmarshalJSON(data []byte) error { return nil } +// BackendBucket: A BackendBucket resource. This resource defines a +// Cloud Storage bucket. +type BackendBucket struct { + // BucketName: Cloud Storage bucket name. + BucketName string `json:"bucketName,omitempty"` + + // CreationTimestamp: [Output Only] Creation timestamp in RFC3339 text + // format. + CreationTimestamp string `json:"creationTimestamp,omitempty"` + + // Description: An optional textual description of the resource; + // provided by the client when the resource is created. + Description string `json:"description,omitempty"` + + // EnableCdn: If true, enable Cloud CDN for this BackendBucket. + EnableCdn bool `json:"enableCdn,omitempty"` + + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. + Id uint64 `json:"id,omitempty,string"` + + // Kind: Type of the resource. + Kind string `json:"kind,omitempty"` + + // Name: Name of the resource. Provided by the client when the resource + // is created. The name must be 1-63 characters long, and comply with + // RFC1035. Specifically, the name must be 1-63 characters long and + // match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means + // the first character must be a lowercase letter, and all following + // characters must be a dash, lowercase letter, or digit, except the + // last character, which cannot be a dash. + Name string `json:"name,omitempty"` + + // SelfLink: [Output Only] Server-defined URL for the resource. + SelfLink string `json:"selfLink,omitempty"` + + // ServerResponse contains the HTTP response code and headers from the + // server. + googleapi.ServerResponse `json:"-"` + + // ForceSendFields is a list of field names (e.g. "BucketName") to + // unconditionally include in API requests. By default, fields with + // empty values are omitted from API requests. However, any non-pointer, + // non-interface field appearing in ForceSendFields will be sent to the + // server regardless of whether the field is empty or not. This may be + // used to include empty fields in Patch requests. + ForceSendFields []string `json:"-"` + + // NullFields is a list of field names (e.g. "BucketName") to include in + // API requests with the JSON null value. By default, fields with empty + // values are omitted from API requests. However, any field with an + // empty value appearing in NullFields will be sent to the server as + // null. It is an error if a field in this list has a non-empty value. + // This may be used to include null fields in Patch requests. + NullFields []string `json:"-"` +} + +func (s *BackendBucket) MarshalJSON() ([]byte, error) { + type noMethod BackendBucket + raw := noMethod(*s) + return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) +} + +// BackendBucketList: Contains a list of BackendBucket resources. +type BackendBucketList struct { + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. + Id string `json:"id,omitempty"` + + // Items: A list of BackendBucket resources. + Items []*BackendBucket `json:"items,omitempty"` + + // Kind: Type of resource. + Kind string `json:"kind,omitempty"` + + // NextPageToken: [Output Only] A token used to continue a truncated + // list request. + NextPageToken string `json:"nextPageToken,omitempty"` + + // SelfLink: [Output Only] Server-defined URL for this resource. + SelfLink string `json:"selfLink,omitempty"` + + // ServerResponse contains the HTTP response code and headers from the + // server. + googleapi.ServerResponse `json:"-"` + + // ForceSendFields is a list of field names (e.g. "Id") to + // unconditionally include in API requests. By default, fields with + // empty values are omitted from API requests. However, any non-pointer, + // non-interface field appearing in ForceSendFields will be sent to the + // server regardless of whether the field is empty or not. This may be + // used to include empty fields in Patch requests. + ForceSendFields []string `json:"-"` + + // NullFields is a list of field names (e.g. "Id") to include in API + // requests with the JSON null value. By default, fields with empty + // values are omitted from API requests. However, any field with an + // empty value appearing in NullFields will be sent to the server as + // null. It is an error if a field in this list has a non-empty value. + // This may be used to include null fields in Patch requests. + NullFields []string `json:"-"` +} + +func (s *BackendBucketList) MarshalJSON() ([]byte, error) { + type noMethod BackendBucketList + raw := noMethod(*s) + return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) +} + // BackendService: A BackendService resource. This resource defines a // group of backend virtual machines and their serving capacity. type BackendService struct { @@ -1872,6 +1997,9 @@ type BackendService struct { // Backends: The list of backends that serve this BackendService. Backends []*Backend `json:"backends,omitempty"` + // CdnPolicy: Cloud CDN configuration for this BackendService. + CdnPolicy *BackendServiceCdnPolicy `json:"cdnPolicy,omitempty"` + ConnectionDraining *ConnectionDraining `json:"connectionDraining,omitempty"` // CreationTimestamp: [Output Only] Creation timestamp in RFC3339 text @@ -1941,7 +2069,7 @@ type BackendService struct { // Protocol: The protocol this BackendService uses to communicate with // backends. // - // Possible values are HTTP, HTTPS, HTTP2, TCP and SSL. The default is + // Possible values are HTTP, HTTPS, TCP, and SSL. The default is // HTTP. // // For internal load balancing, the possible values are TCP and UDP, and @@ -2062,6 +2190,36 @@ func (s *BackendServiceAggregatedList) MarshalJSON() ([]byte, error) { return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) } +// BackendServiceCdnPolicy: Message containing Cloud CDN configuration +// for a backend service. +type BackendServiceCdnPolicy struct { + // CacheKeyPolicy: The CacheKeyPolicy for this CdnPolicy. + CacheKeyPolicy *CacheKeyPolicy `json:"cacheKeyPolicy,omitempty"` + + // ForceSendFields is a list of field names (e.g. "CacheKeyPolicy") to + // unconditionally include in API requests. By default, fields with + // empty values are omitted from API requests. However, any non-pointer, + // non-interface field appearing in ForceSendFields will be sent to the + // server regardless of whether the field is empty or not. This may be + // used to include empty fields in Patch requests. + ForceSendFields []string `json:"-"` + + // NullFields is a list of field names (e.g. "CacheKeyPolicy") to + // include in API requests with the JSON null value. By default, fields + // with empty values are omitted from API requests. However, any field + // with an empty value appearing in NullFields will be sent to the + // server as null. It is an error if a field in this list has a + // non-empty value. This may be used to include null fields in Patch + // requests. + NullFields []string `json:"-"` +} + +func (s *BackendServiceCdnPolicy) MarshalJSON() ([]byte, error) { + type noMethod BackendServiceCdnPolicy + raw := noMethod(*s) + return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) +} + type BackendServiceGroupHealth struct { HealthStatus []*HealthStatus `json:"healthStatus,omitempty"` @@ -2109,8 +2267,12 @@ type BackendServiceList struct { // compute#backendServiceList for lists of backend services. Kind string `json:"kind,omitempty"` - // NextPageToken: [Output Only] A token used to continue a truncated - // list request. + // NextPageToken: [Output Only] This token allows you to get the next + // page of results for list requests. If the number of results is larger + // than maxResults, use the nextPageToken as a value for the query + // parameter pageToken in the next list request. Subsequent list + // requests will have their own nextPageToken to continue paging through + // the results. NextPageToken string `json:"nextPageToken,omitempty"` // SelfLink: [Output Only] Server-defined URL for this resource. @@ -2196,6 +2358,7 @@ type BackendServicesScopedListWarning struct { // "NOT_CRITICAL_ERROR" // "NO_RESULTS_ON_PAGE" // "REQUIRED_TOS_AGREEMENT" + // "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING" // "RESOURCE_NOT_DELETED" // "SINGLE_INSTANCE_PROPERTY_TEMPLATE" // "UNREACHABLE" @@ -2300,6 +2463,59 @@ func (s *CacheInvalidationRule) MarshalJSON() ([]byte, error) { return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) } +// CacheKeyPolicy: Message containing what to include in the cache key +// for a request for Cloud CDN. +type CacheKeyPolicy struct { + // IncludeHost: If true, requests to different hosts will be cached + // separately. + IncludeHost bool `json:"includeHost,omitempty"` + + // IncludeProtocol: If true, http and https requests will be cached + // separately. + IncludeProtocol bool `json:"includeProtocol,omitempty"` + + // IncludeQueryString: If true, include query string parameters in the + // cache key according to query_string_whitelist and + // query_string_blacklist. If neither is set, the entire query string + // will be included. If false, the query string will be excluded from + // the cache key entirely. + IncludeQueryString bool `json:"includeQueryString,omitempty"` + + // QueryStringBlacklist: Names of query string parameters to exclude in + // cache keys. All other parameters will be included. Either specify + // query_string_whitelist or query_string_blacklist, not both. '&' and + // '=' will be percent encoded and not treated as delimiters. + QueryStringBlacklist []string `json:"queryStringBlacklist,omitempty"` + + // QueryStringWhitelist: Names of query string parameters to include in + // cache keys. All other parameters will be excluded. Either specify + // query_string_whitelist or query_string_blacklist, not both. '&' and + // '=' will be percent encoded and not treated as delimiters. + QueryStringWhitelist []string `json:"queryStringWhitelist,omitempty"` + + // ForceSendFields is a list of field names (e.g. "IncludeHost") to + // unconditionally include in API requests. By default, fields with + // empty values are omitted from API requests. However, any non-pointer, + // non-interface field appearing in ForceSendFields will be sent to the + // server regardless of whether the field is empty or not. This may be + // used to include empty fields in Patch requests. + ForceSendFields []string `json:"-"` + + // NullFields is a list of field names (e.g. "IncludeHost") to include + // in API requests with the JSON null value. By default, fields with + // empty values are omitted from API requests. However, any field with + // an empty value appearing in NullFields will be sent to the server as + // null. It is an error if a field in this list has a non-empty value. + // This may be used to include null fields in Patch requests. + NullFields []string `json:"-"` +} + +func (s *CacheKeyPolicy) MarshalJSON() ([]byte, error) { + type noMethod CacheKeyPolicy + raw := noMethod(*s) + return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) +} + // ConnectionDraining: Message containing connection draining // configuration. type ConnectionDraining struct { @@ -2658,7 +2874,8 @@ type DiskAggregatedList struct { // than maxResults, use the nextPageToken as a value for the query // parameter pageToken in the next list request. Subsequent list // requests will have their own nextPageToken to continue paging through - // the results. + // the results. Acceptable values are 0 to 500, inclusive. (Default: + // 500) NextPageToken string `json:"nextPageToken,omitempty"` // SelfLink: [Output Only] Server-defined URL for this resource. @@ -2693,174 +2910,173 @@ func (s *DiskAggregatedList) MarshalJSON() ([]byte, error) { // DiskList: A list of Disk resources. type DiskList struct { - // Id: [Output Only] The unique identifier for the resource. This - // identifier is defined by the server. + // Id: [Output Only] Unique identifier for the resource; defined by the + // server. Id string `json:"id,omitempty"` - // Items: [Output Only] A list of persistent disks. + // Items: A list of Disk resources. Items []*Disk `json:"items,omitempty"` // Kind: [Output Only] Type of resource. Always compute#diskList for // lists of disks. - Kind string `json:"kind,omitempty"` - - // NextPageToken: [Output Only] This token allows you to get the next - // page of results for list requests. If the number of results is larger - // than maxResults, use the nextPageToken as a value for the query - // parameter pageToken in the next list request. Subsequent list - // requests will have their own nextPageToken to continue paging through - // the results. - NextPageToken string `json:"nextPageToken,omitempty"` - - // SelfLink: [Output Only] Server-defined URL for this resource. - SelfLink string `json:"selfLink,omitempty"` - - // ServerResponse contains the HTTP response code and headers from the - // server. - googleapi.ServerResponse `json:"-"` - - // ForceSendFields is a list of field names (e.g. "Id") to - // unconditionally include in API requests. By default, fields with - // empty values are omitted from API requests. However, any non-pointer, - // non-interface field appearing in ForceSendFields will be sent to the - // server regardless of whether the field is empty or not. This may be - // used to include empty fields in Patch requests. - ForceSendFields []string `json:"-"` - - // NullFields is a list of field names (e.g. "Id") to include in API - // requests with the JSON null value. By default, fields with empty - // values are omitted from API requests. However, any field with an - // empty value appearing in NullFields will be sent to the server as - // null. It is an error if a field in this list has a non-empty value. - // This may be used to include null fields in Patch requests. - NullFields []string `json:"-"` -} - -func (s *DiskList) MarshalJSON() ([]byte, error) { - type noMethod DiskList - raw := noMethod(*s) - return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) -} - -type DiskMoveRequest struct { - // DestinationZone: The URL of the destination zone to move the disk. - // This can be a full or partial URL. For example, the following are all - // valid URLs to a zone: - // - https://www.googleapis.com/compute/v1/projects/project/zones/zone - // - // - projects/project/zones/zone - // - zones/zone - DestinationZone string `json:"destinationZone,omitempty"` - - // TargetDisk: The URL of the target disk to move. This can be a full or - // partial URL. For example, the following are all valid URLs to a disk: - // - // - - // https://www.googleapis.com/compute/v1/projects/project/zones/zone/disks/disk - // - projects/project/zones/zone/disks/disk - // - zones/zone/disks/disk - TargetDisk string `json:"targetDisk,omitempty"` - - // ForceSendFields is a list of field names (e.g. "DestinationZone") to - // unconditionally include in API requests. By default, fields with - // empty values are omitted from API requests. However, any non-pointer, - // non-interface field appearing in ForceSendFields will be sent to the - // server regardless of whether the field is empty or not. This may be - // used to include empty fields in Patch requests. - ForceSendFields []string `json:"-"` - - // NullFields is a list of field names (e.g. "DestinationZone") to - // include in API requests with the JSON null value. By default, fields - // with empty values are omitted from API requests. However, any field - // with an empty value appearing in NullFields will be sent to the - // server as null. It is an error if a field in this list has a - // non-empty value. This may be used to include null fields in Patch - // requests. - NullFields []string `json:"-"` -} - -func (s *DiskMoveRequest) MarshalJSON() ([]byte, error) { - type noMethod DiskMoveRequest - raw := noMethod(*s) - return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) -} - -// DiskType: A DiskType resource. -type DiskType struct { - // CreationTimestamp: [Output Only] Creation timestamp in RFC3339 text - // format. - CreationTimestamp string `json:"creationTimestamp,omitempty"` - - // DefaultDiskSizeGb: [Output Only] Server-defined default disk size in - // GB. - DefaultDiskSizeGb int64 `json:"defaultDiskSizeGb,omitempty,string"` - - // Deprecated: [Output Only] The deprecation status associated with this - // disk type. - Deprecated *DeprecationStatus `json:"deprecated,omitempty"` - - // Description: [Output Only] An optional description of this resource. - Description string `json:"description,omitempty"` - - // Id: [Output Only] The unique identifier for the resource. This - // identifier is defined by the server. - Id uint64 `json:"id,omitempty,string"` - - // Kind: [Output Only] Type of the resource. Always compute#diskType for - // disk types. - Kind string `json:"kind,omitempty"` - - // Name: [Output Only] Name of the resource. - Name string `json:"name,omitempty"` - - // SelfLink: [Output Only] Server-defined URL for the resource. - SelfLink string `json:"selfLink,omitempty"` - - // ValidDiskSize: [Output Only] An optional textual description of the - // valid disk size, such as "10GB-10TB". - ValidDiskSize string `json:"validDiskSize,omitempty"` - - // Zone: [Output Only] URL of the zone where the disk type resides. - Zone string `json:"zone,omitempty"` - - // ServerResponse contains the HTTP response code and headers from the - // server. - googleapi.ServerResponse `json:"-"` - - // ForceSendFields is a list of field names (e.g. "CreationTimestamp") - // to unconditionally include in API requests. By default, fields with - // empty values are omitted from API requests. However, any non-pointer, - // non-interface field appearing in ForceSendFields will be sent to the - // server regardless of whether the field is empty or not. This may be - // used to include empty fields in Patch requests. - ForceSendFields []string `json:"-"` - - // NullFields is a list of field names (e.g. "CreationTimestamp") to - // include in API requests with the JSON null value. By default, fields - // with empty values are omitted from API requests. However, any field - // with an empty value appearing in NullFields will be sent to the - // server as null. It is an error if a field in this list has a - // non-empty value. This may be used to include null fields in Patch - // requests. - NullFields []string `json:"-"` -} - -func (s *DiskType) MarshalJSON() ([]byte, error) { - type noMethod DiskType - raw := noMethod(*s) - return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) -} - -type DiskTypeAggregatedList struct { - // Id: [Output Only] The unique identifier for the resource. This - // identifier is defined by the server. - Id string `json:"id,omitempty"` - - // Items: [Output Only] A map of scoped disk type lists. - Items map[string]DiskTypesScopedList `json:"items,omitempty"` - - // Kind: [Output Only] Type of resource. Always - // compute#diskTypeAggregatedList. + Kind string `json:"kind,omitempty"` + + // NextPageToken: This token allows you to get the next page of results + // for list requests. If the number of results is larger than + // maxResults, use the nextPageToken as a value for the query parameter + // pageToken in the next list request. Subsequent list requests will + // have their own nextPageToken to continue paging through the results. + NextPageToken string `json:"nextPageToken,omitempty"` + + // SelfLink: [Output Only] Server-defined URL for this resource. + SelfLink string `json:"selfLink,omitempty"` + + // ServerResponse contains the HTTP response code and headers from the + // server. + googleapi.ServerResponse `json:"-"` + + // ForceSendFields is a list of field names (e.g. "Id") to + // unconditionally include in API requests. By default, fields with + // empty values are omitted from API requests. However, any non-pointer, + // non-interface field appearing in ForceSendFields will be sent to the + // server regardless of whether the field is empty or not. This may be + // used to include empty fields in Patch requests. + ForceSendFields []string `json:"-"` + + // NullFields is a list of field names (e.g. "Id") to include in API + // requests with the JSON null value. By default, fields with empty + // values are omitted from API requests. However, any field with an + // empty value appearing in NullFields will be sent to the server as + // null. It is an error if a field in this list has a non-empty value. + // This may be used to include null fields in Patch requests. + NullFields []string `json:"-"` +} + +func (s *DiskList) MarshalJSON() ([]byte, error) { + type noMethod DiskList + raw := noMethod(*s) + return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) +} + +type DiskMoveRequest struct { + // DestinationZone: The URL of the destination zone to move the disk. + // This can be a full or partial URL. For example, the following are all + // valid URLs to a zone: + // - https://www.googleapis.com/compute/v1/projects/project/zones/zone + // + // - projects/project/zones/zone + // - zones/zone + DestinationZone string `json:"destinationZone,omitempty"` + + // TargetDisk: The URL of the target disk to move. This can be a full or + // partial URL. For example, the following are all valid URLs to a disk: + // + // - + // https://www.googleapis.com/compute/v1/projects/project/zones/zone/disks/disk + // - projects/project/zones/zone/disks/disk + // - zones/zone/disks/disk + TargetDisk string `json:"targetDisk,omitempty"` + + // ForceSendFields is a list of field names (e.g. "DestinationZone") to + // unconditionally include in API requests. By default, fields with + // empty values are omitted from API requests. However, any non-pointer, + // non-interface field appearing in ForceSendFields will be sent to the + // server regardless of whether the field is empty or not. This may be + // used to include empty fields in Patch requests. + ForceSendFields []string `json:"-"` + + // NullFields is a list of field names (e.g. "DestinationZone") to + // include in API requests with the JSON null value. By default, fields + // with empty values are omitted from API requests. However, any field + // with an empty value appearing in NullFields will be sent to the + // server as null. It is an error if a field in this list has a + // non-empty value. This may be used to include null fields in Patch + // requests. + NullFields []string `json:"-"` +} + +func (s *DiskMoveRequest) MarshalJSON() ([]byte, error) { + type noMethod DiskMoveRequest + raw := noMethod(*s) + return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) +} + +// DiskType: A DiskType resource. +type DiskType struct { + // CreationTimestamp: [Output Only] Creation timestamp in RFC3339 text + // format. + CreationTimestamp string `json:"creationTimestamp,omitempty"` + + // DefaultDiskSizeGb: [Output Only] Server-defined default disk size in + // GB. + DefaultDiskSizeGb int64 `json:"defaultDiskSizeGb,omitempty,string"` + + // Deprecated: [Output Only] The deprecation status associated with this + // disk type. + Deprecated *DeprecationStatus `json:"deprecated,omitempty"` + + // Description: [Output Only] An optional description of this resource. + Description string `json:"description,omitempty"` + + // Id: [Output Only] The unique identifier for the resource. This + // identifier is defined by the server. + Id uint64 `json:"id,omitempty,string"` + + // Kind: [Output Only] Type of the resource. Always compute#diskType for + // disk types. + Kind string `json:"kind,omitempty"` + + // Name: [Output Only] Name of the resource. + Name string `json:"name,omitempty"` + + // SelfLink: [Output Only] Server-defined URL for the resource. + SelfLink string `json:"selfLink,omitempty"` + + // ValidDiskSize: [Output Only] An optional textual description of the + // valid disk size, such as "10GB-10TB". + ValidDiskSize string `json:"validDiskSize,omitempty"` + + // Zone: [Output Only] URL of the zone where the disk type resides. + Zone string `json:"zone,omitempty"` + + // ServerResponse contains the HTTP response code and headers from the + // server. + googleapi.ServerResponse `json:"-"` + + // ForceSendFields is a list of field names (e.g. "CreationTimestamp") + // to unconditionally include in API requests. By default, fields with + // empty values are omitted from API requests. However, any non-pointer, + // non-interface field appearing in ForceSendFields will be sent to the + // server regardless of whether the field is empty or not. This may be + // used to include empty fields in Patch requests. + ForceSendFields []string `json:"-"` + + // NullFields is a list of field names (e.g. "CreationTimestamp") to + // include in API requests with the JSON null value. By default, fields + // with empty values are omitted from API requests. However, any field + // with an empty value appearing in NullFields will be sent to the + // server as null. It is an error if a field in this list has a + // non-empty value. This may be used to include null fields in Patch + // requests. + NullFields []string `json:"-"` +} + +func (s *DiskType) MarshalJSON() ([]byte, error) { + type noMethod DiskType + raw := noMethod(*s) + return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields) +} + +type DiskTypeAggregatedList struct { + // Id: [Output Only] The unique identifier for the resource. This + // identifier is defined by the server. + Id string `json:"id,omitempty"` + + // Items: [Output Only] A map of scoped disk type lists. + Items map[string]DiskTypesScopedList `json:"items,omitempty"` + + // Kind: [Output Only] Type of resource. Always + // compute#diskTypeAggregatedList. Kind string `json:"kind,omitempty"` // NextPageToken: [Output Only] This token allows you to get the next @@ -3004,6 +3220,7 @@ type DiskTypesScopedListWarning struct { // "NOT_CRITICAL_ERROR" // "NO_RESULTS_ON_PAGE" // "REQUIRED_TOS_AGREEMENT" + // "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING" // "RESOURCE_NOT_DELETED" // "SINGLE_INSTANCE_PROPERTY_TEMPLATE" // "UNREACHABLE" @@ -3158,6 +3375,7 @@ type DisksScopedListWarning struct { // "NOT_CRITICAL_ERROR" // "NO_RESULTS_ON_PAGE" // "REQUIRED_TOS_AGREEMENT" + // "RESOURCE_IN_USE_BY_OTHER_RESOURCE_WARNING" // "RESOURCE_NOT_DELETED" // "SINGLE_INSTANCE_PROPERTY_TEMPLATE" // "UNREACHABLE" @@ -3287,7 +3505,7 @@ type Firewall struct { // will apply to traffic that has source IP address within sourceRanges // OR the source IP that belongs to a tag listed in the sourceTags // property. The connection does not need to match both properties for - // the firewall to apply. + // the firewall to apply. Only IPv4 is supported. SourceRanges []string `json:"sourceRanges,omitempty"` // SourceTags: If source tags are specified, the firewall will apply @@ -3430,7 +3648,7 @@ type ForwardingRule struct { // IPAddress: The IP address that this forwarding rule is serving on // behalf of. // - // For global forwarding rules, the address must be a global IP; for + // For global forwarding rules, the address must be a global IP. For // regional forwarding rules, the address must live in the same region // as the forwarding rule. By default, this field is empty and an // ephemeral IP from the same scope (global or regional) will be @@ -3441,18 +3659,19 @@ type ForwardingRule struct { // the forwarding rule. A reserved address cannot be used. If the field // is empty, the IP address will be automatically allocated from the // internal IP range of the subnetwork or network configured for this - // forwarding rule. + // forwarding rule. Only IPv4 is supported. IPAddress string `json:"IPAddress,omitempty"` // IPProtocol: The IP protocol to which this rule applies. Valid options // are TCP, UDP, ESP, AH, SCTP or ICMP. // - // When the load balancing scheme is INTERNALQwX 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 z z^EcrCd1ABe6lPU5K}DR?XYld3Sr z2pma9(ZtGxVPw8Vy*%}UP9XwDdtpMM6+rK8^%Gta(!u1;HS7DUWavBY&yd!4R!Nw* zsb>4@K4+cp0n`U_?<>__>PfDdW`Wg2M#h1m!aG^A$8y(`by_loqu~l!&c^U+vPmB) zhdMa52R?HYY=l>o=Hl`U2!Xxd>^VS#Dadbi5>fa<=~PyG}T*c ztt?FwaBvmSbpLoGka{5APIE-li>vXCgU^PTN-h|ltru|pU`pWS%?lCm4Beq*%~?a@ z@?%(v?1w=Q9_7Ux9$T8kHWOoCs>ozRN@NaI@BeDJ0Qh>6Kq0rT_;Alk22Qo5cZW0E z9C8Qc;)I}Aol6^PYb|XC)nv-&Q5iSmE(uVYPe$s^`%}ldc+}>y(1`fP;qybR{2E4M z`Q58&+z9k$l4Y;VpLM^yVDrSk_7A02!%>o#Fdj+n@Ya0Wlij6HJKE%&@;7%YEGhDY z4b*0ahsD?SyE;FpFKVerx|JJhHQ9bwUN!_f_RDRfY~Yuf$kH~>H!~5~s_kj}6w~lA zI#sX0{%%|ew$h~h?R?f;4x4U8o9-pOf@wH+0mU^HN{UyZnqc zioQqUUqa5vf+WVHhn~eu0$=wxDNJj*H2l^s5wQ_4ugW;B9;Hau+)ZIf`d=a$XA?Lb zv&l;5@TRf8_JO*rvH9jYx90$w#}v$Jub(R$+T{sqj)EEug2r6(39Wo{Lh^gMa%6Ck zMDjgvMJ;1pa-7>6FvvKMxYqA=c^8Hh-uz9;c3ucfLHVP&ilpp{rgDc-_oufM?koP= zDp7>9=^s!R8$s`jhjqWH+>Z%F($Gr3A=BcnlmTqx=^?PC<7^Zeg zj-h1cjLcWsa!Mj3FGiBejE=x1m2Rcd@mT@!$?)M+J?22$URexda@bQ{DLN{S@gHp_ zx?y!%-V>M%?PC{K?}YTKS2>UkfT}R1cD<^|W|>ifs97b?fpfLx~L*qM#J3L%%|{hFf+^_OWiF7PS`bGKQy9chFU`y;b~ zNAyQ}nh$rt?YaD5oPCu0{a7SSZprinL9-fHMB5JCs1q=(tBF-vH~I-8#uQM`fEa8F zQSkuAX~pn9KEPpKu^($l_O{z=tQ>D*caG(U&S%zByIhbs8x!4y!NHCLlH#n}NHEmg z9IL*$$;Viw8SqiO`V-V;h8_D4=gs0e_0rzR0_n}HVRbuA7X?T}*hkMqj39cxWhc8a zRE~iJqs47)E{hmU#>oHK3uH_EUM&K4jWvi51oAEWt-qu0Zq~vzCkcIUxs23N> z3MkywUp=-TOVbDpOj-!G$o_&K3o!ZY_Dh+E{K7 zR3~0tq>g}9lftqJCDQu(uVeNrZu~f#F@={D6+1C~p91SObV(9KEh8XL812Pdd2viL zc<{gL@|y*lI)da|{2i-T0ai33Y->LgrpkF=OG3NFP_8hsl;v>@un4VR=^0_^Ld2W(3)~u`7*T6wzyF~ZH_ttGit7aD^2y| zhf3VJL%R1F`sUy)$pQbX8tAg+?m%?nr^BO)guF? zi&W&UC#=rnHu(@pQc>BKoQkqV-#edG(%~KaAC%}U-I^5^EM;LQASeq}4|wi)cck#T4+QGZ$x?oV$Z@|<%0-tSrbK{P4BlDtVuGB3OBO`+{Wyk~(2m4yKCvA{5> zVY*;SJ6#^2ycT8n{}B6zUnW#|JLe&jCFpnTb{{1YaLHE5gMIKFoXu$AGrNDScfAk7?-ZjI(S*upWSCK{)V{&(~_b7*arOn4gN@3`2Q-G zIOvgJ8@X|ihX;86uo?fnA4nfacU^Vfk}Gm3^8Vn~Z`8dFinwEJs1*E@Gk?zQUu`o4 z-kb{L0w!pG)qADAxhXo)2#bnNeQxqZ^;TMgtZ_4s_*#i{@Aqk(fr%}0rNXq0o+(s@PE!Y zzinybTguDv>Zu&?4>RBk$>sM(nyl{_)nANyWP6*bSL=%z@1HNM0Qqmj0TZq9{;&GY zw=9`bn@0Zfzj9s0ROfb0KuOWmthOcLupzB^Shg)5n`A zU~X=n=6ImK`q`33_>Iib4|Ps(rvS{avg|}tN0CW2+_bdiX~}Y||9F$0kZoJnPZcAL ztXeOs8DOFX5Xq-D80&)4{0wca8{KW`&j5kKKm-3py|3xr6=^Mc~Y#&wr z*qnkPAL{%jtm5tm>t9;59{t8@>m>^1kiWFW&PvgN;}EARmhlVK~}{?|$Euwmq+b$3U zVz7}G{vcv@fSiHC%vf+)3+OFOCGE$KjW&@Jv)@{~=yuA+e|p^#o$*DL!0{WqLG3yS z00zU7x653xKIpd+!egZ1K9g4Bh(&pnqd`Z;DFs0~!U>D=etOyPhJT83eW%4V z!^FgBsGi#YS%SalL{4;|>PDdLT>1IY=!jq!MTODl%kHCpe(nXlad2j4uy^%}o(d8@ zZ?c~O&Ptkd~nHW9}tm>QV7KC0Bt5{AJTH%_5A%iB8z98dmjQ=AC zKYYQhSvmfB*+sVlkxm^|vk}UtUprA+OE;abPn}Ec9*xc;%G(G0uWq}$yM&2Kb6>=% zNxYv{H@E!}`MQ2c#MR{$rhnOcCL<#=lkj<~ukw0whQnflRMT-%q0VRa@PH9MmpDyW zo4BgL!1%e4AIU)3zw|P|a_Wn_-tnQ9fEzavp{&RQLgR((u?%OP`Lm&u(bvQt*3NF0 zx$On4gd7<0?skhzv5hKYJOU~2D|*wsEf~8*;(%4vpAiJcqLMHX*CF1u#6b4hO8+

Dm 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 a{?sc7R4z}j*PjGBX3Nvn&t;ucLK6UEBrhcZ literal 0 HcmV?d00001 diff --git a/website/source/assets/images/docs/tfe-variables.png b/website/source/assets/images/docs/tfe-variables.png new file mode 100644 index 0000000000000000000000000000000000000000..7b0e735a0ebcf9a794884beb2366b538fa306f98 GIT binary patch literal 118684 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

yaeH5A|$Xe0d8F+Fq81E~aZo?YC&~kI`HD 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 G?*0$d#8~eD literal 0 HcmV?d00001 diff --git a/website/source/assets/stylesheets/_global.scss b/website/source/assets/stylesheets/_global.scss index 3b69c05ddce8..ae88df702ba0 100755 --- a/website/source/assets/stylesheets/_global.scss +++ b/website/source/assets/stylesheets/_global.scss @@ -33,3 +33,11 @@ h1 { .wf-active, .wf-inactive { visibility: visible; } + +@media (max-width: $screen-md) { + .container { + padding: 0; + max-width: $screen-md; + min-width: $screen-sm; + } +} diff --git a/website/source/docs/configuration/interpolation.html.md b/website/source/docs/configuration/interpolation.html.md index 0a1814665cd8..296b35be2a0c 100644 --- a/website/source/docs/configuration/interpolation.html.md +++ b/website/source/docs/configuration/interpolation.html.md @@ -158,6 +158,11 @@ The supported built-in functions are: **This is not equivalent** of `base64encode(sha256(string))` since `sha256()` returns hexadecimal representation. + * `base64sha512(string)` - Returns a base64-encoded representation of raw + SHA-512 sum of the given string. + **This is not equivalent** of `base64encode(sha512(string))` + since `sha512()` returns hexadecimal representation. + * `ceil(float)` - Returns the least integer value greater than or equal to the argument. @@ -218,15 +223,6 @@ The supported built-in functions are: module, you generally want to make the path relative to the module base, like this: `file("${path.module}/file")`. - * `matchkeys(values, keys, searchset)` - For two lists `values` and `keys` of - equal length, returns all elements from `values` where the corresponding - element from `keys` exists in the `searchset` list. E.g. - `matchkeys(aws_instance.example.*.id, - aws_instance.example.*.availability_zone, list("us-west-2a"))` will return a - list of the instance IDs of the `aws_instance.example` instances in - `"us-west-2a"`. No match will result in empty list. Items of `keys` are - processed sequentially, so the order of returned `values` is preserved. - * `floor(float)` - Returns the greatest integer value less than or equal to the argument. @@ -273,6 +269,8 @@ The supported built-in functions are: * `${list("a", "b", "c")}` returns a list of `"a", "b", "c"`. * `${list()}` returns an empty list. + * `log(x, base)` - Returns the logarithm of `x`. + * `lookup(map, key, [default])` - Performs a dynamic lookup into a map variable. The `map` parameter should be another variable, such as `var.amis`. If `key` does not exist in `map`, the interpolation will @@ -290,6 +288,15 @@ The supported built-in functions are: * `map("hello", "world")` * `map("us-east", list("a", "b", "c"), "us-west", list("b", "c", "d"))` + * `matchkeys(values, keys, searchset)` - For two lists `values` and `keys` of + equal length, returns all elements from `values` where the corresponding + element from `keys` exists in the `searchset` list. E.g. + `matchkeys(aws_instance.example.*.id, + aws_instance.example.*.availability_zone, list("us-west-2a"))` will return a + list of the instance IDs of the `aws_instance.example` instances in + `"us-west-2a"`. No match will result in empty list. Items of `keys` are + processed sequentially, so the order of returned `values` is preserved. + * `max(float1, float2, ...)` - Returns the largest of the floats. * `merge(map1, map2, ...)` - Returns the union of 2 or more maps. The maps @@ -321,6 +328,10 @@ The supported built-in functions are: SHA-256 hash of the given string. Example: `"${sha256("${aws_vpc.default.tags.customer}-s3-bucket")}"` + * `sha512(string)` - Returns a (conventional) hexadecimal representation of the + SHA-512 hash of the given string. + Example: `"${sha512("${aws_vpc.default.tags.customer}-s3-bucket")}"` + * `signum(int)` - Returns `-1` for negative numbers, `0` for `0` and `1` for positive numbers. This function is useful when you need to set a value for the first resource and a different value for the rest of the resources. diff --git a/website/source/docs/enterprise/faq/index.html.md b/website/source/docs/enterprise/faq/index.html.md index 106cb5f0d888..c99a37afc43e 100755 --- a/website/source/docs/enterprise/faq/index.html.md +++ b/website/source/docs/enterprise/faq/index.html.md @@ -11,3 +11,5 @@ description: |- [Monolithic Artifacts](/docs/enterprise/faq/monolithic-artifacts.html) - *How do I build multiple applications into one artifact?* [Rolling Deployments](/docs/enterprise/faq/rolling-deployments.html) - *How do I configure rolling deployments?* + +[Vagrant Cloud Migration](/docs/enterprise/faq/vagrant-cloud-migration.html) - *How can I prepare for the Vagrant Cloud Mirgration?* diff --git a/website/source/docs/enterprise/faq/vagrant-cloud-migration.html.md b/website/source/docs/enterprise/faq/vagrant-cloud-migration.html.md new file mode 100644 index 000000000000..d279de8b54e1 --- /dev/null +++ b/website/source/docs/enterprise/faq/vagrant-cloud-migration.html.md @@ -0,0 +1,23 @@ +--- +layout: "enterprise" +page_title: "Vagrant Cloud Migration - FAQ - Terraform Enterprise" +sidebar_current: "docs-enterprise-faq-vagrant-cloud-migration" +description: |- + Vagrant-related functionality will be moved from Terraform Enterprise into its own product, Vagrant Cloud. This migration is currently planned for June 27th, 2017. +--- + +# Vagrant Cloud Migration + +Vagrant-related functionality will be moved from Terraform Enterprise into its own product, Vagrant Cloud. This migration is currently planned for **June 27th, 2017**. + +All existing Vagrant boxes will be moved to the new system on that date. All users, organizations, and teams will be copied as well. + +## Authentication Tokens + +No existing Terraform Enterprise authentication tokens will be transferred. To prevent a disruption of service for Vagrant-related operations, users must create a new authentication token and check "Migrate to Vagrant Cloud" and begin using these tokens for creating and modifying Vagrant boxes. These tokens will be moved on the migration date. + +Creating a token via `vagrant login` will also mark a token as "Migrate to Vagrant Cloud". + +## More Information + +At least 1 month prior to the migration, we will be releasing more information on the specifics and impact of the migration. \ No newline at end of file diff --git a/website/source/docs/enterprise/runs/variables-and-configuration.html.md b/website/source/docs/enterprise/runs/variables-and-configuration.html.md index 1ebeb7a92301..7f7711fc1f1c 100755 --- a/website/source/docs/enterprise/runs/variables-and-configuration.html.md +++ b/website/source/docs/enterprise/runs/variables-and-configuration.html.md @@ -8,19 +8,34 @@ description: |- # Terraform Variables and Configuration -There are two ways to configure Terraform runs – with Terraform variables or -environment variables. +There are several ways to configure Terraform runs: -## Terraform Variables +1. Terraform variables +2. Environment variables +3. Personal Environment and Personal Organization variables + +You can add, edit, and delete all Terraform, Environment, and Personal +Environment variables from the "Variables" page on your environment: + +![Terraform Enterprise environment variable configuration](docs/tfe-variables.png) + +Personal Organization variables can be managed in your Account Settings under +"Organization Variables": + +![Terraform Enterprise personal organization variables](docs/tfe-organization-variables.png) + +## Variable types + +### Terraform Variables Terraform variables are first-class configuration in Terraform. They define the parameterization of Terraform configurations and are important for sharing and removal of sensitive secrets from version control. Variables are sent with the `terraform push` command. Any variables in your local -`.tfvars` files are securely uploaded. Once variables are uploaded, Terraform will prefer the stored variables over any changes you -make locally. Please refer to the -[Terraform push documentation](https://www.terraform.io/docs/commands/push.html) +`.tfvars` files are securely uploaded. Once variables are uploaded, Terraform +will prefer the stored variables over any changes you make locally. Please refer +to the [Terraform push documentation](https://www.terraform.io/docs/commands/push.html) for more information. You can also add, edit, and delete variables. To update Terraform variables, @@ -32,7 +47,7 @@ For detailed information about Terraform variables, please read the [Terraform variables](https://terraform.io/docs/configuration/variables.html) section of the Terraform documentation. -## Environment Variables +### Environment Variables Environment variables are injected into the virtual environment that Terraform executes in during the `plan` and `apply` phases. @@ -75,9 +90,47 @@ For any of the `GITHUB_` attributes, the value of the environment variable will be the empty string (`""`) if the resource is not connected to GitHub or if the resource was created outside of GitHub (like using `terraform push`). +### Personal Environment and Personal Organization Variables + +Personal variables can be created at the Environment or Organization level and +are private and scoped to the user that created them. Personal Environment +variables are scoped to just the environment they are attached to, while Personal +Organization variables are applied across any environment a user triggers a +Terraform run in. Just like shared Environment variables, they are injected into +the virtual environment during the `plan` and `apply` phases. + +Both Personal Environment and Personal Organization variables can be used to +override Environment variables on a per-user basis. + +## Variable Hierarchy + +It is possible to create the same variable in multiple places for more granular +control. Variables are applied in the following order from least to most +precedence: + +1. Environment +2. Personal Organization +3. Personal Environment + +Here's an example: + +* For the `SlothCorp/petting_zoo` environment, User 1 creates +an Environment variable called `SECRET_GATE_ACCESS_KEY` and sets the value to +`"orange-turtleneck"` +* User 2 adds a Personal Environment variable for +`SECRET_GATE_ACCESS_KEY` and sets the value to `"pink-overalls"` +* When User 2 submits a `plan` or `apply`, the `SECRET_GATE_ACCESS_KEY` +will use `"pink-overalls"` +* When User 1, or any other user, submits a `plan` or `apply`, the +`SECRET_GATE_ACCESS_KEY` will use `"orange-turtleneck"` + ## Managing Secret Multi-Line Files -Terraform Enterprise has the ability to store multi-line files as variables. The recommended way to manage your secret/sensitive multi-line files (private key, SSL cert, SSL private key, CA, etc.) is to add them as [Terraform Variables](#terraform-variables) or [Environment Variables](#environment-variables). +Terraform Enterprise has the ability to store multi-line files as variables. The +recommended way to manage your secret or sensitive multi-line files (private key, +SSL cert, SSL private key, CA, etc.) is to add them as +[Terraform Variables](#terraform-variables) or +[Environment Variables](#environment-variables). Just like secret strings, it is recommended that you never check in these multi-line secret files to version control by following the below steps. diff --git a/website/source/docs/import/importability.html.md b/website/source/docs/import/importability.html.md index b6b22e6093f0..628881886264 100644 --- a/website/source/docs/import/importability.html.md +++ b/website/source/docs/import/importability.html.md @@ -150,6 +150,7 @@ To make a resource importable, please see the * google_compute_instance_group_manager * google_compute_instance_template * google_compute_target_pool +* google_dns_managed_zone * google_project ### OpenStack diff --git a/website/source/docs/providers/aws/d/billing_service_account.markdown b/website/source/docs/providers/aws/d/billing_service_account.html.markdown similarity index 100% rename from website/source/docs/providers/aws/d/billing_service_account.markdown rename to website/source/docs/providers/aws/d/billing_service_account.html.markdown diff --git a/website/source/docs/providers/aws/d/iam_role.html.markdown b/website/source/docs/providers/aws/d/iam_role.html.markdown index 85ce81de27d1..e335fc6677ca 100644 --- a/website/source/docs/providers/aws/d/iam_role.html.markdown +++ b/website/source/docs/providers/aws/d/iam_role.html.markdown @@ -1,7 +1,7 @@ --- layout: "aws" page_title: "AWS: aws_iam_role" -sidebar_current: docs-aws-datasource-iam-role +sidebar_current: "docs-aws-datasource-iam-role" description: |- Get information on a Amazon IAM role --- diff --git a/website/source/docs/providers/aws/d/security_group.html.markdown b/website/source/docs/providers/aws/d/security_group.html.markdown index 988558e6d9b7..175a0bd4bcef 100644 --- a/website/source/docs/providers/aws/d/security_group.html.markdown +++ b/website/source/docs/providers/aws/d/security_group.html.markdown @@ -65,6 +65,10 @@ All of the argument attributes except `filter` blocks are also exported as result attributes. This data source will complete the data by populating any fields that are not included in the configuration with the data for the selected Security Group. -Additionally, the `description` attribute is exported. + +The following fields are also exported: + +* `description` - The description of the security group. +* `arn` - The computed ARN of the security group. ~> **Note:** The [default security group for a VPC](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_SecurityGroups.html#DefaultSecurityGroup) has the name `default`. diff --git a/website/source/docs/providers/aws/index.html.markdown b/website/source/docs/providers/aws/index.html.markdown index 429039d5ad34..75104bdc4feb 100644 --- a/website/source/docs/providers/aws/index.html.markdown +++ b/website/source/docs/providers/aws/index.html.markdown @@ -234,6 +234,22 @@ in excess of those allowed by the access policy of the role that is being assume Nested `endpoints` block supports the following: +* `cloudwatch` - (Optional) Use this to override the default endpoint + URL constructed from the `region`. It's typically used to connect to + custom CloudWatch endpoints. + +* `cloudwatchevents` - (Optional) Use this to override the default endpoint + URL constructed from the `region`. It's typically used to connect to + custom CloudWatchEvents endpoints. + +* `cloudwatchlogs` - (Optional) Use this to override the default endpoint + URL constructed from the `region`. It's typically used to connect to + custom CloudWatchLogs endpoints. + +* `cloudformation` - (Optional) Use this to override the default endpoint + URL constructed from the `region`. It's typically used to connect to + custom CloudFormation endpoints. + * `dynamodb` - (Optional) Use this to override the default endpoint URL constructed from the `region`. It's typically used to connect to `dynamodb-local`. @@ -242,6 +258,10 @@ Nested `endpoints` block supports the following: URL constructed from the `region`. It's typically used to connect to `kinesalite`. +* `kms` - (Optional) Use this to override the default endpoint + URL constructed from the `region`. It's typically used to connect to + custom KMS endpoints. + * `iam` - (Optional) Use this to override the default endpoint URL constructed from the `region`. It's typically used to connect to custom IAM endpoints. @@ -254,10 +274,22 @@ Nested `endpoints` block supports the following: URL constructed from the `region`. It's typically used to connect to custom ELB endpoints. +* `rds` - (Optional) Use this to override the default endpoint + URL constructed from the `region`. It's typically used to connect to + custom RDS endpoints. + * `s3` - (Optional) Use this to override the default endpoint URL constructed from the `region`. It's typically used to connect to custom S3 endpoints. +* `sns` - (Optional) Use this to override the default endpoint + URL constructed from the `region`. It's typically used to connect to + custom SNS endpoints. + +* `sqs` - (Optional) Use this to override the default endpoint + URL constructed from the `region`. It's typically used to connect to + custom SQS endpoints. + ## Getting the Account ID If you use either `allowed_account_ids` or `forbidden_account_ids`, diff --git a/website/source/docs/providers/aws/r/app_cookie_stickiness_policy.html.markdown b/website/source/docs/providers/aws/r/app_cookie_stickiness_policy.html.markdown index aff29db4cfc3..8184db371e5e 100644 --- a/website/source/docs/providers/aws/r/app_cookie_stickiness_policy.html.markdown +++ b/website/source/docs/providers/aws/r/app_cookie_stickiness_policy.html.markdown @@ -1,7 +1,7 @@ --- layout: "aws" page_title: "AWS: aws_app_cookie_stickiness_policy" -sidebar_current: "docs-aws-app-cookie-stickiness-policy" +sidebar_current: "docs-aws-resource-app-cookie-stickiness-policy" description: |- Provides an application cookie stickiness policy, which allows an ELB to wed its stickiness cookie to a cookie generated by your application. --- diff --git a/website/source/docs/providers/aws/r/cloudwatch_event_target.html.markdown b/website/source/docs/providers/aws/r/cloudwatch_event_target.html.markdown index 252beb4c6061..9dc13412a4ba 100644 --- a/website/source/docs/providers/aws/r/cloudwatch_event_target.html.markdown +++ b/website/source/docs/providers/aws/r/cloudwatch_event_target.html.markdown @@ -62,3 +62,11 @@ The following arguments are supported: * `input` - (Optional) Valid JSON text passed to the target. * `input_path` - (Optional) The value of the [JSONPath](http://goessner.net/articles/JsonPath/) that is used for extracting part of the matched event when passing it to the target. +* `role_arn` - (Optional) The Amazon Resource Name (ARN) of the IAM role to be used for this target when the rule is triggered. +* `run_command_targets` - (Optional) Parameters used when you are using the rule to invoke Amazon EC2 Run Command. Documented below. A maximum of 5 are allowed. + +`run_command_parameters` support the following: + +* `key` - (Required) Can be either `tag:tag-key` or `InstanceIds`. +* `values` - (Required) If Key is `tag:tag-key`, Values is a list of tag values. If Key is `InstanceIds`, Values is a list of Amazon EC2 instance IDs. + diff --git a/website/source/docs/providers/aws/r/cloudwatch_metric_alarm.html.markdown b/website/source/docs/providers/aws/r/cloudwatch_metric_alarm.html.markdown index eaa837babdec..1333f46f3239 100644 --- a/website/source/docs/providers/aws/r/cloudwatch_metric_alarm.html.markdown +++ b/website/source/docs/providers/aws/r/cloudwatch_metric_alarm.html.markdown @@ -22,7 +22,7 @@ resource "aws_cloudwatch_metric_alarm" "foobar" { period = "120" statistic = "Average" threshold = "80" - alarm_description = "This metric monitor ec2 cpu utilization" + alarm_description = "This metric monitors ec2 cpu utilization" insufficient_data_actions = [] } ``` @@ -52,7 +52,7 @@ resource "aws_cloudwatch_metric_alarm" "bat" { AutoScalingGroupName = "${aws_autoscaling_group.bar.name}" } - alarm_description = "This metric monitor ec2 cpu utilization" + alarm_description = "This metric monitors ec2 cpu utilization" alarm_actions = ["${aws_autoscaling_policy.bat.arn}"] } ``` diff --git a/website/source/docs/providers/aws/r/config_config_rule.html.markdown b/website/source/docs/providers/aws/r/config_config_rule.html.markdown index 5521547441c2..dae253fe9092 100644 --- a/website/source/docs/providers/aws/r/config_config_rule.html.markdown +++ b/website/source/docs/providers/aws/r/config_config_rule.html.markdown @@ -108,7 +108,7 @@ Provides the rule owner (AWS or customer), the rule identifier, and the notifica For custom rules, the identifier is the ARN of the rule's AWS Lambda function, such as `arn:aws:lambda:us-east-1:123456789012:function:custom_rule_name`. * `source_detail` - (Optional) Provides the source and type of the event that causes AWS Config to evaluate your AWS resources. Only valid if `owner` is `CUSTOM_LAMBDA`. * `event_source` - (Optional) The source of the event, such as an AWS service, that triggers AWS Config - to evaluate your AWS resources. The only valid value is `aws.config`. + to evaluate your AWS resources. This defaults to `aws.config` and is the only valid value. * `maximum_execution_frequency` - (Optional) The frequency that you want AWS Config to run evaluations for a rule that is triggered periodically. If specified, requires `message_type` to be `ScheduledNotification`. * `message_type` - (Optional) The type of notification that triggers AWS Config to run an evaluation for a rule. You can specify the following notification types: diff --git a/website/source/docs/providers/aws/r/db_option_group.html.markdown b/website/source/docs/providers/aws/r/db_option_group.html.markdown index ee1f8804caf4..db2657a31a6a 100644 --- a/website/source/docs/providers/aws/r/db_option_group.html.markdown +++ b/website/source/docs/providers/aws/r/db_option_group.html.markdown @@ -38,10 +38,10 @@ resource "aws_db_option_group" "bar" { The following arguments are supported: -* `name` - (Optional, Forces new resource) The name of the option group. If omitted, Terraform will assign a random, unique name. -* `name_prefix` - (Optional, Forces new resource) Creates a unique name beginning with the specified prefix. Conflicts with `name`. +* `name` - (Optional, Forces new resource) The name of the option group. If omitted, Terraform will assign a random, unique name. This is converted to lowercase, as is stored in AWS. +* `name_prefix` - (Optional, Forces new resource) Creates a unique name beginning with the specified prefix. Conflicts with `name`. This is converted to lowercase, as is stored in AWS. * `option_group_description` - (Optional) The description of the option group. Defaults to "Managed by Terraform". -* `engine_name` - (Required) Specifies the name of the engine that this option group should be associated with.. +* `engine_name` - (Required) Specifies the name of the engine that this option group should be associated with. * `major_engine_version` - (Required) Specifies the major version of the engine that this option group should be associated with. * `option` - (Optional) A list of Options to apply. * `tags` - (Optional) A mapping of tags to assign to the resource. diff --git a/website/source/docs/providers/aws/r/default_route_table.html.markdown b/website/source/docs/providers/aws/r/default_route_table.html.markdown index 30a3bc0081d0..fc7ea34ca056 100644 --- a/website/source/docs/providers/aws/r/default_route_table.html.markdown +++ b/website/source/docs/providers/aws/r/default_route_table.html.markdown @@ -1,7 +1,7 @@ --- layout: "aws" page_title: "AWS: aws_default_route_table" -sidebar_current: "docs-aws-resource-default-route-table|" +sidebar_current: "docs-aws-resource-default-route-table" description: |- Provides a resource to manage a Default VPC Routing Table. --- diff --git a/website/source/docs/providers/aws/r/elasticache_replication_group.html.markdown b/website/source/docs/providers/aws/r/elasticache_replication_group.html.markdown index a14d4e855a81..0145453e3bf0 100644 --- a/website/source/docs/providers/aws/r/elasticache_replication_group.html.markdown +++ b/website/source/docs/providers/aws/r/elasticache_replication_group.html.markdown @@ -12,6 +12,8 @@ Provides an ElastiCache Replication Group resource. ## Example Usage +### Redis Master with One Replica + ```hcl resource "aws_elasticache_replication_group" "bar" { replication_group_id = "tf-rep-group-1" @@ -25,6 +27,23 @@ resource "aws_elasticache_replication_group" "bar" { } ``` +### Native Redis Cluser 2 Masters 2 Replicas + +```hcl +resource "aws_elasticache_replication_group" "baz" { + replication_group_id = "tf-replication-group-1" + replication_group_description = "test description" + node_type = "cache.m1.small" + port = 6379 + parameter_group_name = "default.redis3.2.cluster.on" + automatic_failover_enabled = true + cluster_mode { + replicas_per_node_group = 1 + num_node_groups = 2 + } +} +``` + ~> **Note:** We currently do not support passing a `primary_cluster_id` in order to create the Replication Group. ~> **Note:** Automatic Failover is unavailable for Redis versions earlier than 2.8.6, @@ -32,7 +51,6 @@ and unavailable on T1 and T2 node types. See the [Amazon Replication with Redis](http://docs.aws.amazon.com/en_en/AmazonElastiCache/latest/UserGuide/Replication.html) guide for full details on using Replication Groups. - ## Argument Reference The following arguments are supported: @@ -70,6 +88,12 @@ before being deleted. If the value of SnapshotRetentionLimit is set to zero (0), Please note that setting a `snapshot_retention_limit` is not supported on cache.t1.micro or cache.t2.* cache nodes * `apply_immediately` - (Optional) Specifies whether any modifications are applied immediately, or during the next maintenance window. Default is `false`. * `tags` - (Optional) A mapping of tags to assign to the resource +* `cluster_mode` - (Optional) Create a native redis cluster. `automatic_failover_enabled` must be set to true. Cluster Mode documented below. Only 1 `cluster_mode` block is allowed. + +Cluster Mode (`cluster_mode`) supports the following: + +* `replicas_per_node_group` - (Required) Specify the number of replica nodes in each node group. Valid values are 0 to 5. Changing this number will force a new resource. +* `num_node_groups` - (Required) Specify the number of node groups (shards) for this Redis replication group. Changing this number will force a new resource. ## Attributes Reference diff --git a/website/source/docs/providers/aws/r/iam_role.html.markdown b/website/source/docs/providers/aws/r/iam_role.html.markdown index ecba4be520d5..ad8b983d33d0 100644 --- a/website/source/docs/providers/aws/r/iam_role.html.markdown +++ b/website/source/docs/providers/aws/r/iam_role.html.markdown @@ -46,6 +46,7 @@ The following arguments are supported: * `path` - (Optional) The path to the role. See [IAM Identifiers](https://docs.aws.amazon.com/IAM/latest/UserGuide/Using_Identifiers.html) for more information. +* `description` - (Optional) The description of the role. ## Attributes Reference @@ -55,6 +56,7 @@ The following attributes are exported: * `create_date` - The creation date of the IAM role. * `unique_id` - The stable and unique string identifying the role. * `name` - The name of the role. +* `description` - The description of the role. ## Example of Using Data Source for Assume Role Policy diff --git a/website/source/docs/providers/aws/r/kinesis_stream.html.markdown b/website/source/docs/providers/aws/r/kinesis_stream.html.markdown index 90f0d81a0c1b..51f92a7b1e32 100644 --- a/website/source/docs/providers/aws/r/kinesis_stream.html.markdown +++ b/website/source/docs/providers/aws/r/kinesis_stream.html.markdown @@ -53,6 +53,15 @@ when creating a Kinesis stream. See [Amazon Kinesis Streams][2] for more. * `arn` - The Amazon Resource Name (ARN) specifying the Stream +## Import + +Kinesis Streams can be imported using the `name`, e.g. + +``` +$ terraform import aws_kinesis_stream.test_stream terraform-kinesis-test +``` + [1]: https://aws.amazon.com/documentation/kinesis/ [2]: https://docs.aws.amazon.com/kinesis/latest/dev/amazon-kinesis-streams.html [3]: https://docs.aws.amazon.com/streams/latest/dev/monitoring-with-cloudwatch.html + diff --git a/website/source/docs/providers/aws/r/network_interface_attachment.markdown b/website/source/docs/providers/aws/r/network_interface_attachment.html.markdown similarity index 100% rename from website/source/docs/providers/aws/r/network_interface_attachment.markdown rename to website/source/docs/providers/aws/r/network_interface_attachment.html.markdown diff --git a/website/source/docs/providers/aws/r/route53_health_check.html.markdown b/website/source/docs/providers/aws/r/route53_health_check.html.markdown index 815174759908..03d6bef1db8f 100644 --- a/website/source/docs/providers/aws/r/route53_health_check.html.markdown +++ b/website/source/docs/providers/aws/r/route53_health_check.html.markdown @@ -48,7 +48,7 @@ resource "aws_cloudwatch_metric_alarm" "foobar" { period = "120" statistic = "Average" threshold = "80" - alarm_description = "This metric monitor ec2 cpu utilization" + alarm_description = "This metric monitors ec2 cpu utilization" } resource "aws_route53_health_check" "foo" { diff --git a/website/source/docs/providers/aws/r/route53_record.html.markdown b/website/source/docs/providers/aws/r/route53_record.html.markdown index e0230812be20..0f90cdec6596 100644 --- a/website/source/docs/providers/aws/r/route53_record.html.markdown +++ b/website/source/docs/providers/aws/r/route53_record.html.markdown @@ -141,7 +141,18 @@ Weighted routing policies support the following: ## Import -Route53 Records can be imported using ID of the record, e.g. +Route53 Records can be imported using ID of the record. The ID is made up as ZONEID_RECORDNAME_TYPE_SET-IDENTIFIER + +e.g. + +``` +Z4KAPRWWNC7JR_dev.example.com_NS_dev +``` + +In this example, `Z4KAPRWWNC7JR` is the ZoneID, `dev.example.com` is the Record Name, `NS` is the Type and `dev` is the Set Identifier. +Only the Set Identifier is actually optional in the ID + +To import the ID above, it would look as follows: ``` $ terraform import aws_route53_record.myrecord Z4KAPRWWNC7JR_dev.example.com_NS_dev diff --git a/website/source/docs/providers/aws/r/security_group.html.markdown b/website/source/docs/providers/aws/r/security_group.html.markdown index 3f8597372fe2..5b21907be8d2 100644 --- a/website/source/docs/providers/aws/r/security_group.html.markdown +++ b/website/source/docs/providers/aws/r/security_group.html.markdown @@ -45,7 +45,7 @@ resource "aws_security_group" "allow_all" { Basic usage with tags: -``` +```hcl resource "aws_security_group" "allow_all" { name = "allow_all" description = "Allow all inbound traffic" @@ -116,12 +116,14 @@ specifically re-create it if you desire that rule. We feel this leads to fewer surprises in terms of controlling your egress rules. If you desire this rule to be in place, you can use this `egress` block: +```hcl egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } +``` ## Usage with prefix list IDs @@ -129,7 +131,7 @@ Prefix list IDs are managed by AWS internally. Prefix list IDs are associated with a prefix list name, or service name, that is linked to a specific region. Prefix list IDs are exported on VPC Endpoints, so you can use this format: -``` +```hcl # ... egress { from_port = 0 diff --git a/website/source/docs/providers/aws/r/ses_configuration_set.markdown b/website/source/docs/providers/aws/r/ses_configuration_set.markdown index 9ea3b374e5fe..1b4a0bf3ca2e 100644 --- a/website/source/docs/providers/aws/r/ses_configuration_set.markdown +++ b/website/source/docs/providers/aws/r/ses_configuration_set.markdown @@ -1,7 +1,7 @@ --- layout: "aws" page_title: "AWS: ses_configuration_set" -sidebar_current: "docs-aws-resource-ses-configuration_set" +sidebar_current: "docs-aws-resource-ses-configuration-set" description: |- Provides an SES configuration set --- diff --git a/website/source/docs/providers/aws/r/ses_domain_identity.html.markdown b/website/source/docs/providers/aws/r/ses_domain_identity.html.markdown index fc8dfc1749f2..26f37bf1b652 100644 --- a/website/source/docs/providers/aws/r/ses_domain_identity.html.markdown +++ b/website/source/docs/providers/aws/r/ses_domain_identity.html.markdown @@ -15,11 +15,13 @@ Provides an SES domain identity resource The following arguments are supported: * `domain` - (Required) The domain name to assign to SES - + ## Attributes Reference - + The following attributes are exported: +* `arn` - The ARN of the domain identity. + * `verification_token` - A code which when added to the domain as a TXT record will signal to SES that the owner of the domain has authorised SES to act on their behalf. The domain identity will be in state "verification pending" @@ -41,6 +43,6 @@ resource "aws_route53_record" "example_amazonses_verification_record" { type = "TXT" ttl = "600" records = ["${aws_ses_domain_identity.example.verification_token}"] -} +} ``` diff --git a/website/source/docs/providers/aws/r/spot_datafeed_subscription.html.markdown b/website/source/docs/providers/aws/r/spot_datafeed_subscription.html.markdown index e1cac15d9e01..71e2ec7d119d 100644 --- a/website/source/docs/providers/aws/r/spot_datafeed_subscription.html.markdown +++ b/website/source/docs/providers/aws/r/spot_datafeed_subscription.html.markdown @@ -1,7 +1,7 @@ --- layout: "aws" page_title: "AWS: aws_spot_datafeed_subscription" -sidebar_current: "docs-aws-resource-spot-datafleet-subscription" +sidebar_current: "docs-aws-resource-spot-datafeed-subscription" description: |- Provides a Spot Datafeed Subscription resource. --- diff --git a/website/source/docs/providers/aws/r/spot_fleet_request.html.markdown b/website/source/docs/providers/aws/r/spot_fleet_request.html.markdown index b96e5c8474cc..7b77ee199305 100644 --- a/website/source/docs/providers/aws/r/spot_fleet_request.html.markdown +++ b/website/source/docs/providers/aws/r/spot_fleet_request.html.markdown @@ -23,9 +23,10 @@ resource "aws_spot_fleet_request" "cheap_compute" { valid_until = "2019-11-04T20:44:20Z" launch_specification { - instance_type = "m4.10xlarge" - ami = "ami-1234" - spot_price = "2.793" + instance_type = "m4.10xlarge" + ami = "ami-1234" + spot_price = "2.793" + placement_tenancy = "dedicated" } launch_specification { @@ -114,4 +115,4 @@ requests are placed or enabled to fulfill the request. Defaults to 24 hours. The following attributes are exported: * `id` - The Spot fleet request ID -* `spot_request_state` - The state of the Spot fleet request. \ No newline at end of file +* `spot_request_state` - The state of the Spot fleet request. diff --git a/website/source/docs/providers/aws/r/ssm_association.html.markdown b/website/source/docs/providers/aws/r/ssm_association.html.markdown index ae29a532528f..e9bdc137f334 100644 --- a/website/source/docs/providers/aws/r/ssm_association.html.markdown +++ b/website/source/docs/providers/aws/r/ssm_association.html.markdown @@ -6,7 +6,7 @@ description: |- Assosciates an SSM Document to an instance. --- -# aws\_ssm\_association +# aws_ssm_association Assosciates an SSM Document to an instance. @@ -68,8 +68,9 @@ resource "aws_ssm_association" "foo" { The following arguments are supported: * `name` - (Required) The name of the SSM document to apply. -* `instance_id` - (Required) The instance id to apply an SSM document to. +* `instance_id` - (Optional) The instance id to apply an SSM document to. * `parameters` - (Optional) Additional parameters to pass to the SSM document. +* `targets` - (Optional) The targets (either instances or tags). Instances are specified using Key=instanceids,Values=instanceid1,instanceid2. Tags are specified using Key=tag name,Values=tag value. Only 1 target is currently supported by AWS. ## Attributes Reference diff --git a/website/source/docs/providers/aws/r/ssm_maintenance_window.html.markdown b/website/source/docs/providers/aws/r/ssm_maintenance_window.html.markdown new file mode 100644 index 000000000000..d48b7de16f62 --- /dev/null +++ b/website/source/docs/providers/aws/r/ssm_maintenance_window.html.markdown @@ -0,0 +1,38 @@ +--- +layout: "aws" +page_title: "AWS: aws_ssm_maintenance_window" +sidebar_current: "docs-aws-resource-ssm-maintenance-window" +description: |- + Provides an SSM Maintenance Window resource +--- + +# aws_ssm_maintenance_window + +Provides an SSM Maintenance Window resource + +## Example Usage + +```hcl +resource "aws_ssm_maintenance_window" "production" { + name = "maintenance-window-application" + schedule = "cron(0 16 ? * TUE *)" + duration = 3 + cutoff = 1 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the maintenance window. +* `schedule` - (Required) The schedule of the Maintenance Window in the form of a [cron](https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-maintenance-cron.html) or rate expression. +* `cutoff` - (Required) The number of hours before the end of the Maintenance Window that Systems Manager stops scheduling new tasks for execution. +* `duration` - (Required) The duration of the Maintenance Window in hours. +* `allow_unregistered_targets` - (Optional) Whether targets must be registered with the Maintenance Window before tasks can be defined for those targets. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the maintenance window. \ No newline at end of file diff --git a/website/source/docs/providers/aws/r/ssm_maintenance_window_target.html.markdown b/website/source/docs/providers/aws/r/ssm_maintenance_window_target.html.markdown new file mode 100644 index 000000000000..beff191eeeb5 --- /dev/null +++ b/website/source/docs/providers/aws/r/ssm_maintenance_window_target.html.markdown @@ -0,0 +1,46 @@ +--- +layout: "aws" +page_title: "AWS: aws_ssm_maintenance_window_target" +sidebar_current: "docs-aws-resource-ssm-maintenance-window-target" +description: |- + Provides an SSM Maintenance Window Target resource +--- + +# aws_ssm_maintenance_window_target + +Provides an SSM Maintenance Window Target resource + +## Example Usage + +```hcl +resource "aws_ssm_maintenance_window" "window" { + name = "maintenance-window-webapp" + schedule = "cron(0 16 ? * TUE *)" + duration = 3 + cutoff = 1 +} + +resource "aws_ssm_maintenance_window_target" "target1" { + window_id = "${aws_ssm_maintenance_window.window.id}" + resource_type = "INSTANCE" + targets { + key = "tag:Name" + values = ["acceptance_test"] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `window_id` - (Required) The Id of the maintenance window to register the target with. +* `resource_type` - (Required) The type of target being registered with the Maintenance Window. Possible values `INSTANCE`. +* `targets` - (Required) The targets (either instances or tags). Instances are specified using Key=instanceids,Values=instanceid1,instanceid2. Tags are specified using Key=tag name,Values=tag value. +* `owner_information` - (Optional) User-provided value that will be included in any CloudWatch events raised while running tasks for these targets in this Maintenance Window. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the maintenance window target. \ No newline at end of file diff --git a/website/source/docs/providers/aws/r/ssm_maintenance_window_task.html.markdown b/website/source/docs/providers/aws/r/ssm_maintenance_window_task.html.markdown new file mode 100644 index 000000000000..2ed3b50a4391 --- /dev/null +++ b/website/source/docs/providers/aws/r/ssm_maintenance_window_task.html.markdown @@ -0,0 +1,68 @@ +--- +layout: "aws" +page_title: "AWS: aws_ssm_maintenance_window_task" +sidebar_current: "docs-aws-resource-ssm-maintenance-window-task" +description: |- + Provides an SSM Maintenance Window Task resource +--- + +# aws_ssm_maintenance_window_task + +Provides an SSM Maintenance Window Task resource + +## Example Usage + +```hcl +resource "aws_ssm_maintenance_window" "window" { + name = "maintenance-window-%s" + schedule = "cron(0 16 ? * TUE *)" + duration = 3 + cutoff = 1 +} + +resource "aws_ssm_maintenance_window_task" "target" { + window_id = "${aws_ssm_maintenance_window.window.id}" + task_type = "RUN_COMMAND" + task_arn = "AWS-RunShellScript" + priority = 1 + service_role_arn = "arn:aws:iam::187416307283:role/service-role/AWS_Events_Invoke_Run_Command_112316643" + max_concurrency = "2" + max_errors = "1" + targets { + key = "InstanceIds" + values = ["${aws_instance.instance.id}"] + } +} + +resource "aws_instance" "instance" { + ami = "ami-4fccb37f" + + instance_type = "m1.small" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `window_id` - (Required) The Id of the maintenance window to register the task with. +* `max_concurrency` - (Required) The maximum number of targets this task can be run for in parallel. +* `max_errors` - (Required) The maximum number of errors allowed before this task stops being scheduled. +* `task_type` - (Required) The type of task being registered. The only allowed value is `RUN_COMMAND`. +* `task_arn` - (Required) The ARN of the task to execute. +* `service_role_arn` - (Required) The role that should be assumed when executing the task. +* `targets` - (Required) The targets (either instances or tags). Instances are specified using Key=instanceids,Values=instanceid1,instanceid2. Tags are specified using Key=tag name,Values=tag value. +* `priority` - (Optional) The priority of the task in the Maintenance Window, the lower the number the higher the priority. Tasks in a Maintenance Window are scheduled in priority order with tasks that have the same priority scheduled in parallel. +* `logging_info` - (Optional) A structure containing information about an Amazon S3 bucket to write instance-level logs to. Documented below. + +`logging_info` supports the following: + +* `s3_bucket_name` - (Required) +* `s3_region` - (Required) +* `s3_bucket_prefix` - (Optional) + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the maintenance window task. \ No newline at end of file diff --git a/website/source/docs/providers/azurerm/r/sql_elasticpool.html.markdown b/website/source/docs/providers/azurerm/r/sql_elasticpool.html.markdown new file mode 100644 index 000000000000..e5002135e43c --- /dev/null +++ b/website/source/docs/providers/azurerm/r/sql_elasticpool.html.markdown @@ -0,0 +1,75 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_sql_elasticpool" +sidebar_current: "docs-azurerm-resource-sql-elasticpool" +description: |- + Create a SQL Elastic Pool. +--- + +# azurerm\_sql\_elasticpool + +Allows you to manage an Azure SQL Elastic Pool. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "test" { + name = "test" + location = "West US" +} + +resource "azurerm_sql_server" "test" { + name = "test" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "West US" + version = "12.0" + administrator_login = "4dm1n157r470r" + administrator_login_password = "4-v3ry-53cr37-p455w0rd" +} + +resource "azurerm_sql_elasticpool" "test" { + name = "test" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "West US" + server_name = "${azurerm_sql_server.test.name}" + edition = "Basic" + dtu = 100 + db_min_dtu = 0 + db_max_dtu = 5 + pool_size = 5000 +} +``` + +~> **NOTE on `azurerm_sql_elasticpool`:** - The values of `edition`, `dtu`, and `pool_size` must be consistent with the [Azure SQL Database Service Tiers](https://docs.microsoft.com/en-gb/azure/sql-database/sql-database-service-tiers#elastic-pool-service-tiers-and-performance-in-edtus). Any inconsistent argument configuration will be rejected. + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the elastic pool. + +* `resource_group_name` - (Required) The name of the resource group in which to create the elastic pool. This must be the same as the resource group of the underlying SQL server. + +* `location` - (Required) Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created. + +* `server_name` - (Required) The name of the SQL Server on which to create the elastic pool. Changing this forces a new resource to be created. + +* `edition` - (Required) The edition of the elastic pool to be created. Valid values are `Basic`, `Standard`, and `Premium`. Refer to [Azure SQL Database Service Tiers](https://docs.microsoft.com/en-gb/azure/sql-database/sql-database-service-tiers#elastic-pool-service-tiers-and-performance-in-edtus) for details. Changing this forces a new resource to be created. + +* `dtu` - (Required) The total shared DTU for the elastic pool. Valid values depend on the `edition` which has been defined. Refer to [Azure SQL Database Service Tiers](https://docs.microsoft.com/en-gb/azure/sql-database/sql-database-service-tiers#elastic-pool-service-tiers-and-performance-in-edtus) for valid combinations. + +* `db_dtu_min` - (Optional) The minimum DTU which will be guaranteed to all databases in the elastic pool to be created. + +* `db_dtu_max` - (Optional) The maximum DTU which will be guaranteed to all databases in the elastic pool to be created. + +* `pool_size` - (Optional) The maximum size in MB that all databases in the elastic pool can grow to. The maximum size must be consistent with combination of `edition` and `dtu` and the limits documented in [Azure SQL Database Service Tiers](https://docs.microsoft.com/en-gb/azure/sql-database/sql-database-service-tiers#elastic-pool-service-tiers-and-performance-in-edtus). If not defined when creating an elastic pool, the value is set to the size implied by `edition` and `dtu`. + +* `tags` - (Optional) A mapping of tags to assign to the resource. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The SQL Elastic Pool ID. + +* `creation_date` - The creation date of the SQL Elastic Pool. diff --git a/website/source/docs/providers/azurerm/r/template_deployment.html.markdown b/website/source/docs/providers/azurerm/r/template_deployment.html.markdown index 5dbc67762aff..ffb6386958fd 100644 --- a/website/source/docs/providers/azurerm/r/template_deployment.html.markdown +++ b/website/source/docs/providers/azurerm/r/template_deployment.html.markdown @@ -70,12 +70,22 @@ resource "azurerm_template_deployment" "test" { } } } - ] + ], + "outputs": { + "storageAccountName": { + "type": "string", + "value": "[variables('storageAccountName')]" + } + } } DEPLOY deployment_mode = "Incremental" } + +output "storageAccountName" { + value = "${azurerm_template_deployment.test.outputs["storageAccountName"]}" +} ``` ## Argument Reference @@ -92,13 +102,14 @@ The following arguments are supported: * `template_body` - (Optional) Specifies the JSON definition for the template. * `parameters` - (Optional) Specifies the name and value pairs that define the deployment parameters for the template. - ## Attributes Reference The following attributes are exported: * `id` - The Template Deployment ID. +* `outputs` - A map of supported scalar output types returned from the deployment (currently, Azure Template Deployment outputs of type String, Int and Bool are supported, and are converted to strings - others will be ignored) and can be accessed using `.outputs["name"]`. + ## Note Terraform does not know about the individual resources created by Azure using a deployment template and therefore cannot delete these resources during a destroy. Destroying a template deployment removes the associated deployment operations, but will not delete the Azure resources created by the deployment. In order to delete these resources, the containing resource group must also be destroyed. [More information](https://docs.microsoft.com/en-us/rest/api/resources/deployments#Deployments_Delete). diff --git a/website/source/docs/providers/gitlab/r/project_hook.html.markdown b/website/source/docs/providers/gitlab/r/project_hook.html.markdown new file mode 100644 index 000000000000..e117efc63ee7 --- /dev/null +++ b/website/source/docs/providers/gitlab/r/project_hook.html.markdown @@ -0,0 +1,59 @@ +--- +layout: "gitlab" +page_title: "GitLab: gitlab_project_hook" +sidebar_current: "docs-gitlab-resource-project-hook" +description: |- +Creates and manages hooks for GitLab projects +--- + +# gitlab\_project\_hook + +This resource allows you to create and manage hooks for your GitLab projects. +For further information on hooks, consult the [gitlab +documentation](https://docs.gitlab.com/ce/user/project/integrations/webhooks.html). + + +## Example Usage + +```hcl +resource "gitlab_project_hook" "example" { + project = "example/hooked" + url = "https://example.com/hook/example" + merge_requests_events = true +} +``` + +## Argument Reference + +The following arguments are supported: + +* `project` - (Required) The name or id of the project to add the hook to. + +* `url` - (Required) The url of the hook to invoke. + +* `token` - (Optional) A token to present when invoking the hook. + +* `enable_ssl_verification` - (Optional) Enable ssl verification when invoking +the hook. + +* `push_events` - (Optional) Invoke the hook for push events. + +* `issues_events` - (Optional) Invoke the hook for issues events. + +* `merge_requests_events` - (Optional) Invoke the hook for merge requests. + +* `tag_push_events` - (Optional) Invoke the hook for tag push events. + +* `note_events` - (Optional) Invoke the hook for tag push events. + +* `build_events` - (Optional) Invoke the hook for build events. + +* `pipeline_events` - (Optional) Invoke the hook for pipeline events. + +* `wiki_page_events` - (Optional) Invoke the hook for wiki page events. + +## Attributes Reference + +The resource exports the following attributes: + +* `id` - The unique id assigned to the hook by the GitLab server. diff --git a/website/source/docs/providers/google/r/compute_backend_bucket.html.markdown b/website/source/docs/providers/google/r/compute_backend_bucket.html.markdown new file mode 100644 index 000000000000..79f1de9703dc --- /dev/null +++ b/website/source/docs/providers/google/r/compute_backend_bucket.html.markdown @@ -0,0 +1,52 @@ +--- +layout: "google" +page_title: "Google: google_compute_backend_bucket" +sidebar_current: "docs-google-compute-backend-bucket" +description: |- + Creates a Backend Bucket resource for Google Compute Engine. +--- + +# google\_compute\_backend\_bucket + +A Backend Bucket defines a Google Cloud Storage bucket that will serve traffic through Google Cloud +Load Balancer. + +## Example Usage + +```hcl +resource "google_compute_backend_bucket" "foobar" { + name = "image-backend-bucket" + description = "Contains beautiful images" + bucket_name = "${google_storage_bucket.image_bucket.name}" + enable_cdn = true +} + +resource "google_storage_bucket" "image_bucket" { + name = "image-store-bucket" + location = "EU" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the backend bucket. + +* `bucket_name` - (Required) The name of the Google Cloud Storage bucket to be used as a backend + bucket. + +- - - + +* `description` - (Optional) The textual description for the backend bucket. + +* `enable_cdn` - (Optional) Whether or not to enable the Cloud CDN on the backend bucket. + +* `project` - (Optional) The project in which the resource belongs. If it is not provided, the + provider project is used. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `self_link` - The URI of the created resource. diff --git a/website/source/docs/providers/google/r/compute_backend_service.html.markdown b/website/source/docs/providers/google/r/compute_backend_service.html.markdown index 551c29883525..f2d36d8220c7 100644 --- a/website/source/docs/providers/google/r/compute_backend_service.html.markdown +++ b/website/source/docs/providers/google/r/compute_backend_service.html.markdown @@ -10,6 +10,8 @@ description: |- A Backend Service defines a group of virtual machines that will serve traffic for load balancing. +For internal load balancing, use a [google_compute_region_backend_service](/docs/providers/google/r/compute_region_backend_service.html). + ## Example Usage ```hcl diff --git a/website/source/docs/providers/google/r/compute_snapshot.html.markdown b/website/source/docs/providers/google/r/compute_snapshot.html.markdown new file mode 100644 index 000000000000..cdeb4fea9850 --- /dev/null +++ b/website/source/docs/providers/google/r/compute_snapshot.html.markdown @@ -0,0 +1,66 @@ +--- +layout: "google" +page_title: "Google: google_compute_snapshot" +sidebar_current: "docs-google-compute-snapshot" +description: |- + Creates a new snapshot of a disk within GCE. +--- + +# google\_compute\_snapshot + +Creates a new snapshot of a disk within GCE. + +## Example Usage + +```js +resource "google_compute_snapshot" "default" { + name = "test-snapshot" + source_disk = "test-disk" + zone = "us-central1-a" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) A unique name for the resource, required by GCE. + Changing this forces a new resource to be created. + +* `zone` - (Required) The zone where the source disk is located. + +* `source_disk` - (Required) The disk which will be used as the source of the snapshot. + +- - - + +* `source_disk_encryption_key_raw` - (Optional) A 256-bit [customer-supplied encryption key] + (https://cloud.google.com/compute/docs/disks/customer-supplied-encryption), + encoded in [RFC 4648 base64](https://tools.ietf.org/html/rfc4648#section-4) + to decrypt the source disk. + +* `snapshot_encryption_key_raw` - (Optional) A 256-bit [customer-supplied encryption key] + (https://cloud.google.com/compute/docs/disks/customer-supplied-encryption), + encoded in [RFC 4648 base64](https://tools.ietf.org/html/rfc4648#section-4) + to encrypt this snapshot. + +* `project` - (Optional) The project in which the resource belongs. If it + is not provided, the provider project is used. + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are +exported: + +* `snapshot_encryption_key_sha256` - The [RFC 4648 base64] + (https://tools.ietf.org/html/rfc4648#section-4) encoded SHA-256 hash of the + [customer-supplied encryption key](https://cloud.google.com/compute/docs/disks/customer-supplied-encryption) + that protects this resource. + +* `source_disk_encryption_key_sha256` - The [RFC 4648 base64] + (https://tools.ietf.org/html/rfc4648#section-4) encoded SHA-256 hash of the + [customer-supplied encryption key](https://cloud.google.com/compute/docs/disks/customer-supplied-encryption) + that protects the source disk. + +* `source_disk_link` - The URI of the source disk. + +* `self_link` - The URI of the created resource. diff --git a/website/source/docs/providers/google/r/compute_url_map.html.markdown b/website/source/docs/providers/google/r/compute_url_map.html.markdown index faad2a1ee9c3..f28e83816918 100644 --- a/website/source/docs/providers/google/r/compute_url_map.html.markdown +++ b/website/source/docs/providers/google/r/compute_url_map.html.markdown @@ -41,6 +41,11 @@ resource "google_compute_url_map" "foobar" { paths = ["/login"] service = "${google_compute_backend_service.login.self_link}" } + + path_rule { + paths = ["/static"] + service = "${google_compute_backend_bucket.static.self_link}" + } } test { @@ -55,7 +60,6 @@ resource "google_compute_backend_service" "login" { port_name = "http" protocol = "HTTP" timeout_sec = 10 - region = "us-central1" health_checks = ["${google_compute_http_health_check.default.self_link}"] } @@ -65,7 +69,6 @@ resource "google_compute_backend_service" "home" { port_name = "http" protocol = "HTTP" timeout_sec = 10 - region = "us-central1" health_checks = ["${google_compute_http_health_check.default.self_link}"] } @@ -76,14 +79,25 @@ resource "google_compute_http_health_check" "default" { check_interval_sec = 1 timeout_sec = 1 } + +resource "google_compute_backend_bucket" "static" { + name = "static-asset-backend-bucket" + bucket_name = "${google_storage_bucket.static.name}" + enable_cdn = true +} + +resource "google_storage_bucket" "static" { + name = "static-asset-bucket" + location = "US" +} ``` ## Argument Reference The following arguments are supported: -* `default_service` - (Required) The URL of the backend service to use when none - of the given rules match. See the documentation for formatting the service +* `default_service` - (Required) The URL of the backend service or backend bucket to use when none + of the given rules match. See the documentation for formatting the service/bucket URL [here](https://cloud.google.com/compute/docs/reference/latest/urlMaps#defaultService) @@ -118,8 +132,8 @@ The `host_rule` block supports: (This block can be defined multiple times). The `path_matcher` block supports: (This block can be defined multiple times) -* `default_service` - (Required) The URL for the backend service to use if none - of the given paths match. See the documentation for formatting the service +* `default_service` - (Required) The URL for the backend service or backend bucket to use if none + of the given paths match. See the documentation for formatting the service/bucket URL [here](https://cloud.google.com/compute/docs/reference/latest/urlMaps#pathMatcher.defaultService) * `name` - (Required) The name of the `path_matcher` resource. Used by the @@ -133,13 +147,13 @@ multiple times) * `paths` - (Required) The list of paths to match against. See the documentation for formatting these [here](https://cloud.google.com/compute/docs/reference/latest/urlMaps#pathMatchers.pathRules.paths) -* `default_service` - (Required) The URL for the backend service to use if any - of the given paths match. See the documentation for formatting the service +* `service` - (Required) The URL for the backend service or backend bucket to use if any + of the given paths match. See the documentation for formatting the service/bucket URL [here](https://cloud.google.com/compute/docs/reference/latest/urlMaps#pathMatcher.defaultService) The optional `test` block supports: (This block can be defined multiple times) -* `service` - (Required) The service that should be matched by this test. +* `service` - (Required) The backend service or backend bucket that should be matched by this test. * `host` - (Required) The host component of the URL being tested. diff --git a/website/source/docs/providers/google/r/pubsub_subscription.html.markdown b/website/source/docs/providers/google/r/pubsub_subscription.html.markdown index ce375b12d165..e5cf641d20cb 100644 --- a/website/source/docs/providers/google/r/pubsub_subscription.html.markdown +++ b/website/source/docs/providers/google/r/pubsub_subscription.html.markdown @@ -68,4 +68,4 @@ The optional `push_config` block supports: ## Attributes Reference -Only the arguments listed above are exposed as attributes. +* `path` - Path of the subscription in the format `projects/{project}/subscriptions/{sub}` diff --git a/website/source/docs/providers/google/r/storage_bucket_object.html.markdown b/website/source/docs/providers/google/r/storage_bucket_object.html.markdown index 49f7e24e05d8..38a18c00ced2 100644 --- a/website/source/docs/providers/google/r/storage_bucket_object.html.markdown +++ b/website/source/docs/providers/google/r/storage_bucket_object.html.markdown @@ -8,7 +8,7 @@ description: |- # google\_storage\_bucket\_object -Creates a new object inside an exisiting bucket in Google cloud storage service (GCS). Currently, it does not support creating custom ACLs. For more information see [the official documentation](https://cloud.google.com/storage/docs/overview) and [API](https://cloud.google.com/storage/docs/json_api). +Creates a new object inside an existing bucket in Google cloud storage service (GCS). Currently, it does not support creating custom ACLs. For more information see [the official documentation](https://cloud.google.com/storage/docs/overview) and [API](https://cloud.google.com/storage/docs/json_api). ## Example Usage @@ -31,17 +31,33 @@ The following arguments are supported: * `name` - (Required) The name of the object. -- - - +One of the following is required: * `content` - (Optional) Data as `string` to be uploaded. Must be defined if `source` is not. +* `source` - (Optional) A path to the data you want to upload. Must be defined + if `content` is not. + +- - - + +* `cache_control` - (Optional) [Cache-Control](https://tools.ietf.org/html/rfc7234#section-5.2) + directive to specify caching behavior of object data. If omitted and object is accessible to all anonymous users, the default will be public, max-age=3600 + +* `content_disposition` - (Optional) [Content-Disposition](https://tools.ietf.org/html/rfc6266) of the object data. + +* `content_encoding` - (Optional) [Content-Encoding](https://tools.ietf.org/html/rfc7231#section-3.1.2.2) of the object data. + +* `content_language` - (Optional) [Content-Language](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) of the object data. + +* `content_type` - (Optional) [Content-Type](https://tools.ietf.org/html/rfc7231#section-3.1.1.5) of the object data. Defaults to "application/octet-stream" or "text/plain; charset=utf-8". + * `predefined_acl` - (Optional, Deprecated) The [canned GCS ACL](https://cloud.google.com/storage/docs/access-control#predefined-acl) apply. Please switch to `google_storage_object_acl.predefined_acl`. - -* `source` - (Optional) A path to the data you want to upload. Must be defined - if `content` is not. +* `storage_class` - (Optional) The [StorageClass](https://cloud.google.com/storage/docs/storage-classes) of the new bucket object. + Supported values include: `MULTI_REGIONAL`, `REGIONAL`, `NEARLINE`, `COLDLINE`. If not provided, this defaults to the bucket's default + storage class or to a [standard](https://cloud.google.com/storage/docs/storage-classes#standard) class. ## Attributes Reference diff --git a/website/source/docs/providers/http/data_source.html.md b/website/source/docs/providers/http/data_source.html.md new file mode 100644 index 000000000000..a833959a470e --- /dev/null +++ b/website/source/docs/providers/http/data_source.html.md @@ -0,0 +1,51 @@ +--- +layout: "http" +page_title: "HTTP Data Source" +sidebar_current: "docs-http-data-source" +description: |- + Retrieves the content at an HTTP or HTTPS URL. +--- + +# `http` Data Source + +The `http` data source makes an HTTP GET request to the given URL and exports +information about the response. + +The given URL may be either an `http` or `https` URL. At present this resource +can only retrieve data from URLs that respond with `text/*` or +`application/json` content types, and expects the result to be UTF-8 encoded +regardless of the returned content type header. + +~> **Important** Although `https` URLs can be used, there is currently no +mechanism to authenticate the remote server except for general verification of +the server certificate's chain of trust. Data retrieved from servers not under +your control should be treated as untrustworthy. + +## Example Usage + +```hcl +data "http" "example" { + url = "https://checkpoint-api.hashicorp.com/v1/check/terraform" + + # Optional request headers + request_headers { + "Accept" = "application/json" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `url` - (Required) The URL to request data from. This URL must respond with + a `200 OK` response and a `text/*` or `application/json` Content-Type. + +* `request_headers` - (Optional) A map of strings representing additional HTTP + headers to include in the request. + +## Attributes Reference + +The following attributes are exported: + +* `body` - The raw body of the HTTP response. diff --git a/website/source/docs/providers/http/index.html.markdown b/website/source/docs/providers/http/index.html.markdown new file mode 100644 index 000000000000..c6d37f46cdf9 --- /dev/null +++ b/website/source/docs/providers/http/index.html.markdown @@ -0,0 +1,15 @@ +--- +layout: "http" +page_title: "Provider: HTTP" +sidebar_current: "docs-http-index" +description: |- + The HTTP provider interacts with HTTP servers. +--- + +# HTTP Provider + +The HTTP provider is a utility provider for interacting with generic HTTP +servers as part of a Terraform configuration. + +This provider requires no configuration. For information on the resources +it provides, see the navigation bar. diff --git a/website/source/docs/providers/kubernetes/r/limit_range.html.markdown b/website/source/docs/providers/kubernetes/r/limit_range.html.markdown new file mode 100644 index 000000000000..732e8cef0862 --- /dev/null +++ b/website/source/docs/providers/kubernetes/r/limit_range.html.markdown @@ -0,0 +1,97 @@ +--- +layout: "kubernetes" +page_title: "Kubernetes: kubernetes_limit_range" +sidebar_current: "docs-kubernetes-resource-limit-range" +description: |- + Limit Range sets resource usage limits (e.g. memory, cpu, storage) for supported kinds of resources in a namespace. +--- + +# kubernetes_limit_range + +Limit Range sets resource usage limits (e.g. memory, cpu, storage) for supported kinds of resources in a namespace. + +Read more in [the official docs](https://kubernetes.io/docs/tasks/configure-pod-container/apply-resource-quota-limit/#applying-default-resource-requests-and-limits). + + +## Example Usage + +```hcl +resource "kubernetes_limit_range" "example" { + metadata { + name = "terraform-example" + } + spec { + limit { + type = "Pod" + max { + cpu = "200m" + memory = "1024M" + } + } + limit { + type = "PersistentVolumeClaim" + min { + storage = "24M" + } + } + limit { + type = "Container" + default { + cpu = "50m" + memory = "24M" + } + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `metadata` - (Required) Standard limit range's metadata. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#metadata +* `spec` - (Optional) Spec defines the limits enforced. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status + +## Nested Blocks + +### `spec` + +#### Arguments + +* `limit` - (Optional) The list of limits that are enforced. + +### `limit` + +#### Arguments + +* `default` - (Optional) Default resource requirement limit value by resource name if resource limit is omitted. +* `default_request` - (Optional) The default resource requirement request value by resource name if resource request is omitted. +* `max` - (Optional) Max usage constraints on this kind by resource name. +* `max_limit_request_ratio` - (Optional) The named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource. +* `min` - (Optional) Min usage constraints on this kind by resource name. +* `type` - (Optional) Type of resource that this limit applies to. e.g. `Pod`, `Container` or `PersistentVolumeClaim` + +### `metadata` + +#### Arguments + +* `annotations` - (Optional) An unstructured key value map stored with the limit range that may be used to store arbitrary metadata. More info: http://kubernetes.io/docs/user-guide/annotations +* `generate_name` - (Optional) Prefix, used by the server, to generate a unique name ONLY IF the `name` field has not been provided. This value will also be combined with a unique suffix. Read more: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#idempotency +* `labels` - (Optional) Map of string keys and values that can be used to organize and categorize (scope and select) the limit range. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels +* `name` - (Optional) Name of the limit range, must be unique. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names +* `namespace` - (Optional) Namespace defines the space within which name of the limit range must be unique. + +#### Attributes + +* `generation` - A sequence number representing a specific generation of the desired state. +* `resource_version` - An opaque value that represents the internal version of this limit range that can be used by clients to determine when limit range has changed. Read more: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency +* `self_link` - A URL representing this limit range. +* `uid` - The unique in time and space value for this limit range. More info: http://kubernetes.io/docs/user-guide/identifiers#uids + +## Import + +Limit Range can be imported using its name, e.g. + +``` +$ terraform import kubernetes_limit_range.example terraform-example +``` diff --git a/website/source/docs/providers/kubernetes/r/resource_quota.html.markdown b/website/source/docs/providers/kubernetes/r/resource_quota.html.markdown new file mode 100644 index 000000000000..64982144e479 --- /dev/null +++ b/website/source/docs/providers/kubernetes/r/resource_quota.html.markdown @@ -0,0 +1,69 @@ +--- +layout: "kubernetes" +page_title: "Kubernetes: kubernetes_resource_quota" +sidebar_current: "docs-kubernetes-resource-resource-quota" +description: |- + A resource quota provides constraints that limit aggregate resource consumption per namespace. It can limit the quantity of objects that can be created in a namespace by type, as well as the total amount of compute resources that may be consumed by resources in that project. +--- + +# kubernetes_resource_quota + +A resource quota provides constraints that limit aggregate resource consumption per namespace. It can limit the quantity of objects that can be created in a namespace by type, as well as the total amount of compute resources that may be consumed by resources in that project. + + +## Example Usage + +```hcl +resource "kubernetes_resource_quota" "example" { + metadata { + name = "terraform-example" + } + spec { + hard { + pods = 10 + } + scopes = ["BestEffort"] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `metadata` - (Required) Standard resource quota's metadata. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#metadata +* `spec` - (Optional) Spec defines the desired quota. http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status + +## Nested Blocks + +### `metadata` + +#### Arguments + +* `annotations` - (Optional) An unstructured key value map stored with the resource quota that may be used to store arbitrary metadata. More info: http://kubernetes.io/docs/user-guide/annotations +* `labels` - (Optional) Map of string keys and values that can be used to organize and categorize (scope and select) the resource quota. May match selectors of replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels +* `name` - (Optional) Name of the resource quota, must be unique. Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names +* `namespace` - (Optional) Namespace defines the space within which name of the resource quota must be unique. + +#### Attributes + + +* `generation` - A sequence number representing a specific generation of the desired state. +* `resource_version` - An opaque value that represents the internal version of this resource quota that can be used by clients to determine when resource quota has changed. Read more: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency +* `self_link` - A URL representing this resource quota. +* `uid` - The unique in time and space value for this resource quota. More info: http://kubernetes.io/docs/user-guide/identifiers#uids + +### `spec` + +#### Arguments + +* `hard` - (Optional) The set of desired hard limits for each named resource. More info: http://releases.k8s.io/HEAD/docs/design/admission_control_resource_quota.md#admissioncontrol-plugin-resourcequota +* `scopes` - (Optional) A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects. + +## Import + +Resource Quota can be imported using its name, e.g. + +``` +$ terraform import kubernetes_resource_quota.example terraform-example +``` \ No newline at end of file diff --git a/website/source/docs/providers/kubernetes/r/secret.html.markdown b/website/source/docs/providers/kubernetes/r/secret.html.markdown index ba01c7203d93..ccb3f7154496 100644 --- a/website/source/docs/providers/kubernetes/r/secret.html.markdown +++ b/website/source/docs/providers/kubernetes/r/secret.html.markdown @@ -33,6 +33,22 @@ resource "kubernetes_secret" "example" { } ``` +## Example Usage (Docker config) + +```hcl +resource "kubernetes_secret" "example" { + metadata { + name = "docker-cfg" + } + + data { + ".dockercfg" = "${file("${path.module}/.docker/config.json")}" + } + + type = "kubernetes.io/dockercfg" +} +``` + ## Argument Reference The following arguments are supported: diff --git a/website/source/docs/providers/openstack/r/lb_listener_v2.html.markdown b/website/source/docs/providers/openstack/r/lb_listener_v2.html.markdown index 579d2f744712..66c3cc9c3473 100644 --- a/website/source/docs/providers/openstack/r/lb_listener_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/lb_listener_v2.html.markdown @@ -1,12 +1,12 @@ --- layout: "openstack" page_title: "OpenStack: openstack_lb_listener_v2" -sidebar_current: "docs-openstack-resource-lbaas-listener-v2" +sidebar_current: "docs-openstack-resource-lb-listener-v2" description: |- Manages a V2 listener resource within OpenStack. --- -# openstack\_lbaas\_listener\_v2 +# openstack\_lb\_listener\_v2 Manages a V2 listener resource within OpenStack. diff --git a/website/source/docs/providers/openstack/r/lb_member_v2.html.markdown b/website/source/docs/providers/openstack/r/lb_member_v2.html.markdown index ea5e7ecfb842..9f26c070eec4 100644 --- a/website/source/docs/providers/openstack/r/lb_member_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/lb_member_v2.html.markdown @@ -1,12 +1,12 @@ --- layout: "openstack" page_title: "OpenStack: openstack_lb_member_v2" -sidebar_current: "docs-openstack-resource-lbaas-member-v2" +sidebar_current: "docs-openstack-resource-lb-member-v2" description: |- Manages a V2 member resource within OpenStack. --- -# openstack\_lbaas\_member\_v2 +# openstack\_lb\_member\_v2 Manages a V2 member resource within OpenStack. diff --git a/website/source/docs/providers/openstack/r/lb_monitor_v2.html.markdown b/website/source/docs/providers/openstack/r/lb_monitor_v2.html.markdown index 86c65aa5c8bf..eeadfb6be9e1 100644 --- a/website/source/docs/providers/openstack/r/lb_monitor_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/lb_monitor_v2.html.markdown @@ -1,12 +1,12 @@ --- layout: "openstack" page_title: "OpenStack: openstack_lb_monitor_v2" -sidebar_current: "docs-openstack-resource-lbaas-monitor-v2" +sidebar_current: "docs-openstack-resource-lb-monitor-v2" description: |- Manages a V2 monitor resource within OpenStack. --- -# openstack\_lbaas\_monitor\_v2 +# openstack\_lb\_monitor\_v2 Manages a V2 monitor resource within OpenStack. diff --git a/website/source/docs/providers/openstack/r/lb_pool_v2.html.markdown b/website/source/docs/providers/openstack/r/lb_pool_v2.html.markdown index 1613ad72a924..ce0b2235f65a 100644 --- a/website/source/docs/providers/openstack/r/lb_pool_v2.html.markdown +++ b/website/source/docs/providers/openstack/r/lb_pool_v2.html.markdown @@ -1,12 +1,12 @@ --- layout: "openstack" page_title: "OpenStack: openstack_lb_pool_v2" -sidebar_current: "docs-openstack-resource-lbaas-pool-v2" +sidebar_current: "docs-openstack-resource-lb-pool-v2" description: |- Manages a V2 pool resource within OpenStack. --- -# openstack\_lbaas\_pool\_v2 +# openstack\_lb\_pool\_v2 Manages a V2 pool resource within OpenStack. diff --git a/website/source/docs/providers/template/d/file.html.md b/website/source/docs/providers/template/d/file.html.md index e28094b42809..c4be59fb5a23 100644 --- a/website/source/docs/providers/template/d/file.html.md +++ b/website/source/docs/providers/template/d/file.html.md @@ -129,7 +129,7 @@ data "template_file" "init" { ``` In the above example, the template is processed by Terraform first to -turn it into: `$${foo}:80`. After that, the template is processed as a +turn it into: `${foo}:80`. After that, the template is processed as a template to interpolate `foo`. In general, you should use template variables in the `vars` block and try diff --git a/website/source/guides/index.html.md b/website/source/guides/index.html.md new file mode 100644 index 000000000000..8eb7073844e0 --- /dev/null +++ b/website/source/guides/index.html.md @@ -0,0 +1,17 @@ +--- +layout: "guides" +page_title: "Guides" +sidebar_current: "guides-home" +description: |- + Welcome to the Terraform guides! The guides provide examples for common + Terraform workflows and actions for both beginner and advanced Terraform + users. +--- + +# Terraform Guides + +Welcome to the Terraform guides section! If you are just getting started with +Terraform, please start with the [Terraform introduction](/intro/index.html) +instead and then continue on to the guides. The guides provide examples for +common Terraform workflows and actions for both beginner and advanced Terraform +users. diff --git a/website/source/guides/writing-custom-terraform-providers.html.md b/website/source/guides/writing-custom-terraform-providers.html.md new file mode 100644 index 000000000000..fffc2cf0e4b5 --- /dev/null +++ b/website/source/guides/writing-custom-terraform-providers.html.md @@ -0,0 +1,586 @@ +--- +layout: "guides" +page_title: "Writing Custom Providers - Guides" +sidebar_current: "guides-writing-custom-terraform-providers" +description: |- + Terraform providers are easy to create and manage. This guide demonstrates + authoring a Terraform provider from scratch. +--- + +# Writing Custom Providers + +~> **This is an advanced guide!** Following this guide is not required for +regular use of Terraform and is only intended for advance users or Terraform +contributors. + +In Terraform, a "provider" is the logical abstraction of an upstream API. This +guide details how to build a custom provider for Terraform. + +## Why? + +There are a few possible reasons for authoring a custom Terraform provider, such +as: + +- An internal private cloud whose functionality is either proprietary or would + not benefit the open source community. + +- A "work in progress" provider being tested locally before contributing back. + +- Extensions of an existing provider + +## Local Setup + +Terraform supports a plugin model, and all providers are actually plugins. +Plugins are distributed as Go binaries. Although technically possible to write a +plugin in another language, almost all Terraform plugins are written in +[Go](https://golang.org). For more information on installing and configuring Go, +please visit the [Golang installation guide](https://golang.org/doc/install). + +This post assumes familiarity with Golang and basic programming concepts. + +As a reminder, all of Terraform's core providers are open source. When stuck or +looking for examples, please feel free to reference +[the open source providers](https://github.com/hashicorp/terraform/tree/master/builtin/providers) for help. + +## The Provider Schema + +To start, create a file named `provider.go`. This is the root of the provider +and should include the following boilerplate code: + +```go +package main + +import ( + "github.com/hashicorp/terraform/helper/schema" +) + +func Provider() *schema.Provider { + return &schema.Provider{ + ResourcesMap: map[string]*schema.Resource{}, + } +} +``` + +The +[`helper/schema`](https://godoc.org/github.com/hashicorp/terraform/helper/schema) +library is part of Terraform's core. It abstracts many of the complexities and +ensures consistency between providers. The example above defines an empty provider (there are no _resources_). + +The `*schema.Provider` type describes the provider's properties including: + +- the configuration keys it accepts +- the resources it supports +- any callbacks to configure + +## Building the Plugin + +Go requires a `main.go` file, which is the default executable when the binary is +built. Since Terraform plugins are distributed as Go binaries, it is important +to define this entry-point with the following code: + +```go +package main + +import ( + "github.com/hashicorp/terraform/plugin" + "github.com/hashicorp/terraform/terraform" +) + +func main() { + plugin.Serve(&plugin.ServeOpts{ + ProviderFunc: func() terraform.ResourceProvider { + return Provider() + }, + }) +} +``` + +This establishes the main function to produce a valid, executable Go binary. The +contents of the main function consume Terraform's `plugin` library. This library +deals with all the communication between Terraform core and the plugin. + +Next, build the plugin using the Go toolchain: + +```shell +$ go build -o terraform-provider-example +``` + +The output name (`-o`) is **very important**. Terraform searches for plugins in +the format of: + +```text +terraform-- +``` + +In the case above, the plugin is of type "provider" and of name "example". + +To verify things are working correctly, execute the binary just created: + +```shell +$ ./terraform-provider-example +This binary is a plugin. These are not meant to be executed directly. +Please execute the program that consumes these plugins, which will +load any plugins automatically +``` + +This is the basic project structure and scaffolding for a Terraform plugin. To +recap, the file structure is: + +```text +. +├── main.go +└── provider.go +``` + +## Defining Resources + +Terraform providers manage resources. A provider is an abstraction of an +upstream API, and a resource is a component of that provider. As an example, the +AWS provider supports `aws_instance` and `aws_elastic_ip`. DNSimple supports +`dnsimple_record`. Fastly supports `fastly_service`. Let's add a resource to our +fictitious provider. + +As a general convention, Terraform providers put each resource in their own +file, named after the resource, prefixed with `resource_`. To create an +`example_server`, this would be `resource_server.go` by convention: + +```go +package main + +import ( + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceServer() *schema.Resource { + return &schema.Resource{ + Create: resourceServerCreate, + Read: resourceServerRead, + Update: resourceServerUpdate, + Delete: resourceServerDelete, + + Schema: map[string]*schema.Schema{ + "address": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +``` + +This uses the +[`schema.Resource` type](https://godoc.org/github.com/hashicorp/terraform/helper/schema#Resource). +This structure defines the data schema and CRUD operations for the resource. +Defining these properties are the only required thing to create a resource. + +The schema above defines one element, `"address"`, which is a required string. +Terraform's schema automatically enforces validation and type casting. + +Next there are four "fields" defined - `Create`, `Read`, `Update`, and `Delete`. +The `Create`, `Read`, and `Delete` functions are required for a resource to be +functional. There are other functions, but these are the only required ones. +Terraform itself handles which function to call and with what data. Based on the +schema and current state of the resource, Terraform can determine whether it +needs to create a new resource, update an existing one, or destroy. + +Each of the four struct fields point to a function. While it is technically +possible to inline all functions in the resource schema, best practice dictates +pulling each function into its own method. This optimizes for both testing and +readability. Fill in those stubs now, paying close attention to method +signatures. + +```golang +func resourceServerCreate(d *schema.ResourceData, m interface{}) error { + return nil +} + +func resourceServerRead(d *schema.ResourceData, m interface{}) error { + return nil +} + +func resourceServerUpdate(d *schema.ResourceData, m interface{}) error { + return nil +} + +func resourceServerDelete(d *schema.ResourceData, m interface{}) error { + return nil +} +``` + +Lastly, update the provider schema in `provider.go` to register this new resource. + +```golang +func Provider() *schema.Provider { + return &schema.Provider{ + ResourcesMap: map[string]*schema.Resource{ + "example_server": resourceServer(), + }, + } +} +``` + +Build and test the plugin. Everything should compile as-is, although all +operations are a no-op. + +```shell +$ go build -o terraform-provider-example + +$ ./terraform-provider-example +This binary is a plugin. These are not meant to be executed directly. +Please execute the program that consumes these plugins, which will +load any plugins automatically +``` + +The layout now looks like this: + +```text +. +├── main.go +├── provider.go +├── resource_server.go +└── terraform-provider-example +``` + +## Invoking the Provider + +Previous sections showed running the provider directly via the shell, which +outputs a warning message like: + +```text +This binary is a plugin. These are not meant to be executed directly. +Please execute the program that consumes these plugins, which will +load any plugins automatically +``` + +Terraform plugins should be executed by Terraform directly. To test this, create +a `main.tf` in the working directory (the same place where the plugin exists). + +```hcl +resource "example_server" "my-server" {} +``` + +And execute `terraform plan`: + +```text +$ terraform plan + +1 error(s) occurred: + +* example_server.my-server: "address": required field is not set +``` + +This validates Terraform is correctly delegating work to our plugin and that our +validation is working as intended. Fix the validation error by adding an +`address` field to the resource: + +```hcl +resource "example_server" "my-server" { + address = "1.2.3.4" +} +``` + +Execute `terraform plan` to verify the validation is passing: + +```text +$ terraform plan + ++ example_server.my-server + address: "1.2.3.4" + + +Plan: 1 to add, 0 to change, 0 to destroy. +``` + +It is possible to run `terraform apply`, but it will be a no-op because all of +the resource options currently take no action. + +## Implement Create + +Back in `resource_server.go`, implement the create functionality: + +```go +func resourceServerCreate(d *schema.ResourceData, m interface{}) error { + address := d.Get("address").(string) + d.SetId(address) + return nil +} +``` + +This uses the [`schema.ResourceData +API`](https://godoc.org/github.com/hashicorp/terraform/helper/schema#ResourceData) +to get the value of `"address"` provided by the user in the Terraform +configuration. Due to the way Go works, we have to typecast it to string. This +is a safe operation, however, since our schema guarantees it will be a string +type. + +Next, it uses `SetId`, a built-in function, to set the ID of the resource to the +address. The existence of a non-blank ID is what tells Terraform that a resource +was created. This ID can be any string value, but should be a value that can be +used to read the resource again. + +Recompile the binary, the run `terraform plan` and `terraform apply`. + +```shell +$ go build -o terraform-provider-example +# ... +``` + +```text +$ terraform plan + ++ example_server.my-server + address: "1.2.3.4" + + +Plan: 1 to add, 0 to change, 0 to destroy. +``` + +```text +$ terraform apply + +example_server.my-server: Creating... + address: "" => "1.2.3.4" +example_server.my-server: Creation complete (ID: 1.2.3.4) + +Apply complete! Resources: 1 added, 0 changed, 0 destroyed. +``` + +Since the `Create` operation used `SetId`, Terraform believes the resource created successfully. Verify this by running `terraform plan`. + +```text +$ terraform plan +Refreshing Terraform state in-memory prior to plan... +The refreshed state will be used to calculate this plan, but will not be +persisted to local or remote state storage. + +example_server.my-server: Refreshing state... (ID: 1.2.3.4) +No changes. Infrastructure is up-to-date. + +This means that Terraform did not detect any differences between your +configuration and real physical resources that exist. As a result, Terraform +doesn't need to do anything. +``` + +Again, because of the call to `SetId`, Terraform believes the resource was +created. When running `plan`, Terraform properly determines there are no changes +to apply. + +To verify this behavior, change the value of the `address` field and run +`terraform plan` again. You should see output like this: + +```text +$ terraform plan +example_server.my-server: Refreshing state... (ID: 1.2.3.4) + +~ example_server.my-server + address: "1.2.3.4" => "5.6.7.8" + + +Plan: 0 to add, 1 to change, 0 to destroy. +``` + +Terraform detects the change and displays a diff with a `~` prefix, noting the +resource will be modified in place, rather than created new. + +Run `terraform apply` to apply the changes. + +```text +$ terraform apply +example_server.my-server: Refreshing state... (ID: 1.2.3.4) +example_server.my-server: Modifying... (ID: 1.2.3.4) + address: "1.2.3.4" => "5.6.7.8" +example_server.my-server: Modifications complete (ID: 1.2.3.4) + +Apply complete! Resources: 0 added, 1 changed, 0 destroyed. +``` + +Since we did not implement the `Update` function, you would expect the +`terraform plan` operation to report changes, but it does not! How were our +changes persisted without the `Update` implementation? + +## Error Handling & Partial State + +Previously our `Update` operation succeeded and persisted the new state with an +empty function definition. Recall the current update function: + +```golang +func resourceServerUpdate(d *schema.ResourceData, m interface{}) error { + return nil +} +``` + +The `return nil` tells Terraform that the update operation succeeded without +error. Terraform assumes this means any changes requested applied without error. +Because of this, our state updated and Terraform believes there are no further +changes. + +To say it another way: if a callback returns no error, Terraform automatically +assumes the entire diff successfully applied, merges the diff into the final +state, and persists it. + +Functions should _never_ intentionally `panic` or call `os.Exit` - always return +an error. + +In reality, it is a bit more complicated than this. Imagine the scenario where +our update function has to update two separate fields which require two separate +API calls. What do we do if the first API call succeeds but the second fails? +How do we properly tell Terraform to only persist half the diff? This is known +as a _partial state_ scenario, and implementing these properly is critical to a +well-behaving provider. + +Here are the rules for state updating in Terraform. Note that this mentions +callbacks we have not discussed, for the sake of completeness. + +- If the `Create` callback returns with or without an error without an ID set + using `SetId`, the resource is assumed to not be created, and no state is + saved. + +- If the `Create` callback returns with or without an error and an ID has been + set, the resource is assumed created and all state is saved with it. Repeating + because it is important: if there is an error, but the ID is set, the state is + fully saved. + +- If the `Update` callback returns with or without an error, the full state is + saved. If the ID becomes blank, the resource is destroyed (even within an + update, though this shouldn't happen except in error scenarios). + +- If the `Destroy` callback returns without an error, the resource is assumed to + be destroyed, and all state is removed. + +- If the `Destroy` callback returns with an error, the resource is assumed to + still exist, and all prior state is preserved. + +- If partial mode (covered next) is enabled when a create or update returns, + only the explicitly enabled configuration keys are persisted, resulting in a + partial state. + +_Partial mode_ is a mode that can be enabled by a callback that tells Terraform +that it is possible for partial state to occur. When this mode is enabled, the +provider must explicitly tell Terraform what is safe to persist and what is not. + +Here is an example of a partial mode with an update function: + +```go +func resourceServerUpdate(d *schema.ResourceData, m interface{}) error { + // Enable partial state mode + d.Partial(true) + + if d.HasChange("address") { + // Try updating the address + if err := updateAddress(d, meta); err != nil { + return err + } + + d.SetPartial("address") + } + + // If we were to return here, before disabling partial mode below, + // then only the "address" field would be saved. + + // We succeeded, disable partial mode. This causes Terraform to save + // save all fields again. + d.Partial(false) + + return nil +} +``` + +Note - this code will not compile since there is no `updateAddress` function. +You can implement a dummy version of this function to play around with partial +state. For this example, partial state does not mean much in this documentation +example. If `updateAddress` were to fail, then the address field would not be +updated. + +## Implementing Destroy + +The `Destroy` callback is exactly what it sounds like - it is called to destroy +the resource. This operation should never update any state on the resource. It +is not necessary to call `d.SetId("")`, since any non-error return value assumes +the resource was deleted successfully. + +```go +func resourceServerDelete(d *schema.ResourceData, m interface{}) error { + // d.SetId("") is automatically called assuming delete returns no errors, but + // it is added here for explicitness. + d.SetId("") + return nil +} +``` + +The destroy function should always handle the case where the resource might +already be destroyed (manually, for example). If the resource is already +destroyed, this should not return an error. This allows Terraform users to +manually delete resources without breaking Terraform. + +```shell +$ go build -o terraform-provider-example +``` + +Run `terraform destroy` to destroy the resource. + +```text +$ terraform destroy +Do you really want to destroy? + Terraform will delete all your managed infrastructure. + There is no undo. Only 'yes' will be accepted to confirm. + + Enter a value: yes + +example_server.my-server: Refreshing state... (ID: 5.6.7.8) +example_server.my-server: Destroying... (ID: 5.6.7.8) +example_server.my-server: Destruction complete + +Destroy complete! Resources: 1 destroyed. +``` + +## Implementing Read + +The `Read` callback is used to sync the local state with the actual state +(upstream). This is called at various points by Terraform and should be a +read-only operation. This callback should never modify the real resource. + +If the ID is updated to blank, this tells Terraform the resource no longer +exists (maybe it was destroyed out of band). Just like the destroy callback, the +`Read` function should gracefully handle this case. + +```go +func resourceServerRead(d *schema.ResourceData, m interface{}) error { + client := meta.(*MyClient) + + // Attempt to read from an upstream API + obj, ok := client.Get(d.Id()) + + // If the resource does not exist, inform Terraform. We want to immediately + // return here to prevent further processing. + if !ok { + d.SetId("") + return nil + } + + d.Set("address", obj.Address) + return nil +} +``` + +## Next Steps + +This guide covers the schema and structure for implementing a Terraform provider +using the provider framework. As next steps, reference the internal providers +for examples. Terraform also includes a full framework for testing frameworks. + +## General Rules + +### Dedicated Upstream Libraries + +One of the biggest mistakes new users make is trying to conflate a client +library with the Terraform implementation. Terraform should always consume an +independent client library which implements the core logic for communicating +with the upstream. Do not try to implement this type of logic in the provider +itself. + +### Data Sources + +While not explicitly discussed here, _data sources_ are a special subset of +resources which are read-only. They are resolved earlier than regular resources +and can be used as part of Terraform's interpolation. diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 71ee8b80a757..5c3ac37ac8ce 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -26,6 +26,9 @@ > aws_ami + > + aws_ami_ids + > aws_autoscaling_groups @@ -35,6 +38,9 @@ > aws_availability_zones + > + aws_billing_service_account + > aws_caller_identity @@ -50,6 +56,9 @@ > aws_ebs_snapshot + > + aws_ebs_snapshot_ids + > aws_ebs_volume @@ -65,6 +74,9 @@ > aws_efs_file_system + > + aws_eip + > aws_elb_hosted_zone_id @@ -72,7 +84,7 @@ aws_elb_service_account > - kinesis_stream + aws_kinesis_stream > aws_iam_account_alias @@ -92,6 +104,9 @@ > aws_ip_ranges + > + aws_kms_alias + > aws_kms_secret @@ -1249,6 +1264,18 @@ aws_ssm_document + > + aws_ssm_maintenance_window + + + > + aws_ssm_maintenance_window_target + + + > + aws_ssm_maintenance_window_task + + @@ -1319,7 +1346,9 @@ > aws_network_interface - + > + aws_network_interface_attachment + > aws_route diff --git a/website/source/layouts/azurerm.erb b/website/source/layouts/azurerm.erb index 037498a0465d..80b38a2754e3 100644 --- a/website/source/layouts/azurerm.erb +++ b/website/source/layouts/azurerm.erb @@ -264,6 +264,10 @@ azurerm_sql_database + > + azurerm_sql_elasticpool + + > azurerm_sql_firewall_rule diff --git a/website/source/layouts/commands-state.erb b/website/source/layouts/commands-state.erb index ca4e8bcd7977..8e99522ab94b 100644 --- a/website/source/layouts/commands-state.erb +++ b/website/source/layouts/commands-state.erb @@ -3,7 +3,7 @@

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 `VN9(%U=hBvKJU&Ro;E4 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 bdy0B)ZT8A12xDB@0`Pphi?Fi>ACvhH%|s Date: Tue, 9 May 2017 20:26:50 -0500 Subject: [PATCH 67/67] merging with hashicorp master --- CHANGELOG.md | 75 +- builtin/providers/aws/autoscaling_tags.go | 2 +- builtin/providers/aws/config.go | 54 +- .../aws/data_source_aws_iam_account_alias.go | 1 + .../data_source_aws_iam_account_alias_test.go | 43 - .../aws/data_source_aws_security_group.go | 14 +- .../data_source_aws_security_group_test.go | 6 + .../aws/import_aws_iam_account_alias_test.go | 2 +- .../providers/aws/import_aws_iam_role_test.go | 8 +- builtin/providers/aws/provider.go | 85 +- .../providers/aws/resource_aws_alb_test.go | 96 + .../resource_aws_cloudwatch_event_target.go | 89 +- ...source_aws_cloudwatch_event_target_test.go | 148 +- .../aws/resource_aws_cloudwatch_log_group.go | 14 +- .../aws/resource_aws_config_config_rule.go | 14 +- .../aws/resource_aws_db_option_group.go | 9 + .../aws/resource_aws_db_option_group_test.go | 10 +- ...ource_aws_elasticache_replication_group.go | 48 +- ..._aws_elasticache_replication_group_test.go | 179 + .../providers/aws/resource_aws_elb_test.go | 47 +- .../resource_aws_iam_account_alias_test.go | 81 +- .../providers/aws/resource_aws_iam_role.go | 81 +- .../aws/resource_aws_iam_role_test.go | 163 +- .../aws/resource_aws_kinesis_stream.go | 20 +- .../aws/resource_aws_kinesis_stream_test.go | 109 +- .../resource_aws_route53_health_check_test.go | 2 +- .../aws/resource_aws_route53_record.go | 4 +- .../aws/resource_aws_route53_record_test.go | 34 + .../aws/resource_aws_ses_domain_identity.go | 10 +- .../resource_aws_ses_domain_identity_test.go | 33 +- .../aws/resource_aws_spot_fleet_request.go | 16 +- .../resource_aws_spot_fleet_request_test.go | 120 + .../aws/resource_aws_ssm_association.go | 53 +- .../aws/resource_aws_ssm_association_test.go | 72 +- .../aws/resource_aws_ssm_document.go | 19 +- .../resource_aws_ssm_maintenance_window.go | 168 + ...ource_aws_ssm_maintenance_window_target.go | 143 + ..._aws_ssm_maintenance_window_target_test.go | 122 + ...esource_aws_ssm_maintenance_window_task.go | 230 ++ ...ce_aws_ssm_maintenance_window_task_test.go | 158 + ...esource_aws_ssm_maintenance_window_test.go | 141 + builtin/providers/aws/s3_tags.go | 2 +- builtin/providers/aws/s3_tags_test.go | 26 - builtin/providers/aws/structure.go | 100 +- builtin/providers/aws/structure_test.go | 4 + builtin/providers/aws/tags.go | 4 +- builtin/providers/aws/tagsBeanstalk.go | 2 +- builtin/providers/aws/tagsCloudtrail.go | 2 +- builtin/providers/aws/tagsCodeBuild.go | 2 +- builtin/providers/aws/tagsEC.go | 2 +- builtin/providers/aws/tagsEFS.go | 2 +- builtin/providers/aws/tagsELB.go | 2 +- builtin/providers/aws/tagsGeneric.go | 2 +- builtin/providers/aws/tagsInspector.go | 2 +- builtin/providers/aws/tagsKMS.go | 2 +- builtin/providers/aws/tagsRDS.go | 2 +- builtin/providers/aws/tagsRedshift.go | 2 +- .../aws/tags_elasticsearchservice.go | 2 +- builtin/providers/aws/tags_kinesis.go | 2 +- builtin/providers/aws/tags_route53.go | 2 +- builtin/providers/aws/validators.go | 28 +- builtin/providers/aws/validators_test.go | 23 + builtin/providers/azurerm/config.go | 9 + .../import_arm_sql_elasticpool_test.go | 32 + builtin/providers/azurerm/provider.go | 1 + .../azurerm/resource_arm_sql_database.go | 5 + .../azurerm/resource_arm_sql_database_test.go | 58 + .../azurerm/resource_arm_sql_elasticpool.go | 220 + .../resource_arm_sql_elasticpool_test.go | 168 + .../resource_arm_template_deployment.go | 31 +- .../resource_arm_template_deployment_test.go | 156 +- .../dns/data_dns_a_record_set_test.go | 35 +- .../dns/data_dns_cname_record_set_test.go | 8 + .../providers/dns/data_dns_txt_record_set.go | 3 + .../dns/data_dns_txt_record_set_test.go | 59 + .../dns/test_check_attr_string_array.go | 21 +- .../test_check_attr_string_array_member.go | 39 + builtin/providers/dyn/config.go | 6 +- builtin/providers/gitlab/provider.go | 3 +- .../gitlab/resource_gitlab_project_hook.go | 192 + .../resource_gitlab_project_hook_test.go | 220 + .../google/import_compute_route_test.go | 47 + .../google/import_dns_managed_zone_test.go | 28 + builtin/providers/google/provider.go | 15 + .../google/resource_compute_autoscaler.go | 2 +- .../google/resource_compute_backend_bucket.go | 201 + .../resource_compute_backend_bucket_test.go | 191 + .../resource_compute_backend_service.go | 12 +- .../resource_compute_backend_service_test.go | 4 +- .../google/resource_compute_instance.go | 33 +- ...resource_compute_instance_group_manager.go | 2 +- .../google/resource_compute_route.go | 3 + .../google/resource_compute_snapshot.go | 210 + .../google/resource_compute_snapshot_test.go | 183 + .../google/resource_dns_managed_zone.go | 7 +- .../resource_google_project_services_test.go | 2 + .../google/resource_google_service_account.go | 3 +- .../google/resource_pubsub_subscription.go | 6 + .../resource_pubsub_subscription_test.go | 3 +- .../google/resource_storage_bucket_object.go | 76 + .../resource_storage_bucket_object_test.go | 158 +- .../heroku/import_heroku_app_test.go | 58 + .../providers/heroku/resource_heroku_app.go | 22 + builtin/providers/http/data_source.go | 104 + builtin/providers/http/data_source_test.go | 166 + builtin/providers/http/provider.go | 18 + builtin/providers/http/provider_test.go | 18 + builtin/providers/kubernetes/provider.go | 2 + .../resource_kubernetes_limit_range.go | 188 + .../resource_kubernetes_limit_range_test.go | 475 +++ .../resource_kubernetes_resource_quota.go | 211 + ...resource_kubernetes_resource_quota_test.go | 352 ++ builtin/providers/kubernetes/structures.go | 183 +- builtin/providers/kubernetes/validators.go | 19 +- builtin/providers/nomad/resource_job_test.go | 25 +- ...enstack_compute_floatingip_associate_v2.go | 2 +- .../profitbricks/data_source_image_test.go | 2 +- .../resource_profitbricks_server.go | 42 +- .../resource_profitbricks_volume.go | 30 +- builtin/providers/vault/provider.go | 1 + .../providers/vault/resource_auth_backend.go | 121 + .../vault/resource_auth_backend_test.go | 129 + command/internal_plugin_list.go | 2 + config/interpolate_funcs.go | 44 + config/interpolate_funcs_test.go | 57 + config/loader_hcl.go | 55 +- config/loader_test.go | 16 +- config/test-fixtures/module-unnamed.tf | 7 + config/test-fixtures/output-unnamed.tf | 4 + config/test-fixtures/variable-no-name.tf | 3 + docs/maintainer-etiquette.md | 91 + .../README.md | 26 - .../deploy.ci.sh | 36 - .../deploy.mac.sh | 15 - .../azure-2-vms-loadbalancer-lbrules/main.tf | 138 - .../outputs.tf | 11 - .../provider.tf | 6 - .../terraform.tfvars | 6 - .../variables.tf | 80 - .../deploy.ci.sh | 4 +- .../deploy.mac.sh | 2 +- .../azure-cdn-with-storage-account/graph.png | Bin 0 -> 69826 bytes .../azure-cdn-with-storage-account/main.tf | 7 + .../provider.tf.example | 7 - .../terraform.tfvars | 8 - examples/azure-vm-from-user-image/README.md | 28 - .../azure-vm-from-user-image/deploy.ci.sh | 45 - .../azure-vm-from-user-image/deploy.mac.sh | 18 - examples/azure-vm-from-user-image/main.tf | 66 - examples/azure-vm-from-user-image/outputs.tf | 11 - examples/azure-vm-from-user-image/provider.tf | 6 - .../azure-vm-from-user-image/terraform.tfvars | 13 - .../azure-vm-from-user-image/variables.tf | 56 - .../README.md | 8 +- .../after_deploy.ci.sh | 9 - .../deploy.mac.sh | 2 +- .../main.tf | 11 +- .../outputs.tf | 4 +- .../provider.tf | 7 - .../terraform.tfvars | 8 - examples/azure-vnet-two-subnets/README.md | 6 +- examples/azure-vnet-two-subnets/deploy.ci.sh | 2 +- examples/azure-vnet-two-subnets/deploy.mac.sh | 2 +- examples/azure-vnet-two-subnets/main.tf | 7 + .../provider.tf.example | 6 - helper/schema/serialize.go | 3 + helper/schema/set_test.go | 17 + main.go | 9 + synchronized_writers.go | 31 + terraform/context_plan_test.go | 101 + terraform/interpolate.go | 8 - terraform/interpolate_test.go | 86 +- .../main.tf | 3 + .../plan-count-splat-reference/main.tf | 9 + .../Azure/azure-sdk-for-go/arm/sql/client.go | 59 + .../azure-sdk-for-go/arm/sql/databases.go | 934 +++++ .../azure-sdk-for-go/arm/sql/elasticpools.go | 582 +++ .../Azure/azure-sdk-for-go/arm/sql/models.go | 810 ++++ .../arm/sql/recommendedelasticpools.go | 380 ++ .../Azure/azure-sdk-for-go/arm/sql/servers.go | 553 +++ .../Azure/azure-sdk-for-go/arm/sql/version.go | 60 + vendor/github.com/hashicorp/hcl/appveyor.yml | 2 +- .../hashicorp/hcl/hcl/parser/parser.go | 6 + .../hashicorp/hcl/hcl/printer/printer.go | 1 - .../hashicorp/hcl/json/parser/parser.go | 2 +- .../github.com/hashicorp/hil/ast/literal.go | 9 + .../hashicorp/hil/ast/variables_helper.go | 57 +- .../github.com/hashicorp/hil/check_types.go | 57 +- vendor/github.com/hashicorp/hil/eval.go | 58 +- .../profitbricks/profitbricks-sdk-go/nic.go | 2 +- .../profitbricks-sdk-go/request.go | 69 + .../api/compute/v1/compute-api.json | 880 +++- .../api/compute/v1/compute-gen.go | 3587 ++++++++++++----- vendor/vendor.json | 92 +- website/README.md | 5 +- .../docs/tfe-organization-variables.png | Bin 0 -> 19766 bytes .../assets/images/docs/tfe-variables.png | Bin 0 -> 118684 bytes .../source/assets/stylesheets/_global.scss | 8 + .../docs/configuration/interpolation.html.md | 29 +- .../source/docs/enterprise/faq/index.html.md | 2 + .../faq/vagrant-cloud-migration.html.md | 23 + .../runs/variables-and-configuration.html.md | 69 +- .../source/docs/import/importability.html.md | 1 + ... => billing_service_account.html.markdown} | 0 .../providers/aws/d/iam_role.html.markdown | 2 +- .../aws/d/security_group.html.markdown | 6 +- .../docs/providers/aws/index.html.markdown | 32 + ...app_cookie_stickiness_policy.html.markdown | 2 +- .../r/cloudwatch_event_target.html.markdown | 8 + .../r/cloudwatch_metric_alarm.html.markdown | 4 +- .../aws/r/config_config_rule.html.markdown | 2 +- .../aws/r/db_option_group.html.markdown | 6 +- .../aws/r/default_route_table.html.markdown | 2 +- ...lasticache_replication_group.html.markdown | 26 +- .../providers/aws/r/iam_role.html.markdown | 2 + .../aws/r/kinesis_stream.html.markdown | 9 + ...etwork_interface_attachment.html.markdown} | 0 .../aws/r/route53_health_check.html.markdown | 2 +- .../aws/r/route53_record.html.markdown | 13 +- .../aws/r/security_group.html.markdown | 6 +- .../aws/r/ses_configuration_set.markdown | 2 +- .../aws/r/ses_domain_identity.html.markdown | 8 +- .../spot_datafeed_subscription.html.markdown | 2 +- .../aws/r/spot_fleet_request.html.markdown | 9 +- .../aws/r/ssm_association.html.markdown | 5 +- .../r/ssm_maintenance_window.html.markdown | 38 + ...sm_maintenance_window_target.html.markdown | 46 + .../ssm_maintenance_window_task.html.markdown | 68 + .../azurerm/r/sql_elasticpool.html.markdown | 75 + .../r/template_deployment.html.markdown | 15 +- .../gitlab/r/project_hook.html.markdown | 59 + .../r/compute_backend_bucket.html.markdown | 52 + .../r/compute_backend_service.html.markdown | 2 + .../google/r/compute_snapshot.html.markdown | 66 + .../google/r/compute_url_map.html.markdown | 32 +- .../r/pubsub_subscription.html.markdown | 2 +- .../r/storage_bucket_object.html.markdown | 26 +- .../docs/providers/http/data_source.html.md | 51 + .../docs/providers/http/index.html.markdown | 15 + .../kubernetes/r/limit_range.html.markdown | 97 + .../kubernetes/r/resource_quota.html.markdown | 69 + .../kubernetes/r/secret.html.markdown | 16 + .../openstack/r/lb_listener_v2.html.markdown | 4 +- .../openstack/r/lb_member_v2.html.markdown | 4 +- .../openstack/r/lb_monitor_v2.html.markdown | 4 +- .../openstack/r/lb_pool_v2.html.markdown | 4 +- .../docs/providers/template/d/file.html.md | 2 +- website/source/guides/index.html.md | 17 + ...writing-custom-terraform-providers.html.md | 586 +++ website/source/layouts/aws.erb | 33 +- website/source/layouts/azurerm.erb | 4 + website/source/layouts/commands-state.erb | 2 +- website/source/layouts/docs.erb | 4 + website/source/layouts/enterprise.erb | 4 + website/source/layouts/google.erb | 10 +- website/source/layouts/guides.erb | 11 + website/source/layouts/http.erb | 22 + website/source/layouts/kubernetes.erb | 10 +- website/source/layouts/layout.erb | 1 + 259 files changed, 16276 insertions(+), 2425 deletions(-) delete mode 100644 builtin/providers/aws/data_source_aws_iam_account_alias_test.go create mode 100644 builtin/providers/aws/resource_aws_ssm_maintenance_window.go create mode 100644 builtin/providers/aws/resource_aws_ssm_maintenance_window_target.go create mode 100644 builtin/providers/aws/resource_aws_ssm_maintenance_window_target_test.go create mode 100644 builtin/providers/aws/resource_aws_ssm_maintenance_window_task.go create mode 100644 builtin/providers/aws/resource_aws_ssm_maintenance_window_task_test.go create mode 100644 builtin/providers/aws/resource_aws_ssm_maintenance_window_test.go create mode 100644 builtin/providers/azurerm/import_arm_sql_elasticpool_test.go create mode 100644 builtin/providers/azurerm/resource_arm_sql_elasticpool.go create mode 100644 builtin/providers/azurerm/resource_arm_sql_elasticpool_test.go create mode 100644 builtin/providers/dns/data_dns_txt_record_set_test.go create mode 100644 builtin/providers/dns/test_check_attr_string_array_member.go create mode 100644 builtin/providers/gitlab/resource_gitlab_project_hook.go create mode 100644 builtin/providers/gitlab/resource_gitlab_project_hook_test.go create mode 100644 builtin/providers/google/import_compute_route_test.go create mode 100644 builtin/providers/google/import_dns_managed_zone_test.go create mode 100644 builtin/providers/google/resource_compute_backend_bucket.go create mode 100644 builtin/providers/google/resource_compute_backend_bucket_test.go create mode 100644 builtin/providers/google/resource_compute_snapshot.go create mode 100644 builtin/providers/google/resource_compute_snapshot_test.go create mode 100644 builtin/providers/heroku/import_heroku_app_test.go create mode 100644 builtin/providers/http/data_source.go create mode 100644 builtin/providers/http/data_source_test.go create mode 100644 builtin/providers/http/provider.go create mode 100644 builtin/providers/http/provider_test.go create mode 100644 builtin/providers/kubernetes/resource_kubernetes_limit_range.go create mode 100644 builtin/providers/kubernetes/resource_kubernetes_limit_range_test.go create mode 100644 builtin/providers/kubernetes/resource_kubernetes_resource_quota.go create mode 100644 builtin/providers/kubernetes/resource_kubernetes_resource_quota_test.go create mode 100644 builtin/providers/vault/resource_auth_backend.go create mode 100644 builtin/providers/vault/resource_auth_backend_test.go create mode 100644 config/test-fixtures/module-unnamed.tf create mode 100644 docs/maintainer-etiquette.md delete mode 100644 examples/azure-2-vms-loadbalancer-lbrules/README.md delete mode 100755 examples/azure-2-vms-loadbalancer-lbrules/deploy.ci.sh delete mode 100755 examples/azure-2-vms-loadbalancer-lbrules/deploy.mac.sh delete mode 100644 examples/azure-2-vms-loadbalancer-lbrules/main.tf delete mode 100644 examples/azure-2-vms-loadbalancer-lbrules/outputs.tf delete mode 100644 examples/azure-2-vms-loadbalancer-lbrules/provider.tf delete mode 100644 examples/azure-2-vms-loadbalancer-lbrules/terraform.tfvars delete mode 100644 examples/azure-2-vms-loadbalancer-lbrules/variables.tf create mode 100644 examples/azure-cdn-with-storage-account/graph.png delete mode 100644 examples/azure-cdn-with-storage-account/provider.tf.example delete mode 100644 examples/azure-cdn-with-storage-account/terraform.tfvars delete mode 100644 examples/azure-vm-from-user-image/README.md delete mode 100755 examples/azure-vm-from-user-image/deploy.ci.sh delete mode 100755 examples/azure-vm-from-user-image/deploy.mac.sh delete mode 100644 examples/azure-vm-from-user-image/main.tf delete mode 100644 examples/azure-vm-from-user-image/outputs.tf delete mode 100644 examples/azure-vm-from-user-image/provider.tf delete mode 100644 examples/azure-vm-from-user-image/terraform.tfvars delete mode 100644 examples/azure-vm-from-user-image/variables.tf delete mode 100755 examples/azure-vm-simple-linux-managed-disk/after_deploy.ci.sh delete mode 100644 examples/azure-vm-simple-linux-managed-disk/provider.tf delete mode 100644 examples/azure-vm-simple-linux-managed-disk/terraform.tfvars delete mode 100644 examples/azure-vnet-two-subnets/provider.tf.example create mode 100644 synchronized_writers.go create mode 100644 terraform/test-fixtures/interpolate-resource-variable-multi/main.tf create mode 100644 terraform/test-fixtures/plan-count-splat-reference/main.tf create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/arm/sql/client.go create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/arm/sql/databases.go create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/arm/sql/elasticpools.go create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/arm/sql/models.go create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/arm/sql/recommendedelasticpools.go create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/arm/sql/servers.go create mode 100644 vendor/github.com/Azure/azure-sdk-for-go/arm/sql/version.go create mode 100644 website/source/assets/images/docs/tfe-organization-variables.png create mode 100644 website/source/assets/images/docs/tfe-variables.png create mode 100644 website/source/docs/enterprise/faq/vagrant-cloud-migration.html.md rename website/source/docs/providers/aws/d/{billing_service_account.markdown => billing_service_account.html.markdown} (100%) rename website/source/docs/providers/aws/r/{network_interface_attachment.markdown => network_interface_attachment.html.markdown} (100%) create mode 100644 website/source/docs/providers/aws/r/ssm_maintenance_window.html.markdown create mode 100644 website/source/docs/providers/aws/r/ssm_maintenance_window_target.html.markdown create mode 100644 website/source/docs/providers/aws/r/ssm_maintenance_window_task.html.markdown create mode 100644 website/source/docs/providers/azurerm/r/sql_elasticpool.html.markdown create mode 100644 website/source/docs/providers/gitlab/r/project_hook.html.markdown create mode 100644 website/source/docs/providers/google/r/compute_backend_bucket.html.markdown create mode 100644 website/source/docs/providers/google/r/compute_snapshot.html.markdown create mode 100644 website/source/docs/providers/http/data_source.html.md create mode 100644 website/source/docs/providers/http/index.html.markdown create mode 100644 website/source/docs/providers/kubernetes/r/limit_range.html.markdown create mode 100644 website/source/docs/providers/kubernetes/r/resource_quota.html.markdown create mode 100644 website/source/guides/index.html.md create mode 100644 website/source/guides/writing-custom-terraform-providers.html.md create mode 100644 website/source/layouts/guides.erb create mode 100644 website/source/layouts/http.erb diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bc51d03001b..447fa5c36e0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,25 +4,62 @@ BACKWARDS INCOMPATIBILITIES / NOTES: * provider/aws: Users of aws_cloudfront_distributions with custom_origins have been broken due to changes in the AWS API requiring `OriginReadTimeout` being set for updates. This has been fixed and will show as a change in terraform plan / apply. [GH-13367] * provider/aws: Users of China and Gov clouds, cannot use the new tagging of volumes created as part of aws_instances [GH-14055] +* provider/aws: Skip tag operations on cloudwatch logs in govcloud partition. Currently not supported by Amazon. [GH-12414] +* provider/aws: More consistent (un)quoting of long TXT/SPF `aws_route53_record`s. + Previously we were trimming first 2 quotes and now we're (correctly) trimming first and last one. + Depending on the use of quotes in your TXT/SPF records this may result in extra diff in plan/apply [GH-14170] FEATURES: * **New Provider:** `gitlab` [GH-13898] -* **New Resource:** `heroku_app_feature` [GH-14035] * **New Resource:** `aws_emr_security_configuration` [GH-14080] +* **New Resource:** `aws_ssm_maintenance_window` [GH-14087] +* **New Resource:** `aws_ssm_maintenance_window_target` [GH-14087] +* **New Resource:** `aws_ssm_maintenance_window_task` [GH-14087] +* **New Resource:** `azurerm_sql_elasticpool` [GH-14099] +* **New Resource:** `google_compute_backend_bucket` [GH-14015] +* **New Resource:** `google_compute_snapshot` [GH-12482] +* **New Resource:** `heroku_app_feature` [GH-14035] * **New Resource:** `heroku_pipeline` [GH-14078] * **New Resource:** `heroku_pipeline_coupling` [GH-14078] +* **New Resource:** `kubernetes_limit_range` [GH-14285] +* **New Resource:** `kubernetes_resource_quota` [GH-13914] +* **New Resource:** `vault_auth_backend` [GH-10988] * **New Data Source:** `aws_efs_file_system` [GH-14041] +* **New Data Source:** `http`, for retrieving text data from generic HTTP servers [GH-14270] +* **New Interpolation Function:** `log`, for computing logarithms [GH-12872] IMPROVEMENTS: +* core: `sha512` and `base64sha512` interpolation functions, similar to their `sha256` equivalents. [GH-14100] +* core: It's now possible to use the index operator `[ ]` to select a known value out of a partially-known list, such as using "splat syntax" and increasing the `count`. [GH-14135] * provider/aws: Add support for CustomOrigin timeouts to aws_cloudfront_distribution [GH-13367] * provider/aws: Add support for IAMDatabaseAuthenticationEnabled [GH-14092] * provider/aws: aws_dynamodb_table Add support for TimeToLive [GH-14104] * provider/aws: Add `security_configuration` support to `aws_emr_cluster` [GH-14133] +* provider/aws: Add support for the tenancy placement option in `aws_spot_fleet_request` [GH-14163] +* provider/aws: aws_db_option_group normalizes name to lowercase [GH-14192] +* provider/aws: Add support description to aws_iam_role [GH-14208] +* provider/aws: Add support for SSM Documents to aws_cloudwatch_event_target [GH-14067] +* provider/aws: add additional custom service endpoint options for CloudFormation, KMS, RDS, SNS & SQS [GH-14097] +* provider/aws: Add ARN to security group data source [GH-14245] +* provider/aws: Improve the wording of DynamoDB Validation error message [GH-14256] +* provider/aws: Add support for importing Kinesis Streams [GH-14278] +* provider/aws: Add `arn` attribute to `aws_ses_domain_identity` resource [GH-14306] +* provider/aws: Add support for targets to aws_ssm_association [GH-14246] +* provider/aws: native redis clustering support for elasticache [GH-14317] +* provider/azurerm: `azurerm_template_deployment` now supports String/Int/Boolean outputs [GH-13670] * provider/azurerm: Expose the Private IP Address for a Load Balancer, if available [GH-13965] +* provider/dns: Fix data dns txt record set [GH-14271] * provider/dnsimple: Add support for import for dnsimple_records [GH-9130] +* provider/dyn: Add verbose Dyn provider logs [GH-14076] * provider/google: Add support for networkIP in compute instance templates [GH-13515] +* provider/google: google_dns_managed_zone is now importable [GH-13824] +* provider/google: Add support for `compute_route` [GH-14065] +* provider/google: Add `path` to `google_pubsub_subscription` [GH-14238] +* provider/google: Improve Service Account by offering to recreate if missing [GH-14282] +* provider/google: Add additional properties for google resource storage bucket object [GH-14259] +* provider/heroku: import heroku_app resource [GH-14248] * provider/nomad: Add TLS options [GH-13956] * provider/triton: Add support for reading provider configuration from `TRITON_*` environment variables in addition to `SDC_*`[GH-14000] * provider/triton: Add `cloud_config` argument to `triton_machine` resources for Linux containers [GH-12840] @@ -30,6 +67,9 @@ IMPROVEMENTS: BUG FIXES: +* core: `module` blocks without names are now caught in validation, along with various other block types [GH-14162] +* core: no longer will errors and normal log output get garbled together on Windows [GH-14194] +* core: Avoid crash on empty TypeSet blocks [GH-14305] * provider/aws: Update aws_ebs_volume when attached [GH-14005] * provider/aws: Set aws_instance volume_tags to be Computed [GH-14007] * provider/aws: Fix issue getting partition for federated users [GH-13992] @@ -37,11 +77,22 @@ BUG FIXES: * provider/aws: Exclude aws_instance volume tagging for China and Gov Clouds [GH-14055] * provider/aws: Fix source_dest_check with network_interface [GH-14079] * provider/aws: Fixes the bug where SNS delivery policy get always recreated [GH-14064] +* provider/aws: Prevent Crash when importing aws_route53_record [GH-14218] +* provider/aws: More consistent (un)quoting of long TXT/SPF `aws_route53_record`s [GH-14170] +* provider/aws: Retry deletion of AWSConfig Rule on ResourceInUseException [GH-14269] +* provider/aws: Refresh ssm document from state on 404 [GH-14279] +* provider/aws: Allow zero-value ELB and ALB names [GH-14304] +* provider/aws: Update the ignoring of AWS specific tags [GH-14321] * provider/digitalocean: Prevent diffs when using IDs of images instead of slugs [GH-13879] * provider/fastly: Changes setting conditionals to optional [GH-14103] * provider/google: Ignore certain project services that can't be enabled directly via the api [GH-13730] * provider/google: Ability to add more than 25 project services [GH-13758] -* providers/heroku: Configure buildpacks correctly for both Org Apps and non-org Apps [GH-13990] +* provider/google: Fix compute instance panic with bad disk config [GH-14169] +* provider/google: Handle `google_storage_bucket_object` not being found [GH-14203] +* provider/google: Handle `google_compute_instance_group_manager` not being found [GH-14190] +* provider/google: better visibility for compute_region_backend_service [GH-14301] +* provider/heroku: Configure buildpacks correctly for both Org Apps and non-org Apps [GH-13990] +* provider/openstack: Handle disassociating deleted FloatingIP's from a server [GH-14210] * provider/postgres grant role when creating database [GH-11452] * provisioner/remote-exec: Fix panic from remote_exec provisioner [GH-14134] @@ -112,7 +163,7 @@ IMPROVEMENTS: * provider/heroku: Set App buildpacks from config ([#13910](https://github.com/hashicorp/terraform/issues/13910)) * provider/heroku: Create Heroku app in a private space ([#13862](https://github.com/hashicorp/terraform/issues/13862)) * provider/vault: `vault_generic_secret` resource can now optionally detect drift if it has appropriate access ([#11776](https://github.com/hashicorp/terraform/issues/11776)) - + BUG FIXES: * core: Prevent resource.Retry from adding untracked resources after the timeout: ([#13778](https://github.com/hashicorp/terraform/issues/13778)) @@ -142,7 +193,7 @@ BUG FIXES: * provider/azurerm: Locking route table on subnet create/delete ([#13791](https://github.com/hashicorp/terraform/issues/13791)) * provider/azurerm: VM's - fixes a bug where ssh_keys could contain a null entry ([#13755](https://github.com/hashicorp/terraform/issues/13755)) * provider/azurerm: VM's - ignoring the case on the `create_option` field during Diff's ([#13933](https://github.com/hashicorp/terraform/issues/13933)) - * provider/azurerm: fixing a bug refreshing the `azurerm_redis_cache` [[#13899](https://github.com/hashicorp/terraform/issues/13899)] + * provider/azurerm: fixing a bug refreshing the `azurerm_redis_cache` [[#13899](https://github.com/hashicorp/terraform/issues/13899)] * provider/fastly: Fix issue with using 0 for `default_ttl` ([#13648](https://github.com/hashicorp/terraform/issues/13648)) * provider/google: Fix panic in GKE provisioning with addons ([#13954](https://github.com/hashicorp/terraform/issues/13954)) * provider/fastly: Add ability to associate a healthcheck to a backend ([#13539](https://github.com/hashicorp/terraform/issues/13539)) @@ -160,7 +211,7 @@ BUG FIXES: ## 0.9.3 (April 12, 2017) BACKWARDS INCOMPATIBILITIES / NOTES: - * provider/aws: Fix a critical bug in `aws_emr_cluster` in order to preserve the ordering + * provider/aws: Fix a critical bug in `aws_emr_cluster` in order to preserve the ordering of any arguments in `bootstrap_action`. Terraform will now enforce the ordering from the configuration. As a result, `aws_emr_cluster` resources may need to be recreated, as there is no API to update them in-place ([#13580](https://github.com/hashicorp/terraform/issues/13580)) @@ -180,7 +231,7 @@ FEATURES: * **New Data Source:** `aws_iam_role` ([#13213](https://github.com/hashicorp/terraform/issues/13213)) IMPROVEMENTS: - + * core: add `-lock-timeout` option, which will block and retry locks for the given duration ([#13262](https://github.com/hashicorp/terraform/issues/13262)) * core: new `chomp` interpolation function which returns the given string with any trailing newline characters removed ([#13419](https://github.com/hashicorp/terraform/issues/13419)) * backend/remote-state: Add support for assume role extensions to s3 backend ([#13236](https://github.com/hashicorp/terraform/issues/13236)) @@ -221,8 +272,8 @@ IMPROVEMENTS: * provider/statuscake: Add support for StatusCake TriggerRate field ([#13340](https://github.com/hashicorp/terraform/issues/13340)) * provider/triton: Move to joyent/triton-go ([#13225](https://github.com/hashicorp/terraform/issues/13225)) * provisioner/chef: Make sure we add new Chef-Vault clients as clients ([#13525](https://github.com/hashicorp/terraform/issues/13525)) - -BUG FIXES: + +BUG FIXES: * core: Escaped interpolation-like sequences (like `$${foo}`) now permitted in variable defaults ([#13137](https://github.com/hashicorp/terraform/issues/13137)) * core: Fix strange issues with computed values in provider configuration that were worked around with `-input=false` ([#11264](https://github.com/hashicorp/terraform/issues/11264)], [[#13264](https://github.com/hashicorp/terraform/issues/13264)) @@ -322,8 +373,8 @@ IMPROVEMENTS: * provider/pagerduty: Validate credentials ([#12854](https://github.com/hashicorp/terraform/issues/12854)) * provider/openstack: Adding all_metadata attribute ([#13061](https://github.com/hashicorp/terraform/issues/13061)) * provider/profitbricks: Handling missing resources ([#13053](https://github.com/hashicorp/terraform/issues/13053)) - -BUG FIXES: + +BUG FIXES: * core: Remove legacy remote state configuration on state migration. This fixes errors when saving plans. ([#12888](https://github.com/hashicorp/terraform/issues/12888)) * provider/arukas: Default timeout for launching container increased to 15mins (was 10mins) ([#12849](https://github.com/hashicorp/terraform/issues/12849)) @@ -389,7 +440,7 @@ BUG FIXES: * provider/aws: Stop setting weight property on route53_record read ([#12756](https://github.com/hashicorp/terraform/issues/12756)) * provider/google: Fix the Google provider asking for account_file input on every run ([#12729](https://github.com/hashicorp/terraform/issues/12729)) * provider/profitbricks: Prevent panic on profitbricks volume ([#12819](https://github.com/hashicorp/terraform/issues/12819)) - + ## 0.9.0 (March 15, 2017) @@ -537,7 +588,7 @@ BUG FIXES: * provider/google: Correct the incorrect instance group manager URL returned from GKE ([#4336](https://github.com/hashicorp/terraform/issues/4336)) * provider/google: Fix a plan/apply cycle in IAM policies ([#12387](https://github.com/hashicorp/terraform/issues/12387)) * provider/google: Fix a plan/apply cycle in forwarding rules when only a single port is specified ([#12662](https://github.com/hashicorp/terraform/issues/12662)) - + ## 0.9.0-beta2 (March 2, 2017) BACKWARDS INCOMPATIBILITIES / NOTES: diff --git a/builtin/providers/aws/autoscaling_tags.go b/builtin/providers/aws/autoscaling_tags.go index e9ca8531aedf..0ca063598b81 100644 --- a/builtin/providers/aws/autoscaling_tags.go +++ b/builtin/providers/aws/autoscaling_tags.go @@ -190,7 +190,7 @@ func setToMapByKey(s *schema.Set, key string) map[string]interface{} { // compare a tag against a list of strings and checks if it should // be ignored or not func tagIgnoredAutoscaling(t *autoscaling.Tag) bool { - filter := []string{"^aws:*"} + filter := []string{"^aws:"} for _, v := range filter { log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) if r, _ := regexp.MatchString(v, *t.Key); r == true { diff --git a/builtin/providers/aws/config.go b/builtin/providers/aws/config.go index 327090130950..3efe53706848 100644 --- a/builtin/providers/aws/config.go +++ b/builtin/providers/aws/config.go @@ -89,13 +89,21 @@ type Config struct { AllowedAccountIds []interface{} ForbiddenAccountIds []interface{} - DynamoDBEndpoint string - KinesisEndpoint string - Ec2Endpoint string - IamEndpoint string - ElbEndpoint string - S3Endpoint string - Insecure bool + CloudFormationEndpoint string + CloudWatchEndpoint string + CloudWatchEventsEndpoint string + CloudWatchLogsEndpoint string + DynamoDBEndpoint string + Ec2Endpoint string + ElbEndpoint string + IamEndpoint string + KinesisEndpoint string + KmsEndpoint string + RdsEndpoint string + S3Endpoint string + SnsEndpoint string + SqsEndpoint string + Insecure bool SkipCredsValidation bool SkipGetEC2Platforms bool @@ -264,12 +272,20 @@ func (c *Config) Client() (interface{}, error) { usEast1Sess := sess.Copy(&aws.Config{Region: aws.String("us-east-1")}) // Some services have user-configurable endpoints + awsCfSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.CloudFormationEndpoint)}) + awsCwSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.CloudWatchEndpoint)}) + awsCweSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.CloudWatchEventsEndpoint)}) + awsCwlSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.CloudWatchLogsEndpoint)}) + awsDynamoSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DynamoDBEndpoint)}) awsEc2Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.Ec2Endpoint)}) awsElbSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.ElbEndpoint)}) awsIamSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.IamEndpoint)}) + awsKinesisSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KinesisEndpoint)}) + awsKmsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KmsEndpoint)}) + awsRdsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.RdsEndpoint)}) awsS3Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.S3Endpoint)}) - dynamoSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.DynamoDBEndpoint)}) - kinesisSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KinesisEndpoint)}) + awsSnsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.SnsEndpoint)}) + awsSqsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.SqsEndpoint)}) // These two services need to be set up early so we can check on AccountID client.iamconn = iam.New(awsIamSess) @@ -312,12 +328,12 @@ func (c *Config) Client() (interface{}, error) { client.apigateway = apigateway.New(sess) client.appautoscalingconn = applicationautoscaling.New(sess) client.autoscalingconn = autoscaling.New(sess) - client.cfconn = cloudformation.New(sess) + client.cfconn = cloudformation.New(awsCfSess) client.cloudfrontconn = cloudfront.New(sess) client.cloudtrailconn = cloudtrail.New(sess) - client.cloudwatchconn = cloudwatch.New(sess) - client.cloudwatcheventsconn = cloudwatchevents.New(sess) - client.cloudwatchlogsconn = cloudwatchlogs.New(sess) + client.cloudwatchconn = cloudwatch.New(awsCwSess) + client.cloudwatcheventsconn = cloudwatchevents.New(awsCweSess) + client.cloudwatchlogsconn = cloudwatchlogs.New(awsCwlSess) client.codecommitconn = codecommit.New(sess) client.codebuildconn = codebuild.New(sess) client.codedeployconn = codedeploy.New(sess) @@ -326,7 +342,7 @@ func (c *Config) Client() (interface{}, error) { client.dmsconn = databasemigrationservice.New(sess) client.codepipelineconn = codepipeline.New(sess) client.dsconn = directoryservice.New(sess) - client.dynamodbconn = dynamodb.New(dynamoSess) + client.dynamodbconn = dynamodb.New(awsDynamoSess) client.ecrconn = ecr.New(sess) client.ecsconn = ecs.New(sess) client.efsconn = efs.New(sess) @@ -340,20 +356,20 @@ func (c *Config) Client() (interface{}, error) { client.firehoseconn = firehose.New(sess) client.inspectorconn = inspector.New(sess) client.glacierconn = glacier.New(sess) - client.kinesisconn = kinesis.New(kinesisSess) - client.kmsconn = kms.New(sess) + client.kinesisconn = kinesis.New(awsKinesisSess) + client.kmsconn = kms.New(awsKmsSess) client.lambdaconn = lambda.New(sess) client.lightsailconn = lightsail.New(usEast1Sess) client.opsworksconn = opsworks.New(sess) client.r53conn = route53.New(usEast1Sess) - client.rdsconn = rds.New(sess) + client.rdsconn = rds.New(awsRdsSess) client.redshiftconn = redshift.New(sess) client.simpledbconn = simpledb.New(sess) client.s3conn = s3.New(awsS3Sess) client.sesConn = ses.New(sess) client.sfnconn = sfn.New(sess) - client.snsconn = sns.New(sess) - client.sqsconn = sqs.New(sess) + client.snsconn = sns.New(awsSnsSess) + client.sqsconn = sqs.New(awsSqsSess) client.ssmconn = ssm.New(sess) client.wafconn = waf.New(sess) diff --git a/builtin/providers/aws/data_source_aws_iam_account_alias.go b/builtin/providers/aws/data_source_aws_iam_account_alias.go index c8b8378cf959..f938973734b9 100644 --- a/builtin/providers/aws/data_source_aws_iam_account_alias.go +++ b/builtin/providers/aws/data_source_aws_iam_account_alias.go @@ -34,6 +34,7 @@ func dataSourceAwsIamAccountAliasRead(d *schema.ResourceData, meta interface{}) if err != nil { return err } + // 'AccountAliases': [] if there is no alias. if resp == nil || len(resp.AccountAliases) == 0 { return fmt.Errorf("no IAM account alias found") diff --git a/builtin/providers/aws/data_source_aws_iam_account_alias_test.go b/builtin/providers/aws/data_source_aws_iam_account_alias_test.go deleted file mode 100644 index a23a6a93e68d..000000000000 --- a/builtin/providers/aws/data_source_aws_iam_account_alias_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package aws - -import ( - "fmt" - "testing" - - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" -) - -func TestAccAWSIamAccountAlias_basic(t *testing.T) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - Steps: []resource.TestStep{ - { - Config: testAccCheckAwsIamAccountAliasConfig_basic, - Check: resource.ComposeTestCheckFunc( - testAccCheckAwsIamAccountAlias("data.aws_iam_account_alias.current"), - ), - }, - }, - }) -} - -func testAccCheckAwsIamAccountAlias(n string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Can't find Account Alias resource: %s", n) - } - - if rs.Primary.Attributes["account_alias"] == "" { - return fmt.Errorf("Missing Account Alias") - } - - return nil - } -} - -const testAccCheckAwsIamAccountAliasConfig_basic = ` -data "aws_iam_account_alias" "current" { } -` diff --git a/builtin/providers/aws/data_source_aws_security_group.go b/builtin/providers/aws/data_source_aws_security_group.go index 1ff1f17a49ef..c0757d9a815d 100644 --- a/builtin/providers/aws/data_source_aws_security_group.go +++ b/builtin/providers/aws/data_source_aws_security_group.go @@ -14,23 +14,29 @@ func dataSourceAwsSecurityGroup() *schema.Resource { Read: dataSourceAwsSecurityGroupRead, Schema: map[string]*schema.Schema{ - "vpc_id": &schema.Schema{ + "vpc_id": { Type: schema.TypeString, Optional: true, Computed: true, }, - "name": &schema.Schema{ + "name": { Type: schema.TypeString, Optional: true, Computed: true, }, "filter": ec2CustomFiltersSchema(), - "id": &schema.Schema{ + "id": { Type: schema.TypeString, Optional: true, Computed: true, }, + + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tagsSchemaComputed(), }, } @@ -81,6 +87,8 @@ func dataSourceAwsSecurityGroupRead(d *schema.ResourceData, meta interface{}) er d.Set("description", sg.Description) d.Set("vpc_id", sg.VpcId) d.Set("tags", tagsToMap(sg.Tags)) + d.Set("arn", fmt.Sprintf("arn:%s:ec2:%s:%s/security-group/%s", + meta.(*AWSClient).partition, meta.(*AWSClient).region, *sg.OwnerId, *sg.GroupId)) return nil } diff --git a/builtin/providers/aws/data_source_aws_security_group_test.go b/builtin/providers/aws/data_source_aws_security_group_test.go index d697c1e3eea3..6e1f1664a83f 100644 --- a/builtin/providers/aws/data_source_aws_security_group_test.go +++ b/builtin/providers/aws/data_source_aws_security_group_test.go @@ -4,6 +4,8 @@ import ( "fmt" "testing" + "strings" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" @@ -66,6 +68,10 @@ func testAccDataSourceAwsSecurityGroupCheck(name string) resource.TestCheckFunc return fmt.Errorf("bad Name tag %s", attr["tags.Name"]) } + if !strings.Contains(attr["arn"], attr["id"]) { + return fmt.Errorf("bad ARN %s", attr["arn"]) + } + return nil } } diff --git a/builtin/providers/aws/import_aws_iam_account_alias_test.go b/builtin/providers/aws/import_aws_iam_account_alias_test.go index e2d00b68cf61..28829a4194fb 100644 --- a/builtin/providers/aws/import_aws_iam_account_alias_test.go +++ b/builtin/providers/aws/import_aws_iam_account_alias_test.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform/helper/resource" ) -func TestAccAWSIAMAccountAlias_importBasic(t *testing.T) { +func testAccAWSIAMAccountAlias_importBasic(t *testing.T) { resourceName := "aws_iam_account_alias.test" rstring := acctest.RandString(5) diff --git a/builtin/providers/aws/import_aws_iam_role_test.go b/builtin/providers/aws/import_aws_iam_role_test.go index ea172721b0fa..37aaace828c8 100644 --- a/builtin/providers/aws/import_aws_iam_role_test.go +++ b/builtin/providers/aws/import_aws_iam_role_test.go @@ -3,22 +3,24 @@ package aws import ( "testing" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" ) func TestAccAWSIAMRole_importBasic(t *testing.T) { resourceName := "aws_iam_role.role" + rName := acctest.RandString(10) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSRoleDestroy, Steps: []resource.TestStep{ - resource.TestStep{ - Config: testAccAWSRoleConfig, + { + Config: testAccAWSRoleConfig(rName), }, - resource.TestStep{ + { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index e6165f33e552..957614d7b7bc 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -422,6 +422,9 @@ func Provider() terraform.ResourceProvider { "aws_ssm_activation": resourceAwsSsmActivation(), "aws_ssm_association": resourceAwsSsmAssociation(), "aws_ssm_document": resourceAwsSsmDocument(), + "aws_ssm_maintenance_window": resourceAwsSsmMaintenanceWindow(), + "aws_ssm_maintenance_window_target": resourceAwsSsmMaintenanceWindowTarget(), + "aws_ssm_maintenance_window_task": resourceAwsSsmMaintenanceWindowTask(), "aws_spot_datafeed_subscription": resourceAwsSpotDataFeedSubscription(), "aws_spot_instance_request": resourceAwsSpotInstanceRequest(), "aws_spot_fleet_request": resourceAwsSpotFleetRequest(), @@ -484,20 +487,36 @@ func init() { "being executed. If the API request still fails, an error is\n" + "thrown.", + "cloudformation_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n", + + "cloudwatch_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n", + + "cloudwatchevents_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n", + + "cloudwatchlogs_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n", + "dynamodb_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n" + "It's typically used to connect to dynamodb-local.", "kinesis_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n" + "It's typically used to connect to kinesalite.", + "kms_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n", + "iam_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n", "ec2_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n", "elb_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n", + "rds_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n", + "s3_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n", + "sns_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n", + + "sqs_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n", + "insecure": "Explicitly allow the provider to perform \"insecure\" SSL requests. If omitted," + "default value is `false`", @@ -574,12 +593,20 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { for _, endpointsSetI := range endpointsSet.List() { endpoints := endpointsSetI.(map[string]interface{}) + config.CloudFormationEndpoint = endpoints["cloudformation"].(string) + config.CloudWatchEndpoint = endpoints["cloudwatch"].(string) + config.CloudWatchEventsEndpoint = endpoints["cloudwatchevents"].(string) + config.CloudWatchLogsEndpoint = endpoints["cloudwatchlogs"].(string) config.DynamoDBEndpoint = endpoints["dynamodb"].(string) - config.IamEndpoint = endpoints["iam"].(string) config.Ec2Endpoint = endpoints["ec2"].(string) config.ElbEndpoint = endpoints["elb"].(string) + config.IamEndpoint = endpoints["iam"].(string) config.KinesisEndpoint = endpoints["kinesis"].(string) + config.KmsEndpoint = endpoints["kms"].(string) + config.RdsEndpoint = endpoints["rds"].(string) config.S3Endpoint = endpoints["s3"].(string) + config.SnsEndpoint = endpoints["sns"].(string) + config.SqsEndpoint = endpoints["sqs"].(string) } if v, ok := d.GetOk("allowed_account_ids"); ok { @@ -648,6 +675,30 @@ func endpointsSchema() *schema.Schema { Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "cloudwatch": { + Type: schema.TypeString, + Optional: true, + Default: "", + Description: descriptions["cloudwatch_endpoint"], + }, + "cloudwatchevents": { + Type: schema.TypeString, + Optional: true, + Default: "", + Description: descriptions["cloudwatchevents_endpoint"], + }, + "cloudwatchlogs": { + Type: schema.TypeString, + Optional: true, + Default: "", + Description: descriptions["cloudwatchlogs_endpoint"], + }, + "cloudformation": { + Type: schema.TypeString, + Optional: true, + Default: "", + Description: descriptions["cloudformation_endpoint"], + }, "dynamodb": { Type: schema.TypeString, Optional: true, @@ -680,12 +731,36 @@ func endpointsSchema() *schema.Schema { Default: "", Description: descriptions["kinesis_endpoint"], }, + "kms": { + Type: schema.TypeString, + Optional: true, + Default: "", + Description: descriptions["kms_endpoint"], + }, + "rds": { + Type: schema.TypeString, + Optional: true, + Default: "", + Description: descriptions["rds_endpoint"], + }, "s3": { Type: schema.TypeString, Optional: true, Default: "", Description: descriptions["s3_endpoint"], }, + "sns": { + Type: schema.TypeString, + Optional: true, + Default: "", + Description: descriptions["sns_endpoint"], + }, + "sqs": { + Type: schema.TypeString, + Optional: true, + Default: "", + Description: descriptions["sqs_endpoint"], + }, }, }, Set: endpointsToHash, @@ -695,12 +770,20 @@ func endpointsSchema() *schema.Schema { func endpointsToHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["cloudwatch"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["cloudwatchevents"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["cloudwatchlogs"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["cloudformation"].(string))) buf.WriteString(fmt.Sprintf("%s-", m["dynamodb"].(string))) buf.WriteString(fmt.Sprintf("%s-", m["iam"].(string))) buf.WriteString(fmt.Sprintf("%s-", m["ec2"].(string))) buf.WriteString(fmt.Sprintf("%s-", m["elb"].(string))) buf.WriteString(fmt.Sprintf("%s-", m["kinesis"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["kms"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["rds"].(string))) buf.WriteString(fmt.Sprintf("%s-", m["s3"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["sns"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["sqs"].(string))) return hashcode.String(buf.String()) } diff --git a/builtin/providers/aws/resource_aws_alb_test.go b/builtin/providers/aws/resource_aws_alb_test.go index 807d1f4e5fc0..5e766d74b1d8 100644 --- a/builtin/providers/aws/resource_aws_alb_test.go +++ b/builtin/providers/aws/resource_aws_alb_test.go @@ -98,6 +98,26 @@ func TestAccAWSALB_generatedName(t *testing.T) { }) } +func TestAccAWSALB_generatesNameForZeroValue(t *testing.T) { + var conf elbv2.LoadBalancer + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "aws_alb.alb_test", + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSALBDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSALBConfig_zeroValueName(), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckAWSALBExists("aws_alb.alb_test", &conf), + resource.TestCheckResourceAttrSet("aws_alb.alb_test", "name"), + ), + }, + }, + }) +} + func TestAccAWSALB_namePrefix(t *testing.T) { var conf elbv2.LoadBalancer @@ -848,6 +868,82 @@ resource "aws_security_group" "alb_test" { }`) } +func testAccAWSALBConfig_zeroValueName() string { + return fmt.Sprintf(` +resource "aws_alb" "alb_test" { + name = "" + internal = true + security_groups = ["${aws_security_group.alb_test.id}"] + subnets = ["${aws_subnet.alb_test.*.id}"] + + idle_timeout = 30 + enable_deletion_protection = false + + tags { + TestName = "TestAccAWSALB_basic" + } +} + +variable "subnets" { + default = ["10.0.1.0/24", "10.0.2.0/24"] + type = "list" +} + +data "aws_availability_zones" "available" {} + +resource "aws_vpc" "alb_test" { + cidr_block = "10.0.0.0/16" + + tags { + TestName = "TestAccAWSALB_basic" + } +} + +resource "aws_internet_gateway" "gw" { + vpc_id = "${aws_vpc.alb_test.id}" + + tags { + Name = "TestAccAWSALB_basic" + } +} + +resource "aws_subnet" "alb_test" { + count = 2 + vpc_id = "${aws_vpc.alb_test.id}" + cidr_block = "${element(var.subnets, count.index)}" + map_public_ip_on_launch = true + availability_zone = "${element(data.aws_availability_zones.available.names, count.index)}" + + tags { + TestName = "TestAccAWSALB_basic" + } +} + +resource "aws_security_group" "alb_test" { + name = "allow_all_alb_test" + description = "Used for ALB Testing" + vpc_id = "${aws_vpc.alb_test.id}" + + ingress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags { + TestName = "TestAccAWSALB_basic" + } +}`) +} + func testAccAWSALBConfig_namePrefix() string { return fmt.Sprintf(` resource "aws_alb" "alb_test" { diff --git a/builtin/providers/aws/resource_aws_cloudwatch_event_target.go b/builtin/providers/aws/resource_aws_cloudwatch_event_target.go index 767b94b16f2e..afa5e84fdcf8 100644 --- a/builtin/providers/aws/resource_aws_cloudwatch_event_target.go +++ b/builtin/providers/aws/resource_aws_cloudwatch_event_target.go @@ -11,6 +11,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" events "github.com/aws/aws-sdk-go/service/cloudwatchevents" + "github.com/hashicorp/terraform/helper/validation" ) func resourceAwsCloudWatchEventTarget() *schema.Resource { @@ -21,14 +22,14 @@ func resourceAwsCloudWatchEventTarget() *schema.Resource { Delete: resourceAwsCloudWatchEventTargetDelete, Schema: map[string]*schema.Schema{ - "rule": &schema.Schema{ + "rule": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: validateCloudWatchEventRuleName, }, - "target_id": &schema.Schema{ + "target_id": { Type: schema.TypeString, Optional: true, Computed: true, @@ -36,12 +37,12 @@ func resourceAwsCloudWatchEventTarget() *schema.Resource { ValidateFunc: validateCloudWatchEventTargetId, }, - "arn": &schema.Schema{ + "arn": { Type: schema.TypeString, Required: true, }, - "input": &schema.Schema{ + "input": { Type: schema.TypeString, Optional: true, ConflictsWith: []string{"input_path"}, @@ -49,11 +50,36 @@ func resourceAwsCloudWatchEventTarget() *schema.Resource { // but for built-in targets input may not be JSON }, - "input_path": &schema.Schema{ + "input_path": { Type: schema.TypeString, Optional: true, ConflictsWith: []string{"input"}, }, + + "role_arn": { + Type: schema.TypeString, + Optional: true, + }, + + "run_command_targets": { + Type: schema.TypeList, + Optional: true, + MaxItems: 5, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 128), + }, + "values": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, }, } } @@ -72,6 +98,7 @@ func resourceAwsCloudWatchEventTargetCreate(d *schema.ResourceData, meta interfa } input := buildPutTargetInputStruct(d) + log.Printf("[DEBUG] Creating CloudWatch Event Target: %s", input) out, err := conn.PutTargets(input) if err != nil { @@ -128,6 +155,13 @@ func resourceAwsCloudWatchEventTargetRead(d *schema.ResourceData, meta interface d.Set("target_id", t.Id) d.Set("input", t.Input) d.Set("input_path", t.InputPath) + d.Set("role_arn", t.RoleArn) + + if t.RunCommandParameters != nil { + if err := d.Set("run_command_targets", flattenAwsCloudWatchEventTargetRunParameters(t.RunCommandParameters)); err != nil { + return fmt.Errorf("[DEBUG] Error setting run_command_targets error: %#v", err) + } + } return nil } @@ -162,6 +196,7 @@ func resourceAwsCloudWatchEventTargetUpdate(d *schema.ResourceData, meta interfa conn := meta.(*AWSClient).cloudwatcheventsconn input := buildPutTargetInputStruct(d) + log.Printf("[DEBUG] Updating CloudWatch Event Target: %s", input) _, err := conn.PutTargets(input) if err != nil { @@ -203,6 +238,14 @@ func buildPutTargetInputStruct(d *schema.ResourceData) *events.PutTargetsInput { e.InputPath = aws.String(v.(string)) } + if v, ok := d.GetOk("role_arn"); ok { + e.RoleArn = aws.String(v.(string)) + } + + if v, ok := d.GetOk("run_command_targets"); ok { + e.RunCommandParameters = expandAwsCloudWatchEventTargetRunParameters(v.([]interface{})) + } + input := events.PutTargetsInput{ Rule: aws.String(d.Get("rule").(string)), Targets: []*events.Target{e}, @@ -210,3 +253,39 @@ func buildPutTargetInputStruct(d *schema.ResourceData) *events.PutTargetsInput { return &input } + +func expandAwsCloudWatchEventTargetRunParameters(config []interface{}) *events.RunCommandParameters { + + commands := make([]*events.RunCommandTarget, 0) + + for _, c := range config { + param := c.(map[string]interface{}) + command := &events.RunCommandTarget{ + Key: aws.String(param["key"].(string)), + Values: expandStringList(param["values"].([]interface{})), + } + + commands = append(commands, command) + } + + command := &events.RunCommandParameters{ + RunCommandTargets: commands, + } + + return command +} + +func flattenAwsCloudWatchEventTargetRunParameters(runCommand *events.RunCommandParameters) []map[string]interface{} { + result := make([]map[string]interface{}, 0) + + for _, x := range runCommand.RunCommandTargets { + config := make(map[string]interface{}) + + config["key"] = *x.Key + config["values"] = flattenStringList(x.Values) + + result = append(result, config) + } + + return result +} diff --git a/builtin/providers/aws/resource_aws_cloudwatch_event_target_test.go b/builtin/providers/aws/resource_aws_cloudwatch_event_target_test.go index a5509bec8a2e..044a32e6794a 100644 --- a/builtin/providers/aws/resource_aws_cloudwatch_event_target_test.go +++ b/builtin/providers/aws/resource_aws_cloudwatch_event_target_test.go @@ -6,6 +6,7 @@ import ( "testing" events "github.com/aws/aws-sdk-go/service/cloudwatchevents" + "github.com/hashicorp/terraform/helper/acctest" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" ) @@ -18,7 +19,7 @@ func TestAccAWSCloudWatchEventTarget_basic(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudWatchEventTargetDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSCloudWatchEventTargetConfig, Check: resource.ComposeTestCheckFunc( testAccCheckCloudWatchEventTargetExists("aws_cloudwatch_event_target.moobar", &target), @@ -28,7 +29,7 @@ func TestAccAWSCloudWatchEventTarget_basic(t *testing.T) { regexp.MustCompile(":tf-acc-moon$")), ), }, - resource.TestStep{ + { Config: testAccAWSCloudWatchEventTargetConfigModified, Check: resource.ComposeTestCheckFunc( testAccCheckCloudWatchEventTargetExists("aws_cloudwatch_event_target.moobar", &target), @@ -50,7 +51,7 @@ func TestAccAWSCloudWatchEventTarget_missingTargetId(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudWatchEventTargetDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccAWSCloudWatchEventTargetConfigMissingTargetId, Check: resource.ComposeTestCheckFunc( testAccCheckCloudWatchEventTargetExists("aws_cloudwatch_event_target.moobar", &target), @@ -65,20 +66,21 @@ func TestAccAWSCloudWatchEventTarget_missingTargetId(t *testing.T) { func TestAccAWSCloudWatchEventTarget_full(t *testing.T) { var target events.Target + rName := acctest.RandomWithPrefix("tf_ssm_Document") resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckAWSCloudWatchEventTargetDestroy, Steps: []resource.TestStep{ - resource.TestStep{ - Config: testAccAWSCloudWatchEventTargetConfig_full, + { + Config: testAccAWSCloudWatchEventTargetConfig_full(rName), Check: resource.ComposeTestCheckFunc( testAccCheckCloudWatchEventTargetExists("aws_cloudwatch_event_target.foobar", &target), resource.TestCheckResourceAttr("aws_cloudwatch_event_target.foobar", "rule", "tf-acc-cw-event-rule-full"), resource.TestCheckResourceAttr("aws_cloudwatch_event_target.foobar", "target_id", "tf-acc-cw-target-full"), resource.TestMatchResourceAttr("aws_cloudwatch_event_target.foobar", "arn", - regexp.MustCompile("^arn:aws:kinesis:.*:stream/terraform-kinesis-test$")), + regexp.MustCompile("^arn:aws:kinesis:.*:stream/tf_ssm_Document")), resource.TestCheckResourceAttr("aws_cloudwatch_event_target.foobar", "input", "{ \"source\": [\"aws.cloudtrail\"] }\n"), resource.TestCheckResourceAttr("aws_cloudwatch_event_target.foobar", "input_path", ""), ), @@ -87,6 +89,25 @@ func TestAccAWSCloudWatchEventTarget_full(t *testing.T) { }) } +func TestAccAWSCloudWatchEventTarget_ssmDocument(t *testing.T) { + var target events.Target + rName := acctest.RandomWithPrefix("tf_ssm_Document") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSCloudWatchEventTargetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSCloudWatchEventTargetConfigSsmDocument(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudWatchEventTargetExists("aws_cloudwatch_event_target.test", &target), + ), + }, + }, + }) +} + func testAccCheckCloudWatchEventTargetExists(n string, rule *events.Target) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -126,17 +147,6 @@ func testAccCheckAWSCloudWatchEventTargetDestroy(s *terraform.State) error { return nil } -func testAccCheckTargetIdExists(targetId string) resource.TestCheckFunc { - return func(s *terraform.State) error { - _, ok := s.RootModule().Resources[targetId] - if !ok { - return fmt.Errorf("Not found: %s", targetId) - } - - return nil - } -} - var testAccAWSCloudWatchEventTargetConfig = ` resource "aws_cloudwatch_event_rule" "foo" { name = "tf-acc-cw-event-rule-basic" @@ -187,7 +197,8 @@ resource "aws_sns_topic" "sun" { } ` -var testAccAWSCloudWatchEventTargetConfig_full = ` +func testAccAWSCloudWatchEventTargetConfig_full(rName string) string { + return fmt.Sprintf(` resource "aws_cloudwatch_event_rule" "foo" { name = "tf-acc-cw-event-rule-full" schedule_expression = "rate(1 hour)" @@ -195,7 +206,7 @@ resource "aws_cloudwatch_event_rule" "foo" { } resource "aws_iam_role" "role" { - name = "test_role" + name = "%s" assume_role_policy = < 0 { + return fmt.Errorf("Expected AWS SSM Maintenance Target to be gone, but was still found") + } + + return nil + } + + return nil +} + +func testAccAWSSSMMaintenanceWindowTargetBasicConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_ssm_maintenance_window" "foo" { + name = "maintenance-window-%s" + schedule = "cron(0 16 ? * TUE *)" + duration = 3 + cutoff = 1 +} + +resource "aws_ssm_maintenance_window_target" "target" { + window_id = "${aws_ssm_maintenance_window.foo.id}" + resource_type = "INSTANCE" + targets { + key = "tag:Name" + values = ["acceptance_test"] + } +} +`, rName) +} diff --git a/builtin/providers/aws/resource_aws_ssm_maintenance_window_task.go b/builtin/providers/aws/resource_aws_ssm_maintenance_window_task.go new file mode 100644 index 000000000000..1665bbfb1ce0 --- /dev/null +++ b/builtin/providers/aws/resource_aws_ssm_maintenance_window_task.go @@ -0,0 +1,230 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ssm" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsSsmMaintenanceWindowTask() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsSsmMaintenanceWindowTaskCreate, + Read: resourceAwsSsmMaintenanceWindowTaskRead, + Delete: resourceAwsSsmMaintenanceWindowTaskDelete, + + Schema: map[string]*schema.Schema{ + "window_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "max_concurrency": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "max_errors": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "task_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "task_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "service_role_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "targets": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + }, + "values": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + }, + + "priority": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + + "logging_info": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "s3_bucket_name": { + Type: schema.TypeString, + Required: true, + }, + "s3_region": { + Type: schema.TypeString, + Required: true, + }, + "s3_bucket_prefix": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + } +} + +func expandAwsSsmMaintenanceWindowLoggingInfo(config []interface{}) *ssm.LoggingInfo { + + loggingConfig := config[0].(map[string]interface{}) + + loggingInfo := &ssm.LoggingInfo{ + S3BucketName: aws.String(loggingConfig["s3_bucket_name"].(string)), + S3Region: aws.String(loggingConfig["s3_region"].(string)), + } + + if s := loggingConfig["s3_bucket_prefix"].(string); s != "" { + loggingInfo.S3KeyPrefix = aws.String(s) + } + + return loggingInfo +} + +func flattenAwsSsmMaintenanceWindowLoggingInfo(loggingInfo *ssm.LoggingInfo) []interface{} { + + result := make(map[string]interface{}) + result["s3_bucket_name"] = *loggingInfo.S3BucketName + result["s3_region"] = *loggingInfo.S3Region + + if loggingInfo.S3KeyPrefix != nil { + result["s3_bucket_prefix"] = *loggingInfo.S3KeyPrefix + } + + return []interface{}{result} +} + +func resourceAwsSsmMaintenanceWindowTaskCreate(d *schema.ResourceData, meta interface{}) error { + ssmconn := meta.(*AWSClient).ssmconn + + log.Printf("[INFO] Registering SSM Maintenance Window Task") + + params := &ssm.RegisterTaskWithMaintenanceWindowInput{ + WindowId: aws.String(d.Get("window_id").(string)), + MaxConcurrency: aws.String(d.Get("max_concurrency").(string)), + MaxErrors: aws.String(d.Get("max_errors").(string)), + TaskType: aws.String(d.Get("task_type").(string)), + ServiceRoleArn: aws.String(d.Get("service_role_arn").(string)), + TaskArn: aws.String(d.Get("task_arn").(string)), + Targets: expandAwsSsmTargets(d), + } + + if v, ok := d.GetOk("priority"); ok { + params.Priority = aws.Int64(int64(v.(int))) + } + + if v, ok := d.GetOk("logging_info"); ok { + params.LoggingInfo = expandAwsSsmMaintenanceWindowLoggingInfo(v.([]interface{})) + } + + resp, err := ssmconn.RegisterTaskWithMaintenanceWindow(params) + if err != nil { + return err + } + + d.SetId(*resp.WindowTaskId) + + return resourceAwsSsmMaintenanceWindowTaskRead(d, meta) +} + +func resourceAwsSsmMaintenanceWindowTaskRead(d *schema.ResourceData, meta interface{}) error { + ssmconn := meta.(*AWSClient).ssmconn + + params := &ssm.DescribeMaintenanceWindowTasksInput{ + WindowId: aws.String(d.Get("window_id").(string)), + } + + resp, err := ssmconn.DescribeMaintenanceWindowTasks(params) + if err != nil { + return err + } + + found := false + for _, t := range resp.Tasks { + if *t.WindowTaskId == d.Id() { + found = true + + d.Set("window_id", t.WindowId) + d.Set("max_concurrency", t.MaxConcurrency) + d.Set("max_errors", t.MaxErrors) + d.Set("task_type", t.Type) + d.Set("service_role_arn", t.ServiceRoleArn) + d.Set("task_arn", t.TaskArn) + d.Set("priority", t.Priority) + + if t.LoggingInfo != nil { + if err := d.Set("logging_info", flattenAwsSsmMaintenanceWindowLoggingInfo(t.LoggingInfo)); err != nil { + return fmt.Errorf("[DEBUG] Error setting logging_info error: %#v", err) + } + } + + if err := d.Set("targets", flattenAwsSsmTargets(t.Targets)); err != nil { + return fmt.Errorf("[DEBUG] Error setting targets error: %#v", err) + } + } + } + + if !found { + log.Printf("[INFO] Maintenance Window Target not found. Removing from state") + d.SetId("") + return nil + } + + return nil +} + +func resourceAwsSsmMaintenanceWindowTaskDelete(d *schema.ResourceData, meta interface{}) error { + ssmconn := meta.(*AWSClient).ssmconn + + log.Printf("[INFO] Deregistering SSM Maintenance Window Task: %s", d.Id()) + + params := &ssm.DeregisterTaskFromMaintenanceWindowInput{ + WindowId: aws.String(d.Get("window_id").(string)), + WindowTaskId: aws.String(d.Id()), + } + + _, err := ssmconn.DeregisterTaskFromMaintenanceWindow(params) + if err != nil { + return err + } + + return nil +} diff --git a/builtin/providers/aws/resource_aws_ssm_maintenance_window_task_test.go b/builtin/providers/aws/resource_aws_ssm_maintenance_window_task_test.go new file mode 100644 index 000000000000..797d75766d7c --- /dev/null +++ b/builtin/providers/aws/resource_aws_ssm_maintenance_window_task_test.go @@ -0,0 +1,158 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/ssm" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSSSMMaintenanceWindowTask_basic(t *testing.T) { + name := acctest.RandString(10) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSSSMMaintenanceWindowTaskDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSSSMMaintenanceWindowTaskBasicConfig(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSSSMMaintenanceWindowTaskExists("aws_ssm_maintenance_window_task.target"), + ), + }, + }, + }) +} + +func testAccCheckAWSSSMMaintenanceWindowTaskExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No SSM Maintenance Window Task Window ID is set") + } + + conn := testAccProvider.Meta().(*AWSClient).ssmconn + + resp, err := conn.DescribeMaintenanceWindowTasks(&ssm.DescribeMaintenanceWindowTasksInput{ + WindowId: aws.String(rs.Primary.Attributes["window_id"]), + }) + if err != nil { + return err + } + + for _, i := range resp.Tasks { + if *i.WindowTaskId == rs.Primary.ID { + return nil + } + } + + return fmt.Errorf("No AWS SSM Maintenance window task found") + } +} + +func testAccCheckAWSSSMMaintenanceWindowTaskDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).ssmconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ssm_maintenance_window_target" { + continue + } + + out, err := conn.DescribeMaintenanceWindowTasks(&ssm.DescribeMaintenanceWindowTasksInput{ + WindowId: aws.String(rs.Primary.Attributes["window_id"]), + }) + + if err != nil { + // Verify the error is what we want + if ae, ok := err.(awserr.Error); ok && ae.Code() == "DoesNotExistException" { + continue + } + return err + } + + if len(out.Tasks) > 0 { + return fmt.Errorf("Expected AWS SSM Maintenance Task to be gone, but was still found") + } + + return nil + } + + return nil +} + +func testAccAWSSSMMaintenanceWindowTaskBasicConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_ssm_maintenance_window" "foo" { + name = "maintenance-window-%s" + schedule = "cron(0 16 ? * TUE *)" + duration = 3 + cutoff = 1 +} + +resource "aws_ssm_maintenance_window_task" "target" { + window_id = "${aws_ssm_maintenance_window.foo.id}" + task_type = "RUN_COMMAND" + task_arn = "AWS-RunShellScript" + priority = 1 + service_role_arn = "${aws_iam_role.ssm_role.arn}" + max_concurrency = "2" + max_errors = "1" + targets { + key = "InstanceIds" + values = ["${aws_instance.foo.id}"] + } +} + +resource "aws_instance" "foo" { + ami = "ami-4fccb37f" + + instance_type = "m1.small" +} + +resource "aws_iam_role" "ssm_role" { + name = "ssm-role-%s" + + assume_role_policy = < 0 { + return fmt.Errorf("Expected AWS SSM Maintenance Document to be gone, but was still found") + } + + return nil + } + + return nil +} + +func testAccAWSSSMMaintenanceWindowBasicConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_ssm_maintenance_window" "foo" { + name = "maintenance-window-%s" + schedule = "cron(0 16 ? * TUE *)" + duration = 3 + cutoff = 1 +} + +`, rName) +} + +func testAccAWSSSMMaintenanceWindowBasicConfigUpdated(rName string) string { + return fmt.Sprintf(` +resource "aws_ssm_maintenance_window" "foo" { + name = "updated-maintenance-window-%s" + schedule = "cron(0 16 ? * WED *)" + duration = 10 + cutoff = 8 +} + +`, rName) +} diff --git a/builtin/providers/aws/s3_tags.go b/builtin/providers/aws/s3_tags.go index 696821b7991f..f691cff46acf 100644 --- a/builtin/providers/aws/s3_tags.go +++ b/builtin/providers/aws/s3_tags.go @@ -121,7 +121,7 @@ func getTagSetS3(s3conn *s3.S3, bucket string) ([]*s3.Tag, error) { // compare a tag against a list of strings and checks if it should // be ignored or not func tagIgnoredS3(t *s3.Tag) bool { - filter := []string{"^aws:*"} + filter := []string{"^aws:"} for _, v := range filter { log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) if r, _ := regexp.MatchString(v, *t.Key); r == true { diff --git a/builtin/providers/aws/s3_tags_test.go b/builtin/providers/aws/s3_tags_test.go index 48d38b715813..42d5b605d048 100644 --- a/builtin/providers/aws/s3_tags_test.go +++ b/builtin/providers/aws/s3_tags_test.go @@ -1,14 +1,11 @@ package aws import ( - "fmt" "reflect" "testing" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/s3" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" ) func TestDiffTagsS3(t *testing.T) { @@ -78,26 +75,3 @@ func TestIgnoringTagsS3(t *testing.T) { } } } - -// testAccCheckTags can be used to check the tags on a resource. -func testAccCheckTagsS3( - ts *[]*s3.Tag, key string, value string) resource.TestCheckFunc { - return func(s *terraform.State) error { - m := tagsToMapS3(*ts) - v, ok := m[key] - if value != "" && !ok { - return fmt.Errorf("Missing tag: %s", key) - } else if value == "" && ok { - return fmt.Errorf("Extra tag: %s", key) - } - if value == "" { - return nil - } - - if v != value { - return fmt.Errorf("%s: bad value: %s", key, v) - } - - return nil - } -} diff --git a/builtin/providers/aws/structure.go b/builtin/providers/aws/structure.go index b0f3fa6e191c..56861d46f52d 100644 --- a/builtin/providers/aws/structure.go +++ b/builtin/providers/aws/structure.go @@ -28,6 +28,7 @@ import ( "github.com/aws/aws-sdk-go/service/rds" "github.com/aws/aws-sdk-go/service/redshift" "github.com/aws/aws-sdk-go/service/route53" + "github.com/aws/aws-sdk-go/service/ssm" "github.com/hashicorp/terraform/helper/schema" "gopkg.in/yaml.v2" ) @@ -820,7 +821,7 @@ func flattenResourceRecords(recs []*route53.ResourceRecord, typeStr string) []st if r.Value != nil { s := *r.Value if typeStr == "TXT" || typeStr == "SPF" { - s = strings.Replace(s, "\"", "", 2) + s = expandTxtEntry(s) } strs = append(strs, s) } @@ -833,14 +834,71 @@ func expandResourceRecords(recs []interface{}, typeStr string) []*route53.Resour for _, r := range recs { s := r.(string) if typeStr == "TXT" || typeStr == "SPF" { - // `flattenResourceRecords` removes quotes. Add them back. - s = fmt.Sprintf("\"%s\"", s) + s = flattenTxtEntry(s) } records = append(records, &route53.ResourceRecord{Value: aws.String(s)}) } return records } +// How 'flattenTxtEntry' and 'expandTxtEntry' work. +// +// In the Route 53, TXT entries are written using quoted strings, one per line. +// Example: +// "x=foo" +// "bar=12" +// +// In Terraform, there are two differences: +// - We use a list of strings instead of separating strings with newlines. +// - Within each string, we dont' include the surrounding quotes. +// Example: +// records = ["x=foo", "bar=12"] # Instead of ["\"x=foo\", \"bar=12\""] +// +// When we pull from Route 53, `expandTxtEntry` removes the surrounding quotes; +// when we push to Route 53, `flattenTxtEntry` adds them back. +// +// One complication is that a single TXT entry can have multiple quoted strings. +// For example, here are two TXT entries, one with two quoted strings and the +// other with three. +// "x=" "foo" +// "ba" "r" "=12" +// +// DNS clients are expected to merge the quoted strings before interpreting the +// value. Since `expandTxtEntry` only removes the quotes at the end we can still +// (hackily) represent the above configuration in Terraform: +// records = ["x=\" \"foo", "ba\" \"r\" \"=12"] +// +// The primary reason to use multiple strings for an entry is that DNS (and Route +// 53) doesn't allow a quoted string to be more than 255 characters long. If you +// want a longer TXT entry, you must use multiple quoted strings. +// +// It would be nice if this Terraform automatically split strings longer than 255 +// characters. For example, imagine "xxx..xxx" has 256 "x" characters. +// records = ["xxx..xxx"] +// When pushing to Route 53, this could be converted to: +// "xxx..xx" "x" +// +// This could also work when the user is already using multiple quoted strings: +// records = ["xxx.xxx\" \"yyy..yyy"] +// When pushing to Route 53, this could be converted to: +// "xxx..xx" "xyyy...y" "yy" +// +// If you want to add this feature, make sure to follow all the quoting rules in +// . If you make a mistake, people +// might end up relying on that mistake so fixing it would be a breaking change. + +func flattenTxtEntry(s string) string { + return fmt.Sprintf(`"%s"`, s) +} + +func expandTxtEntry(s string) string { + last := len(s) - 1 + if last != 0 && s[0] == '"' && s[last] == '"' { + s = s[1:last] + } + return s +} + func expandESClusterConfig(m map[string]interface{}) *elasticsearch.ElasticsearchClusterConfig { config := elasticsearch.ElasticsearchClusterConfig{} @@ -2028,3 +2086,39 @@ func sliceContainsMap(l []interface{}, m map[string]interface{}) (int, bool) { return -1, false } + +func expandAwsSsmTargets(d *schema.ResourceData) []*ssm.Target { + var targets []*ssm.Target + + targetConfig := d.Get("targets").([]interface{}) + + for _, tConfig := range targetConfig { + config := tConfig.(map[string]interface{}) + + target := &ssm.Target{ + Key: aws.String(config["key"].(string)), + Values: expandStringList(config["values"].([]interface{})), + } + + targets = append(targets, target) + } + + return targets +} + +func flattenAwsSsmTargets(targets []*ssm.Target) []map[string]interface{} { + if len(targets) == 0 { + return nil + } + + result := make([]map[string]interface{}, 0, len(targets)) + target := targets[0] + + t := make(map[string]interface{}) + t["key"] = *target.Key + t["values"] = flattenStringList(target.Values) + + result = append(result, t) + + return result +} diff --git a/builtin/providers/aws/structure_test.go b/builtin/providers/aws/structure_test.go index 33f822bb009c..11e74d2ba122 100644 --- a/builtin/providers/aws/structure_test.go +++ b/builtin/providers/aws/structure_test.go @@ -822,11 +822,15 @@ func TestFlattenResourceRecords(t *testing.T) { original := []string{ `127.0.0.1`, `"abc def"`, + `"abc" "def"`, + `"abc" ""`, } dequoted := []string{ `127.0.0.1`, `abc def`, + `abc" "def`, + `abc" "`, } var wrapped []*route53.ResourceRecord = nil diff --git a/builtin/providers/aws/tags.go b/builtin/providers/aws/tags.go index 57a8f6ab0a95..4d9d54480189 100644 --- a/builtin/providers/aws/tags.go +++ b/builtin/providers/aws/tags.go @@ -282,7 +282,7 @@ func tagsFromMapELBv2(m map[string]interface{}) []*elbv2.Tag { // tagIgnored compares a tag against a list of strings and checks if it should // be ignored or not func tagIgnored(t *ec2.Tag) bool { - filter := []string{"^aws:*"} + filter := []string{"^aws:"} for _, v := range filter { log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) if r, _ := regexp.MatchString(v, *t.Key); r == true { @@ -295,7 +295,7 @@ func tagIgnored(t *ec2.Tag) bool { // and for ELBv2 as well func tagIgnoredELBv2(t *elbv2.Tag) bool { - filter := []string{"^aws:*"} + filter := []string{"^aws:"} for _, v := range filter { log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) if r, _ := regexp.MatchString(v, *t.Key); r == true { diff --git a/builtin/providers/aws/tagsBeanstalk.go b/builtin/providers/aws/tagsBeanstalk.go index c3b7b95dac10..7b85d611603d 100644 --- a/builtin/providers/aws/tagsBeanstalk.go +++ b/builtin/providers/aws/tagsBeanstalk.go @@ -62,7 +62,7 @@ func tagsToMapBeanstalk(ts []*elasticbeanstalk.Tag) map[string]string { // compare a tag against a list of strings and checks if it should // be ignored or not func tagIgnoredBeanstalk(t *elasticbeanstalk.Tag) bool { - filter := []string{"^aws:*"} + filter := []string{"^aws:"} for _, v := range filter { log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) if r, _ := regexp.MatchString(v, *t.Key); r == true { diff --git a/builtin/providers/aws/tagsCloudtrail.go b/builtin/providers/aws/tagsCloudtrail.go index 020a6ea92311..b4302ddd100b 100644 --- a/builtin/providers/aws/tagsCloudtrail.go +++ b/builtin/providers/aws/tagsCloudtrail.go @@ -100,7 +100,7 @@ func tagsToMapCloudtrail(ts []*cloudtrail.Tag) map[string]string { // compare a tag against a list of strings and checks if it should // be ignored or not func tagIgnoredCloudtrail(t *cloudtrail.Tag) bool { - filter := []string{"^aws:*"} + filter := []string{"^aws:"} for _, v := range filter { log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) if r, _ := regexp.MatchString(v, *t.Key); r == true { diff --git a/builtin/providers/aws/tagsCodeBuild.go b/builtin/providers/aws/tagsCodeBuild.go index e28fc2146ae7..3302d7426387 100644 --- a/builtin/providers/aws/tagsCodeBuild.go +++ b/builtin/providers/aws/tagsCodeBuild.go @@ -55,7 +55,7 @@ func tagsToMapCodeBuild(ts []*codebuild.Tag) map[string]string { // compare a tag against a list of strings and checks if it should // be ignored or not func tagIgnoredCodeBuild(t *codebuild.Tag) bool { - filter := []string{"^aws:*"} + filter := []string{"^aws:"} for _, v := range filter { log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) if r, _ := regexp.MatchString(v, *t.Key); r == true { diff --git a/builtin/providers/aws/tagsEC.go b/builtin/providers/aws/tagsEC.go index 6b29057ad56c..b9b22af9ce9b 100644 --- a/builtin/providers/aws/tagsEC.go +++ b/builtin/providers/aws/tagsEC.go @@ -103,7 +103,7 @@ func tagsToMapEC(ts []*elasticache.Tag) map[string]string { // compare a tag against a list of strings and checks if it should // be ignored or not func tagIgnoredEC(t *elasticache.Tag) bool { - filter := []string{"^aws:*"} + filter := []string{"^aws:"} for _, v := range filter { log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) if r, _ := regexp.MatchString(v, *t.Key); r == true { diff --git a/builtin/providers/aws/tagsEFS.go b/builtin/providers/aws/tagsEFS.go index 2dc6189cc5e6..b61973165c07 100644 --- a/builtin/providers/aws/tagsEFS.go +++ b/builtin/providers/aws/tagsEFS.go @@ -102,7 +102,7 @@ func tagsToMapEFS(ts []*efs.Tag) map[string]string { // compare a tag against a list of strings and checks if it should // be ignored or not func tagIgnoredEFS(t *efs.Tag) bool { - filter := []string{"^aws:*"} + filter := []string{"^aws:"} for _, v := range filter { log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) if r, _ := regexp.MatchString(v, *t.Key); r == true { diff --git a/builtin/providers/aws/tagsELB.go b/builtin/providers/aws/tagsELB.go index 4c177b878481..081de9cc1a1a 100644 --- a/builtin/providers/aws/tagsELB.go +++ b/builtin/providers/aws/tagsELB.go @@ -102,7 +102,7 @@ func tagsToMapELB(ts []*elb.Tag) map[string]string { // compare a tag against a list of strings and checks if it should // be ignored or not func tagIgnoredELB(t *elb.Tag) bool { - filter := []string{"^aws:*"} + filter := []string{"^aws:"} for _, v := range filter { log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) if r, _ := regexp.MatchString(v, *t.Key); r == true { diff --git a/builtin/providers/aws/tagsGeneric.go b/builtin/providers/aws/tagsGeneric.go index 08bba6756059..d494a4972f6d 100644 --- a/builtin/providers/aws/tagsGeneric.go +++ b/builtin/providers/aws/tagsGeneric.go @@ -57,7 +57,7 @@ func tagsToMapGeneric(ts map[string]*string) map[string]string { // compare a tag against a list of strings and checks if it should // be ignored or not func tagIgnoredGeneric(k string) bool { - filter := []string{"^aws:*"} + filter := []string{"^aws:"} for _, v := range filter { log.Printf("[DEBUG] Matching %v with %v\n", v, k) if r, _ := regexp.MatchString(v, k); r == true { diff --git a/builtin/providers/aws/tagsInspector.go b/builtin/providers/aws/tagsInspector.go index f846319f9407..ef18f33c2ec1 100644 --- a/builtin/providers/aws/tagsInspector.go +++ b/builtin/providers/aws/tagsInspector.go @@ -62,7 +62,7 @@ func tagsToMapInspector(ts []*inspector.ResourceGroupTag) map[string]string { // compare a tag against a list of strings and checks if it should // be ignored or not func tagIgnoredInspector(t *inspector.ResourceGroupTag) bool { - filter := []string{"^aws:*"} + filter := []string{"^aws:"} for _, v := range filter { log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) if r, _ := regexp.MatchString(v, *t.Key); r == true { diff --git a/builtin/providers/aws/tagsKMS.go b/builtin/providers/aws/tagsKMS.go index d4d2eca1c53c..4e918414ea5f 100644 --- a/builtin/providers/aws/tagsKMS.go +++ b/builtin/providers/aws/tagsKMS.go @@ -103,7 +103,7 @@ func tagsToMapKMS(ts []*kms.Tag) map[string]string { // compare a tag against a list of strings and checks if it should // be ignored or not func tagIgnoredKMS(t *kms.Tag) bool { - filter := []string{"^aws:*"} + filter := []string{"^aws:"} for _, v := range filter { log.Printf("[DEBUG] Matching %v with %v\n", v, *t.TagKey) if r, _ := regexp.MatchString(v, *t.TagKey); r == true { diff --git a/builtin/providers/aws/tagsRDS.go b/builtin/providers/aws/tagsRDS.go index 1a5b0c1c7d47..2d64113482b1 100644 --- a/builtin/providers/aws/tagsRDS.go +++ b/builtin/providers/aws/tagsRDS.go @@ -121,7 +121,7 @@ func saveTagsRDS(conn *rds.RDS, d *schema.ResourceData, arn string) error { // compare a tag against a list of strings and checks if it should // be ignored or not func tagIgnoredRDS(t *rds.Tag) bool { - filter := []string{"^aws:*"} + filter := []string{"^aws:"} for _, v := range filter { log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) if r, _ := regexp.MatchString(v, *t.Key); r == true { diff --git a/builtin/providers/aws/tagsRedshift.go b/builtin/providers/aws/tagsRedshift.go index 0a87f71145bd..715e8204502e 100644 --- a/builtin/providers/aws/tagsRedshift.go +++ b/builtin/providers/aws/tagsRedshift.go @@ -96,7 +96,7 @@ func tagsToMapRedshift(ts []*redshift.Tag) map[string]string { // compare a tag against a list of strings and checks if it should // be ignored or not func tagIgnoredRedshift(t *redshift.Tag) bool { - filter := []string{"^aws:*"} + filter := []string{"^aws:"} for _, v := range filter { log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) if r, _ := regexp.MatchString(v, *t.Key); r == true { diff --git a/builtin/providers/aws/tags_elasticsearchservice.go b/builtin/providers/aws/tags_elasticsearchservice.go index f048e468160b..e585d1afa72a 100644 --- a/builtin/providers/aws/tags_elasticsearchservice.go +++ b/builtin/providers/aws/tags_elasticsearchservice.go @@ -102,7 +102,7 @@ func tagsToMapElasticsearchService(ts []*elasticsearch.Tag) map[string]string { // compare a tag against a list of strings and checks if it should // be ignored or not func tagIgnoredElasticsearchService(t *elasticsearch.Tag) bool { - filter := []string{"^aws:*"} + filter := []string{"^aws:"} for _, v := range filter { log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) if r, _ := regexp.MatchString(v, *t.Key); r == true { diff --git a/builtin/providers/aws/tags_kinesis.go b/builtin/providers/aws/tags_kinesis.go index 5c5935988427..a5622e95d61b 100644 --- a/builtin/providers/aws/tags_kinesis.go +++ b/builtin/providers/aws/tags_kinesis.go @@ -113,7 +113,7 @@ func tagsToMapKinesis(ts []*kinesis.Tag) map[string]string { // compare a tag against a list of strings and checks if it should // be ignored or not func tagIgnoredKinesis(t *kinesis.Tag) bool { - filter := []string{"^aws:*"} + filter := []string{"^aws:"} for _, v := range filter { log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) if r, _ := regexp.MatchString(v, *t.Key); r == true { diff --git a/builtin/providers/aws/tags_route53.go b/builtin/providers/aws/tags_route53.go index d675f42e038b..3721672914a3 100644 --- a/builtin/providers/aws/tags_route53.go +++ b/builtin/providers/aws/tags_route53.go @@ -99,7 +99,7 @@ func tagsToMapR53(ts []*route53.Tag) map[string]string { // compare a tag against a list of strings and checks if it should // be ignored or not func tagIgnoredRoute53(t *route53.Tag) bool { - filter := []string{"^aws:*"} + filter := []string{"^aws:"} for _, v := range filter { log.Printf("[DEBUG] Matching %v with %v\n", v, *t.Key) if r, _ := regexp.MatchString(v, *t.Key); r == true { diff --git a/builtin/providers/aws/validators.go b/builtin/providers/aws/validators.go index a68253707034..f0974549103e 100644 --- a/builtin/providers/aws/validators.go +++ b/builtin/providers/aws/validators.go @@ -154,22 +154,25 @@ func validateStreamViewType(v interface{}, k string) (ws []string, errors []erro } if !viewTypes[value] { - errors = append(errors, fmt.Errorf("%q be a valid DynamoDB StreamViewType", k)) + errors = append(errors, fmt.Errorf("%q must be a valid DynamoDB StreamViewType", k)) } return } func validateElbName(v interface{}, k string) (ws []string, errors []error) { value := v.(string) - if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { - errors = append(errors, fmt.Errorf( - "only alphanumeric characters and hyphens allowed in %q: %q", - k, value)) + if len(value) == 0 { + return // short-circuit } if len(value) > 32 { errors = append(errors, fmt.Errorf( "%q cannot be longer than 32 characters: %q", k, value)) } + if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "only alphanumeric characters and hyphens allowed in %q: %q", + k, value)) + } if regexp.MustCompile(`^-`).MatchString(value) { errors = append(errors, fmt.Errorf( "%q cannot begin with a hyphen: %q", k, value)) @@ -1301,3 +1304,18 @@ func validateWafMetricName(v interface{}, k string) (ws []string, errors []error } return } + +func validateIamRoleDescription(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + + if len(value) > 1000 { + errors = append(errors, fmt.Errorf("%q cannot be longer than 1000 caracters", k)) + } + + if !regexp.MustCompile(`[\p{L}\p{M}\p{Z}\p{S}\p{N}\p{P}]*`).MatchString(value) { + errors = append(errors, fmt.Errorf( + "Only alphanumeric & accented characters allowed in %q: %q (Must satisfy regular expression pattern: [\\p{L}\\p{M}\\p{Z}\\p{S}\\p{N}\\p{P}]*)", + k, value)) + } + return +} diff --git a/builtin/providers/aws/validators_test.go b/builtin/providers/aws/validators_test.go index b344f206d50a..3d323ca51e9b 100644 --- a/builtin/providers/aws/validators_test.go +++ b/builtin/providers/aws/validators_test.go @@ -2209,3 +2209,26 @@ func TestValidateWafMetricName(t *testing.T) { } } } + +func TestValidateIamRoleDescription(t *testing.T) { + validNames := []string{ + "This 1s a D3scr!pti0n with weird content: @ #^ù£ê®æ ø]ŒîÏî~ÈÙ£÷=,ë", + strings.Repeat("W", 1000), + } + for _, v := range validNames { + _, errors := validateIamRoleDescription(v, "description") + if len(errors) != 0 { + t.Fatalf("%q should be a valid IAM Role Description: %q", v, errors) + } + } + + invalidNames := []string{ + strings.Repeat("W", 1001), // > 1000 + } + for _, v := range invalidNames { + _, errors := validateIamRoleDescription(v, "description") + if len(errors) == 0 { + t.Fatalf("%q should be an invalid IAM Role Description", v) + } + } +} diff --git a/builtin/providers/azurerm/config.go b/builtin/providers/azurerm/config.go index 8535b67c7e26..ff6e6c51a290 100644 --- a/builtin/providers/azurerm/config.go +++ b/builtin/providers/azurerm/config.go @@ -19,6 +19,7 @@ import ( "github.com/Azure/azure-sdk-for-go/arm/resources/resources" "github.com/Azure/azure-sdk-for-go/arm/scheduler" "github.com/Azure/azure-sdk-for-go/arm/servicebus" + "github.com/Azure/azure-sdk-for-go/arm/sql" "github.com/Azure/azure-sdk-for-go/arm/storage" "github.com/Azure/azure-sdk-for-go/arm/trafficmanager" mainStorage "github.com/Azure/azure-sdk-for-go/storage" @@ -99,6 +100,8 @@ type ArmClient struct { serviceBusSubscriptionsClient servicebus.SubscriptionsClient keyVaultClient keyvault.VaultsClient + + sqlElasticPoolsClient sql.ElasticPoolsClient } func withRequestLogging() autorest.SendDecorator { @@ -458,6 +461,12 @@ func (c *Config) getArmClient() (*ArmClient, error) { kvc.Sender = autorest.CreateSender(withRequestLogging()) client.keyVaultClient = kvc + sqlepc := sql.NewElasticPoolsClientWithBaseURI(endpoint, c.SubscriptionID) + setUserAgent(&sqlepc.Client) + sqlepc.Authorizer = spt + sqlepc.Sender = autorest.CreateSender(withRequestLogging()) + client.sqlElasticPoolsClient = sqlepc + return &client, nil } diff --git a/builtin/providers/azurerm/import_arm_sql_elasticpool_test.go b/builtin/providers/azurerm/import_arm_sql_elasticpool_test.go new file mode 100644 index 000000000000..1657f5c15c26 --- /dev/null +++ b/builtin/providers/azurerm/import_arm_sql_elasticpool_test.go @@ -0,0 +1,32 @@ +package azurerm + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "testing" +) + +func TestAccAzureRMSqlElasticPool_importBasic(t *testing.T) { + resourceName := "azurerm_sql_elasticpool.test" + + ri := acctest.RandInt() + config := fmt.Sprintf(testAccAzureRMSqlElasticPool_basic, ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlElasticPoolDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: config, + }, + + resource.TestStep{ + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/builtin/providers/azurerm/provider.go b/builtin/providers/azurerm/provider.go index 3ff68a83ca3c..e7875650eef7 100644 --- a/builtin/providers/azurerm/provider.go +++ b/builtin/providers/azurerm/provider.go @@ -99,6 +99,7 @@ func Provider() terraform.ResourceProvider { "azurerm_servicebus_namespace": resourceArmServiceBusNamespace(), "azurerm_servicebus_subscription": resourceArmServiceBusSubscription(), "azurerm_servicebus_topic": resourceArmServiceBusTopic(), + "azurerm_sql_elasticpool": resourceArmSqlElasticPool(), "azurerm_storage_account": resourceArmStorageAccount(), "azurerm_storage_blob": resourceArmStorageBlob(), "azurerm_storage_container": resourceArmStorageContainer(), diff --git a/builtin/providers/azurerm/resource_arm_sql_database.go b/builtin/providers/azurerm/resource_arm_sql_database.go index b022c7d1b33c..6959586b57ef 100644 --- a/builtin/providers/azurerm/resource_arm_sql_database.go +++ b/builtin/providers/azurerm/resource_arm_sql_database.go @@ -158,6 +158,10 @@ func resourceArmSqlDatabaseCreate(d *schema.ResourceData, meta interface{}) erro command.RequestedServiceObjectiveID = azure.String(v.(string)) } + if v, ok := d.GetOk("elastic_pool_name"); ok { + command.ElasticPoolName = azure.String(v.(string)) + } + if v, ok := d.GetOk("requested_service_objective_name"); ok { command.RequestedServiceObjectiveName = azure.String(v.(string)) } @@ -216,6 +220,7 @@ func resourceArmSqlDatabaseRead(d *schema.ResourceData, meta interface{}) error d.Set("name", resp.Name) d.Set("creation_date", resp.CreationDate) d.Set("default_secondary_location", resp.DefaultSecondaryLocation) + d.Set("elastic_pool_name", resp.ElasticPoolName) flattenAndSetTags(d, resp.Tags) diff --git a/builtin/providers/azurerm/resource_arm_sql_database_test.go b/builtin/providers/azurerm/resource_arm_sql_database_test.go index 9f80eb4261ad..fb306e04aa9d 100644 --- a/builtin/providers/azurerm/resource_arm_sql_database_test.go +++ b/builtin/providers/azurerm/resource_arm_sql_database_test.go @@ -65,6 +65,26 @@ func TestAccAzureRMSqlDatabase_basic(t *testing.T) { }) } +func TestAccAzureRMSqlDatabase_elasticPool(t *testing.T) { + ri := acctest.RandInt() + config := fmt.Sprintf(testAccAzureRMSqlDatabase_elasticPool, ri, ri, ri, ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlDatabaseDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlDatabaseExists("azurerm_sql_database.test"), + resource.TestCheckResourceAttr("azurerm_sql_database.test", "elastic_pool_name", fmt.Sprintf("acctestep%d", ri)), + ), + }, + }, + }) +} + func TestAccAzureRMSqlDatabase_withTags(t *testing.T) { ri := acctest.RandInt() preConfig := fmt.Sprintf(testAccAzureRMSqlDatabase_withTags, ri, ri, ri) @@ -163,6 +183,44 @@ func testCheckAzureRMSqlDatabaseDestroy(s *terraform.State) error { return nil } +var testAccAzureRMSqlDatabase_elasticPool = ` +resource "azurerm_resource_group" "test" { + name = "acctestRG_%d" + location = "West US" +} + +resource "azurerm_sql_server" "test" { + name = "acctestsqlserver%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "West US" + version = "12.0" + administrator_login = "mradministrator" + administrator_login_password = "thisIsDog11" +} + +resource "azurerm_sql_elasticpool" "test" { + name = "acctestep%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "West US" + server_name = "${azurerm_sql_server.test.name}" + edition = "Basic" + dtu = 50 + pool_size = 5000 +} + +resource "azurerm_sql_database" "test" { + name = "acctestdb%d" + resource_group_name = "${azurerm_resource_group.test.name}" + server_name = "${azurerm_sql_server.test.name}" + location = "West US" + edition = "${azurerm_sql_elasticpool.test.edition}" + collation = "SQL_Latin1_General_CP1_CI_AS" + max_size_bytes = "1073741824" + elastic_pool_name = "${azurerm_sql_elasticpool.test.name}" + requested_service_objective_name = "ElasticPool" +} +` + var testAccAzureRMSqlDatabase_basic = ` resource "azurerm_resource_group" "test" { name = "acctestRG_%d" diff --git a/builtin/providers/azurerm/resource_arm_sql_elasticpool.go b/builtin/providers/azurerm/resource_arm_sql_elasticpool.go new file mode 100644 index 000000000000..6231950fb605 --- /dev/null +++ b/builtin/providers/azurerm/resource_arm_sql_elasticpool.go @@ -0,0 +1,220 @@ +package azurerm + +import ( + "fmt" + "github.com/Azure/azure-sdk-for-go/arm/sql" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "log" + "net/http" + "time" +) + +func resourceArmSqlElasticPool() *schema.Resource { + return &schema.Resource{ + Create: resourceArmSqlElasticPoolCreate, + Read: resourceArmSqlElasticPoolRead, + Update: resourceArmSqlElasticPoolCreate, + Delete: resourceArmSqlElasticPoolDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "location": locationSchema(), + + "resource_group_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "server_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "edition": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateSqlElasticPoolEdition(), + }, + + "dtu": { + Type: schema.TypeInt, + Required: true, + }, + + "db_dtu_min": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + + "db_dtu_max": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + + "pool_size": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + + "creation_date": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": tagsSchema(), + }, + } +} + +func resourceArmSqlElasticPoolCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient) + elasticPoolsClient := client.sqlElasticPoolsClient + + log.Printf("[INFO] preparing arguments for Azure ARM SQL ElasticPool creation.") + + name := d.Get("name").(string) + serverName := d.Get("server_name").(string) + location := d.Get("location").(string) + resGroup := d.Get("resource_group_name").(string) + tags := d.Get("tags").(map[string]interface{}) + + elasticPool := sql.ElasticPool{ + Name: &name, + Location: &location, + ElasticPoolProperties: getArmSqlElasticPoolProperties(d), + Tags: expandTags(tags), + } + + _, err := elasticPoolsClient.CreateOrUpdate(resGroup, serverName, name, elasticPool, make(chan struct{})) + if err != nil { + return err + } + + read, err := elasticPoolsClient.Get(resGroup, serverName, name) + if err != nil { + return err + } + if read.ID == nil { + return fmt.Errorf("Cannot read SQL ElasticPool %s (resource group %s) ID", name, resGroup) + } + + d.SetId(*read.ID) + + return resourceArmSqlElasticPoolRead(d, meta) +} + +func resourceArmSqlElasticPoolRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient) + elasticPoolsClient := client.sqlElasticPoolsClient + + resGroup, serverName, name, err := parseArmSqlElasticPoolId(d.Id()) + if err != nil { + return err + } + + resp, err := elasticPoolsClient.Get(resGroup, serverName, name) + if err != nil { + if resp.StatusCode == http.StatusNotFound { + d.SetId("") + return nil + } + return fmt.Errorf("Error making Read request on Sql Elastic Pool %s: %s", name, err) + } + + d.Set("name", resp.Name) + d.Set("resource_group_name", resGroup) + d.Set("location", azureRMNormalizeLocation(*resp.Location)) + d.Set("server_name", serverName) + + elasticPool := resp.ElasticPoolProperties + + if elasticPool != nil { + d.Set("edition", string(elasticPool.Edition)) + d.Set("dtu", int(*elasticPool.Dtu)) + d.Set("db_dtu_min", int(*elasticPool.DatabaseDtuMin)) + d.Set("db_dtu_max", int(*elasticPool.DatabaseDtuMax)) + d.Set("pool_size", int(*elasticPool.StorageMB)) + + if elasticPool.CreationDate != nil { + d.Set("creation_date", elasticPool.CreationDate.Format(time.RFC3339)) + } + } + + flattenAndSetTags(d, resp.Tags) + + return nil +} + +func resourceArmSqlElasticPoolDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient) + elasticPoolsClient := client.sqlElasticPoolsClient + + resGroup, serverName, name, err := parseArmSqlElasticPoolId(d.Id()) + if err != nil { + return err + } + + _, err = elasticPoolsClient.Delete(resGroup, serverName, name) + + return err +} + +func getArmSqlElasticPoolProperties(d *schema.ResourceData) *sql.ElasticPoolProperties { + edition := sql.ElasticPoolEditions(d.Get("edition").(string)) + dtu := int32(d.Get("dtu").(int)) + + props := &sql.ElasticPoolProperties{ + Edition: edition, + Dtu: &dtu, + } + + if databaseDtuMin, ok := d.GetOk("db_dtu_min"); ok { + databaseDtuMin := int32(databaseDtuMin.(int)) + props.DatabaseDtuMin = &databaseDtuMin + } + + if databaseDtuMax, ok := d.GetOk("db_dtu_max"); ok { + databaseDtuMax := int32(databaseDtuMax.(int)) + props.DatabaseDtuMax = &databaseDtuMax + } + + if poolSize, ok := d.GetOk("pool_size"); ok { + poolSize := int32(poolSize.(int)) + props.StorageMB = &poolSize + } + + return props +} + +func parseArmSqlElasticPoolId(sqlElasticPoolId string) (string, string, string, error) { + id, err := parseAzureResourceID(sqlElasticPoolId) + if err != nil { + return "", "", "", fmt.Errorf("[ERROR] Unable to parse SQL ElasticPool ID '%s': %+v", sqlElasticPoolId, err) + } + + return id.ResourceGroup, id.Path["servers"], id.Path["elasticPools"], nil +} + +func validateSqlElasticPoolEdition() schema.SchemaValidateFunc { + return validation.StringInSlice([]string{ + string(sql.ElasticPoolEditionsBasic), + string(sql.ElasticPoolEditionsStandard), + string(sql.ElasticPoolEditionsPremium), + }, false) +} diff --git a/builtin/providers/azurerm/resource_arm_sql_elasticpool_test.go b/builtin/providers/azurerm/resource_arm_sql_elasticpool_test.go new file mode 100644 index 000000000000..991eb691bfca --- /dev/null +++ b/builtin/providers/azurerm/resource_arm_sql_elasticpool_test.go @@ -0,0 +1,168 @@ +package azurerm + +import ( + "fmt" + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "net/http" + "testing" +) + +func TestAccAzureRMSqlElasticPool_basic(t *testing.T) { + ri := acctest.RandInt() + config := fmt.Sprintf(testAccAzureRMSqlElasticPool_basic, ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlElasticPoolDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlElasticPoolExists("azurerm_sql_elasticpool.test"), + ), + }, + }, + }) +} + +func TestAccAzureRMSqlElasticPool_resizeDtu(t *testing.T) { + ri := acctest.RandInt() + preConfig := fmt.Sprintf(testAccAzureRMSqlElasticPool_basic, ri) + postConfig := fmt.Sprintf(testAccAzureRMSqlElasticPool_resizedDtu, ri) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMSqlElasticPoolDestroy, + Steps: []resource.TestStep{ + { + Config: preConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlElasticPoolExists("azurerm_sql_elasticpool.test"), + resource.TestCheckResourceAttr( + "azurerm_sql_elasticpool.test", "dtu", "50"), + resource.TestCheckResourceAttr( + "azurerm_sql_elasticpool.test", "pool_size", "5000"), + ), + }, + { + Config: postConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMSqlElasticPoolExists("azurerm_sql_elasticpool.test"), + resource.TestCheckResourceAttr( + "azurerm_sql_elasticpool.test", "dtu", "100"), + resource.TestCheckResourceAttr( + "azurerm_sql_elasticpool.test", "pool_size", "10000"), + ), + }, + }, + }) +} + +func testCheckAzureRMSqlElasticPoolExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + ressource, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + resourceGroup, serverName, name, err := parseArmSqlElasticPoolId(ressource.Primary.ID) + if err != nil { + return err + } + + conn := testAccProvider.Meta().(*ArmClient).sqlElasticPoolsClient + + resp, err := conn.Get(resourceGroup, serverName, name) + if err != nil { + return fmt.Errorf("Bad: Get on sqlElasticPoolsClient: %s", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: SQL Elastic Pool %q on server: %q (resource group: %q) does not exist", name, serverName, resourceGroup) + } + + return nil + } +} + +func testCheckAzureRMSqlElasticPoolDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*ArmClient).sqlElasticPoolsClient + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_sql_elasticpool" { + continue + } + + name := rs.Primary.Attributes["name"] + serverName := rs.Primary.Attributes["server_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := conn.Get(resourceGroup, serverName, name) + + if err != nil { + return nil + } + + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("SQL Elastic Pool still exists:\n%#v", resp.ElasticPoolProperties) + } + } + + return nil +} + +var testAccAzureRMSqlElasticPool_basic = ` +resource "azurerm_resource_group" "test" { + name = "acctest-%[1]d" + location = "West US" +} + +resource "azurerm_sql_server" "test" { + name = "acctest%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "West US" + version = "12.0" + administrator_login = "4dm1n157r470r" + administrator_login_password = "4-v3ry-53cr37-p455w0rd" +} + +resource "azurerm_sql_elasticpool" "test" { + name = "acctest-pool-%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "West US" + server_name = "${azurerm_sql_server.test.name}" + edition = "Basic" + dtu = 50 + pool_size = 5000 +} +` + +var testAccAzureRMSqlElasticPool_resizedDtu = ` +resource "azurerm_resource_group" "test" { + name = "acctest-%[1]d" + location = "West US" +} + +resource "azurerm_sql_server" "test" { + name = "acctest%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "West US" + version = "12.0" + administrator_login = "4dm1n157r470r" + administrator_login_password = "4-v3ry-53cr37-p455w0rd" +} + +resource "azurerm_sql_elasticpool" "test" { + name = "acctest-pool-%[1]d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "West US" + server_name = "${azurerm_sql_server.test.name}" + edition = "Basic" + dtu = 100 + pool_size = 10000 +} +` diff --git a/builtin/providers/azurerm/resource_arm_template_deployment.go b/builtin/providers/azurerm/resource_arm_template_deployment.go index 9349c9b6abfa..9538f61ee4f8 100644 --- a/builtin/providers/azurerm/resource_arm_template_deployment.go +++ b/builtin/providers/azurerm/resource_arm_template_deployment.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "net/http" + "strconv" "strings" "time" @@ -155,20 +156,40 @@ func resourceArmTemplateDeploymentRead(d *schema.ResourceData, meta interface{}) if resp.Properties.Outputs != nil && len(*resp.Properties.Outputs) > 0 { outputs = make(map[string]string) for key, output := range *resp.Properties.Outputs { + log.Printf("[DEBUG] Processing deployment output %s", key) outputMap := output.(map[string]interface{}) outputValue, ok := outputMap["value"] if !ok { - // No value + log.Printf("[DEBUG] No value - skipping") continue } + outputType, ok := outputMap["type"] + if !ok { + log.Printf("[DEBUG] No type - skipping") + continue + } + + var outputValueString string + switch strings.ToLower(outputType.(string)) { + case "bool": + outputValueString = strconv.FormatBool(outputValue.(bool)) + + case "string": + outputValueString = outputValue.(string) - outputs[key] = outputValue.(string) + case "int": + outputValueString = fmt.Sprint(outputValue) + + default: + log.Printf("[WARN] Ignoring output %s: Outputs of type %s are not currently supported in azurerm_template_deployment.", + key, outputType) + continue + } + outputs[key] = outputValueString } } - d.Set("outputs", outputs) - - return nil + return d.Set("outputs", outputs) } func resourceArmTemplateDeploymentDelete(d *schema.ResourceData, meta interface{}) error { diff --git a/builtin/providers/azurerm/resource_arm_template_deployment_test.go b/builtin/providers/azurerm/resource_arm_template_deployment_test.go index d69716d8bfe8..b3ade2e0a34d 100644 --- a/builtin/providers/azurerm/resource_arm_template_deployment_test.go +++ b/builtin/providers/azurerm/resource_arm_template_deployment_test.go @@ -68,6 +68,29 @@ func TestAccAzureRMTemplateDeployment_withParams(t *testing.T) { }) } +func TestAccAzureRMTemplateDeployment_withOutputs(t *testing.T) { + ri := acctest.RandInt() + config := fmt.Sprintf(testAccAzureRMTemplateDeployment_withOutputs, ri, ri, ri) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMTemplateDeploymentDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMTemplateDeploymentExists("azurerm_template_deployment.test"), + resource.TestCheckOutput("tfIntOutput", "-123"), + resource.TestCheckOutput("tfStringOutput", "Standard_GRS"), + resource.TestCheckOutput("tfFalseOutput", "false"), + resource.TestCheckOutput("tfTrueOutput", "true"), + resource.TestCheckResourceAttr("azurerm_template_deployment.test", "outputs.stringOutput", "Standard_GRS"), + ), + }, + }, + }) +} + func TestAccAzureRMTemplateDeployment_withError(t *testing.T) { ri := acctest.RandInt() config := fmt.Sprintf(testAccAzureRMTemplateDeployment_withError, ri, ri) @@ -275,7 +298,14 @@ var testAccAzureRMTemplateDeployment_withParams = ` } output "test" { - value = "${azurerm_template_deployment.test.outputs.testOutput}" + value = "${azurerm_template_deployment.test.outputs["testOutput"]}" + } + + resource "azurerm_storage_container" "using-outputs" { + name = "vhds" + resource_group_name = "${azurerm_resource_group.test.name}" + storage_account_name = "${azurerm_template_deployment.test.outputs["accountName"]}" + container_access_type = "private" } resource "azurerm_template_deployment" "test" { @@ -339,6 +369,10 @@ var testAccAzureRMTemplateDeployment_withParams = ` "testOutput": { "type": "string", "value": "Output Value" + }, + "accountName": { + "type": "string", + "value": "[variables('storageAccountName')]" } } } @@ -352,6 +386,126 @@ DEPLOY ` +var testAccAzureRMTemplateDeployment_withOutputs = ` + resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "West US" + } + + output "tfStringOutput" { + value = "${azurerm_template_deployment.test.outputs.stringOutput}" + } + + output "tfIntOutput" { + value = "${azurerm_template_deployment.test.outputs.intOutput}" + } + + output "tfFalseOutput" { + value = "${azurerm_template_deployment.test.outputs.falseOutput}" + } + + output "tfTrueOutput" { + value = "${azurerm_template_deployment.test.outputs.trueOutput}" + } + + resource "azurerm_template_deployment" "test" { + name = "acctesttemplate-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + template_body = < 0 && v[0] != nil } +func resourceHerokuAppImport(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + client := m.(*heroku.Service) + + app, err := client.AppInfo(context.TODO(), d.Id()) + if err != nil { + return nil, err + } + + // Flag organization apps by setting the organization name + if app.Organization != nil { + d.Set("organization", []map[string]interface{}{ + {"name": app.Organization.Name}, + }) + } + + return []*schema.ResourceData{d}, nil +} + func switchHerokuAppCreate(d *schema.ResourceData, meta interface{}) error { if isOrganizationApp(d) { return resourceHerokuOrgAppCreate(d, meta) diff --git a/builtin/providers/http/data_source.go b/builtin/providers/http/data_source.go new file mode 100644 index 000000000000..221de4adf9ad --- /dev/null +++ b/builtin/providers/http/data_source.go @@ -0,0 +1,104 @@ +package http + +import ( + "fmt" + "io/ioutil" + "net/http" + "regexp" + "time" + + "github.com/hashicorp/terraform/helper/schema" +) + +func dataSource() *schema.Resource { + return &schema.Resource{ + Read: dataSourceRead, + + Schema: map[string]*schema.Schema{ + "url": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + + "request_headers": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + + "body": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + } +} + +func dataSourceRead(d *schema.ResourceData, meta interface{}) error { + + url := d.Get("url").(string) + headers := d.Get("request_headers").(map[string]interface{}) + + client := &http.Client{} + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return fmt.Errorf("Error creating request: %s", err) + } + + for name, value := range headers { + req.Header.Set(name, value.(string)) + } + + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("Error during making a request: %s", url) + } + + defer resp.Body.Close() + + if resp.StatusCode != 200 { + return fmt.Errorf("HTTP request error. Response code: %d", resp.StatusCode) + } + + contentType := resp.Header.Get("Content-Type") + if contentType == "" || isContentTypeAllowed(contentType) == false { + return fmt.Errorf("Content-Type is not a text type. Got: %s", contentType) + } + + bytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("Error while reading response body. %s", err) + } + + d.Set("body", string(bytes)) + d.SetId(time.Now().UTC().String()) + + return nil +} + +// This is to prevent potential issues w/ binary files +// and generally unprintable characters +// See https://github.com/hashicorp/terraform/pull/3858#issuecomment-156856738 +func isContentTypeAllowed(contentType string) bool { + allowedContentTypes := []*regexp.Regexp{ + regexp.MustCompile("^text/.+"), + regexp.MustCompile("^application/json$"), + } + + for _, r := range allowedContentTypes { + if r.MatchString(contentType) { + return true + } + } + + return false +} diff --git a/builtin/providers/http/data_source_test.go b/builtin/providers/http/data_source_test.go new file mode 100644 index 000000000000..8ad73ce36028 --- /dev/null +++ b/builtin/providers/http/data_source_test.go @@ -0,0 +1,166 @@ +package http + +import ( + "fmt" + "net/http" + "net/http/httptest" + "regexp" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +type TestHttpMock struct { + server *httptest.Server +} + +const testDataSourceConfig_basic = ` +data "http" "http_test" { + url = "%s/meta_%d.txt" +} + +output "body" { + value = "${data.http.http_test.body}" +} +` + +func TestDataSource_http200(t *testing.T) { + testHttpMock := setUpMockHttpServer() + + defer testHttpMock.server.Close() + + resource.UnitTest(t, resource.TestCase{ + Providers: testProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf(testDataSourceConfig_basic, testHttpMock.server.URL, 200), + Check: func(s *terraform.State) error { + _, ok := s.RootModule().Resources["data.http.http_test"] + if !ok { + return fmt.Errorf("missing data resource") + } + + outputs := s.RootModule().Outputs + + if outputs["body"].Value != "1.0.0" { + return fmt.Errorf( + `'body' output is %s; want '1.0.0'`, + outputs["body"].Value, + ) + } + + return nil + }, + }, + }, + }) +} + +func TestDataSource_http404(t *testing.T) { + testHttpMock := setUpMockHttpServer() + + defer testHttpMock.server.Close() + + resource.UnitTest(t, resource.TestCase{ + Providers: testProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf(testDataSourceConfig_basic, testHttpMock.server.URL, 404), + ExpectError: regexp.MustCompile("HTTP request error. Response code: 404"), + }, + }, + }) +} + +const testDataSourceConfig_withHeaders = ` +data "http" "http_test" { + url = "%s/restricted/meta_%d.txt" + + request_headers = { + "Authorization" = "Zm9vOmJhcg==" + } +} + +output "body" { + value = "${data.http.http_test.body}" +} +` + +func TestDataSource_withHeaders200(t *testing.T) { + testHttpMock := setUpMockHttpServer() + + defer testHttpMock.server.Close() + + resource.UnitTest(t, resource.TestCase{ + Providers: testProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf(testDataSourceConfig_withHeaders, testHttpMock.server.URL, 200), + Check: func(s *terraform.State) error { + _, ok := s.RootModule().Resources["data.http.http_test"] + if !ok { + return fmt.Errorf("missing data resource") + } + + outputs := s.RootModule().Outputs + + if outputs["body"].Value != "1.0.0" { + return fmt.Errorf( + `'body' output is %s; want '1.0.0'`, + outputs["body"].Value, + ) + } + + return nil + }, + }, + }, + }) +} + +const testDataSourceConfig_error = ` +data "http" "http_test" { + +} +` + +func TestDataSource_compileError(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + Providers: testProviders, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testDataSourceConfig_error, + ExpectError: regexp.MustCompile("required field is not set"), + }, + }, + }) +} + +func setUpMockHttpServer() *TestHttpMock { + Server := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/meta_200.txt" { + w.WriteHeader(http.StatusOK) + w.Write([]byte("1.0.0")) + } else if r.URL.Path == "/restricted/meta_200.txt" { + if r.Header.Get("Authorization") == "Zm9vOmJhcg==" { + w.WriteHeader(http.StatusOK) + w.Write([]byte("1.0.0")) + } else { + w.WriteHeader(http.StatusForbidden) + } + } else if r.URL.Path == "/meta_404.txt" { + w.WriteHeader(http.StatusNotFound) + } else { + w.WriteHeader(http.StatusNotFound) + } + + w.Header().Add("Content-Type", "text/plain") + }), + ) + + return &TestHttpMock{ + server: Server, + } +} diff --git a/builtin/providers/http/provider.go b/builtin/providers/http/provider.go new file mode 100644 index 000000000000..e11b683465f2 --- /dev/null +++ b/builtin/providers/http/provider.go @@ -0,0 +1,18 @@ +package http + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +func Provider() terraform.ResourceProvider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{}, + + DataSourcesMap: map[string]*schema.Resource{ + "http": dataSource(), + }, + + ResourcesMap: map[string]*schema.Resource{}, + } +} diff --git a/builtin/providers/http/provider_test.go b/builtin/providers/http/provider_test.go new file mode 100644 index 000000000000..dd21abd455c4 --- /dev/null +++ b/builtin/providers/http/provider_test.go @@ -0,0 +1,18 @@ +package http + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +var testProviders = map[string]terraform.ResourceProvider{ + "http": Provider(), +} + +func TestProvider(t *testing.T) { + if err := Provider().(*schema.Provider).InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} diff --git a/builtin/providers/kubernetes/provider.go b/builtin/providers/kubernetes/provider.go index 8a613ab136e1..5420572c407c 100644 --- a/builtin/providers/kubernetes/provider.go +++ b/builtin/providers/kubernetes/provider.go @@ -87,9 +87,11 @@ func Provider() terraform.ResourceProvider { ResourcesMap: map[string]*schema.Resource{ "kubernetes_config_map": resourceKubernetesConfigMap(), + "kubernetes_limit_range": resourceKubernetesLimitRange(), "kubernetes_namespace": resourceKubernetesNamespace(), "kubernetes_persistent_volume": resourceKubernetesPersistentVolume(), "kubernetes_persistent_volume_claim": resourceKubernetesPersistentVolumeClaim(), + "kubernetes_resource_quota": resourceKubernetesResourceQuota(), "kubernetes_secret": resourceKubernetesSecret(), }, ConfigureFunc: providerConfigure, diff --git a/builtin/providers/kubernetes/resource_kubernetes_limit_range.go b/builtin/providers/kubernetes/resource_kubernetes_limit_range.go new file mode 100644 index 000000000000..e6ca5adf4b75 --- /dev/null +++ b/builtin/providers/kubernetes/resource_kubernetes_limit_range.go @@ -0,0 +1,188 @@ +package kubernetes + +import ( + "fmt" + "log" + + "github.com/hashicorp/terraform/helper/schema" + pkgApi "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + api "k8s.io/kubernetes/pkg/api/v1" + kubernetes "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5" +) + +func resourceKubernetesLimitRange() *schema.Resource { + return &schema.Resource{ + Create: resourceKubernetesLimitRangeCreate, + Read: resourceKubernetesLimitRangeRead, + Exists: resourceKubernetesLimitRangeExists, + Update: resourceKubernetesLimitRangeUpdate, + Delete: resourceKubernetesLimitRangeDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "metadata": namespacedMetadataSchema("limit range", true), + "spec": { + Type: schema.TypeList, + Description: "Spec defines the limits enforced. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status", + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "limit": { + Type: schema.TypeList, + Description: "Limits is the list of objects that are enforced.", + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "default": { + Type: schema.TypeMap, + Description: "Default resource requirement limit value by resource name if resource limit is omitted.", + Optional: true, + }, + "default_request": { + Type: schema.TypeMap, + Description: "The default resource requirement request value by resource name if resource request is omitted.", + Optional: true, + Computed: true, + }, + "max": { + Type: schema.TypeMap, + Description: "Max usage constraints on this kind by resource name.", + Optional: true, + }, + "max_limit_request_ratio": { + Type: schema.TypeMap, + Description: "The named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource.", + Optional: true, + }, + "min": { + Type: schema.TypeMap, + Description: "Min usage constraints on this kind by resource name.", + Optional: true, + }, + "type": { + Type: schema.TypeString, + Description: "Type of resource that this limit applies to.", + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func resourceKubernetesLimitRangeCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*kubernetes.Clientset) + + metadata := expandMetadata(d.Get("metadata").([]interface{})) + spec, err := expandLimitRangeSpec(d.Get("spec").([]interface{}), d.IsNewResource()) + if err != nil { + return err + } + limitRange := api.LimitRange{ + ObjectMeta: metadata, + Spec: spec, + } + log.Printf("[INFO] Creating new limit range: %#v", limitRange) + out, err := conn.CoreV1().LimitRanges(metadata.Namespace).Create(&limitRange) + if err != nil { + return fmt.Errorf("Failed to create limit range: %s", err) + } + log.Printf("[INFO] Submitted new limit range: %#v", out) + d.SetId(buildId(out.ObjectMeta)) + + return resourceKubernetesLimitRangeRead(d, meta) +} + +func resourceKubernetesLimitRangeRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*kubernetes.Clientset) + + namespace, name := idParts(d.Id()) + log.Printf("[INFO] Reading limit range %s", name) + limitRange, err := conn.CoreV1().LimitRanges(namespace).Get(name) + if err != nil { + log.Printf("[DEBUG] Received error: %#v", err) + return err + } + log.Printf("[INFO] Received limit range: %#v", limitRange) + + err = d.Set("metadata", flattenMetadata(limitRange.ObjectMeta)) + if err != nil { + return err + } + err = d.Set("spec", flattenLimitRangeSpec(limitRange.Spec)) + if err != nil { + return err + } + + return nil +} + +func resourceKubernetesLimitRangeUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*kubernetes.Clientset) + + namespace, name := idParts(d.Id()) + + ops := patchMetadata("metadata.0.", "/metadata/", d) + if d.HasChange("spec") { + spec, err := expandLimitRangeSpec(d.Get("spec").([]interface{}), d.IsNewResource()) + if err != nil { + return err + } + ops = append(ops, &ReplaceOperation{ + Path: "/spec", + Value: spec, + }) + } + data, err := ops.MarshalJSON() + if err != nil { + return fmt.Errorf("Failed to marshal update operations: %s", err) + } + log.Printf("[INFO] Updating limit range %q: %v", name, string(data)) + out, err := conn.CoreV1().LimitRanges(namespace).Patch(name, pkgApi.JSONPatchType, data) + if err != nil { + return fmt.Errorf("Failed to update limit range: %s", err) + } + log.Printf("[INFO] Submitted updated limit range: %#v", out) + d.SetId(buildId(out.ObjectMeta)) + + return resourceKubernetesLimitRangeRead(d, meta) +} + +func resourceKubernetesLimitRangeDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*kubernetes.Clientset) + + namespace, name := idParts(d.Id()) + log.Printf("[INFO] Deleting limit range: %#v", name) + err := conn.CoreV1().LimitRanges(namespace).Delete(name, &api.DeleteOptions{}) + if err != nil { + return err + } + + log.Printf("[INFO] Limit range %s deleted", name) + + d.SetId("") + return nil +} + +func resourceKubernetesLimitRangeExists(d *schema.ResourceData, meta interface{}) (bool, error) { + conn := meta.(*kubernetes.Clientset) + + namespace, name := idParts(d.Id()) + log.Printf("[INFO] Checking limit range %s", name) + _, err := conn.CoreV1().LimitRanges(namespace).Get(name) + if err != nil { + if statusErr, ok := err.(*errors.StatusError); ok && statusErr.ErrStatus.Code == 404 { + return false, nil + } + log.Printf("[DEBUG] Received error: %#v", err) + } + return true, err +} diff --git a/builtin/providers/kubernetes/resource_kubernetes_limit_range_test.go b/builtin/providers/kubernetes/resource_kubernetes_limit_range_test.go new file mode 100644 index 000000000000..c8eaa705dac0 --- /dev/null +++ b/builtin/providers/kubernetes/resource_kubernetes_limit_range_test.go @@ -0,0 +1,475 @@ +package kubernetes + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + api "k8s.io/kubernetes/pkg/api/v1" + kubernetes "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5" +) + +func TestAccKubernetesLimitRange_basic(t *testing.T) { + var conf api.LimitRange + name := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_limit_range.test", + Providers: testAccProviders, + CheckDestroy: testAccCheckKubernetesLimitRangeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesLimitRangeConfig_basic(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesLimitRangeExists("kubernetes_limit_range.test", &conf), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.%", "1"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.TestAnnotationOne", "one"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one"}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.%", "3"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.TestLabelOne", "one"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.TestLabelThree", "three"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.TestLabelFour", "four"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelThree": "three", "TestLabelFour": "four"}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.#", "1"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.cpu", "200m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.memory", "512M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default_request.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default_request.cpu", "100m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default_request.memory", "256M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.type", "Container"), + ), + }, + { + Config: testAccKubernetesLimitRangeConfig_metaModified(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesLimitRangeExists("kubernetes_limit_range.test", &conf), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.TestAnnotationOne", "one"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.TestAnnotationTwo", "two"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one", "TestAnnotationTwo": "two"}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.%", "3"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.TestLabelOne", "one"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.TestLabelTwo", "two"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.TestLabelThree", "three"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelTwo": "two", "TestLabelThree": "three"}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.#", "1"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.cpu", "200m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.memory", "512M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default_request.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default_request.cpu", "100m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default_request.memory", "256M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.type", "Container"), + ), + }, + { + Config: testAccKubernetesLimitRangeConfig_specModified(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesLimitRangeExists("kubernetes_limit_range.test", &conf), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.%", "0"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.%", "0"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.#", "1"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.cpu", "200m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.memory", "1024M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default_request.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default_request.cpu", "100m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default_request.memory", "256M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.max.%", "1"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.max.cpu", "500m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.min.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.min.cpu", "10m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.min.memory", "10M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.type", "Container"), + ), + }, + }, + }) +} + +func TestAccKubernetesLimitRange_generatedName(t *testing.T) { + var conf api.LimitRange + prefix := "tf-acc-test-" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_limit_range.test", + Providers: testAccProviders, + CheckDestroy: testAccCheckKubernetesLimitRangeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesLimitRangeConfig_generatedName(prefix), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesLimitRangeExists("kubernetes_limit_range.test", &conf), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.%", "0"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.%", "0"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.generate_name", prefix), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.#", "1"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.type", "Pod"), + ), + }, + }, + }) +} + +func TestAccKubernetesLimitRange_typeChange(t *testing.T) { + var conf api.LimitRange + name := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_limit_range.test", + Providers: testAccProviders, + CheckDestroy: testAccCheckKubernetesLimitRangeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesLimitRangeConfig_typeChange(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesLimitRangeExists("kubernetes_limit_range.test", &conf), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.%", "0"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.%", "0"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.#", "1"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.cpu", "200m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.default.memory", "1024M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.type", "Container"), + ), + }, + { + Config: testAccKubernetesLimitRangeConfig_typeChangeModified(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesLimitRangeExists("kubernetes_limit_range.test", &conf), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.%", "0"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.%", "0"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.#", "1"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.min.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.min.cpu", "200m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.min.memory", "1024M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.type", "Pod"), + ), + }, + }, + }) +} + +func TestAccKubernetesLimitRange_multipleLimits(t *testing.T) { + var conf api.LimitRange + name := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_limit_range.test", + Providers: testAccProviders, + CheckDestroy: testAccCheckKubernetesLimitRangeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesLimitRangeConfig_multipleLimits(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesLimitRangeExists("kubernetes_limit_range.test", &conf), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.annotations.%", "0"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.labels.%", "0"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_limit_range.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.#", "3"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.max.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.max.cpu", "200m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.max.memory", "1024M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.0.type", "Pod"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.1.min.%", "1"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.1.min.storage", "24M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.1.type", "PersistentVolumeClaim"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.2.default.%", "2"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.2.default.cpu", "50m"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.2.default.memory", "24M"), + resource.TestCheckResourceAttr("kubernetes_limit_range.test", "spec.0.limit.2.type", "Container"), + ), + }, + }, + }) +} + +func TestAccKubernetesLimitRange_importBasic(t *testing.T) { + resourceName := "kubernetes_limit_range.test" + name := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckKubernetesLimitRangeDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesLimitRangeConfig_basic(name), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckKubernetesLimitRangeDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*kubernetes.Clientset) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "kubernetes_limit_range" { + continue + } + namespace, name := idParts(rs.Primary.ID) + resp, err := conn.CoreV1().LimitRanges(namespace).Get(name) + if err == nil { + if resp.Namespace == namespace && resp.Name == name { + return fmt.Errorf("Limit Range still exists: %s", rs.Primary.ID) + } + } + } + + return nil +} + +func testAccCheckKubernetesLimitRangeExists(n string, obj *api.LimitRange) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := testAccProvider.Meta().(*kubernetes.Clientset) + namespace, name := idParts(rs.Primary.ID) + out, err := conn.CoreV1().LimitRanges(namespace).Get(name) + if err != nil { + return err + } + + *obj = *out + return nil + } +} + +func testAccKubernetesLimitRangeConfig_basic(name string) string { + return fmt.Sprintf(` +resource "kubernetes_limit_range" "test" { + metadata { + annotations { + TestAnnotationOne = "one" + } + labels { + TestLabelOne = "one" + TestLabelThree = "three" + TestLabelFour = "four" + } + name = "%s" + } + spec { + limit { + type = "Container" + + default { + cpu = "200m" + memory = "512M" + } + + default_request { + cpu = "100m" + memory = "256M" + } + } + } +} +`, name) +} + +func testAccKubernetesLimitRangeConfig_metaModified(name string) string { + return fmt.Sprintf(` +resource "kubernetes_limit_range" "test" { + metadata { + annotations { + TestAnnotationOne = "one" + TestAnnotationTwo = "two" + } + labels { + TestLabelOne = "one" + TestLabelTwo = "two" + TestLabelThree = "three" + } + name = "%s" + } + spec { + limit { + type = "Container" + + default { + cpu = "200m" + memory = "512M" + } + + default_request { + cpu = "100m" + memory = "256M" + } + } + } +} +`, name) +} + +func testAccKubernetesLimitRangeConfig_specModified(name string) string { + return fmt.Sprintf(` +resource "kubernetes_limit_range" "test" { + metadata { + name = "%s" + } + spec { + limit { + type = "Container" + + default { + cpu = "200m" + memory = "1024M" + } + + max { + cpu = "500m" + } + + min { + cpu = "10m" + memory = "10M" + } + } + } +} +`, name) +} + +func testAccKubernetesLimitRangeConfig_generatedName(prefix string) string { + return fmt.Sprintf(` +resource "kubernetes_limit_range" "test" { + metadata { + generate_name = "%s" + } + spec { + limit { + type = "Pod" + } + } +} +`, prefix) +} + +func testAccKubernetesLimitRangeConfig_typeChange(name string) string { + return fmt.Sprintf(` +resource "kubernetes_limit_range" "test" { + metadata { + name = "%s" + } + spec { + limit { + type = "Container" + default { + cpu = "200m" + memory = "1024M" + } + } + } +} +`, name) +} + +func testAccKubernetesLimitRangeConfig_typeChangeModified(name string) string { + return fmt.Sprintf(` +resource "kubernetes_limit_range" "test" { + metadata { + name = "%s" + } + spec { + limit { + type = "Pod" + min { + cpu = "200m" + memory = "1024M" + } + } + } +} +`, name) +} + +func testAccKubernetesLimitRangeConfig_multipleLimits(name string) string { + return fmt.Sprintf(` +resource "kubernetes_limit_range" "test" { + metadata { + name = "%s" + } + spec { + limit { + type = "Pod" + max { + cpu = "200m" + memory = "1024M" + } + } + limit { + type = "PersistentVolumeClaim" + min { + storage = "24M" + } + } + limit { + type = "Container" + default { + cpu = "50m" + memory = "24M" + } + } + } +} +`, name) +} diff --git a/builtin/providers/kubernetes/resource_kubernetes_resource_quota.go b/builtin/providers/kubernetes/resource_kubernetes_resource_quota.go new file mode 100644 index 000000000000..758cf8f4fc52 --- /dev/null +++ b/builtin/providers/kubernetes/resource_kubernetes_resource_quota.go @@ -0,0 +1,211 @@ +package kubernetes + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + pkgApi "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" + api "k8s.io/kubernetes/pkg/api/v1" + kubernetes "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5" +) + +func resourceKubernetesResourceQuota() *schema.Resource { + return &schema.Resource{ + Create: resourceKubernetesResourceQuotaCreate, + Read: resourceKubernetesResourceQuotaRead, + Exists: resourceKubernetesResourceQuotaExists, + Update: resourceKubernetesResourceQuotaUpdate, + Delete: resourceKubernetesResourceQuotaDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "metadata": namespacedMetadataSchema("resource quota", true), + "spec": { + Type: schema.TypeList, + Description: "Spec defines the desired quota. http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status", + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "hard": { + Type: schema.TypeMap, + Description: "The set of desired hard limits for each named resource. More info: http://releases.k8s.io/HEAD/docs/design/admission_control_resource_quota.md#admissioncontrol-plugin-resourcequota", + Optional: true, + Elem: schema.TypeString, + ValidateFunc: validateResourceList, + }, + "scopes": { + Type: schema.TypeSet, + Description: "A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.", + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + }, + }, + }, + }, + } +} + +func resourceKubernetesResourceQuotaCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*kubernetes.Clientset) + + metadata := expandMetadata(d.Get("metadata").([]interface{})) + spec, err := expandResourceQuotaSpec(d.Get("spec").([]interface{})) + if err != nil { + return err + } + resQuota := api.ResourceQuota{ + ObjectMeta: metadata, + Spec: spec, + } + log.Printf("[INFO] Creating new resource quota: %#v", resQuota) + out, err := conn.CoreV1().ResourceQuotas(metadata.Namespace).Create(&resQuota) + if err != nil { + return fmt.Errorf("Failed to create resource quota: %s", err) + } + log.Printf("[INFO] Submitted new resource quota: %#v", out) + d.SetId(buildId(out.ObjectMeta)) + + err = resource.Retry(1*time.Minute, func() *resource.RetryError { + quota, err := conn.CoreV1().ResourceQuotas(out.Namespace).Get(out.Name) + if err != nil { + return resource.NonRetryableError(err) + } + if resourceListEquals(spec.Hard, quota.Status.Hard) { + return nil + } + err = fmt.Errorf("Quotas don't match after creation.\nExpected: %#v\nGiven: %#v", + spec.Hard, quota.Status.Hard) + return resource.RetryableError(err) + }) + if err != nil { + return err + } + + return resourceKubernetesResourceQuotaRead(d, meta) +} + +func resourceKubernetesResourceQuotaRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*kubernetes.Clientset) + + namespace, name := idParts(d.Id()) + log.Printf("[INFO] Reading resource quota %s", name) + resQuota, err := conn.CoreV1().ResourceQuotas(namespace).Get(name) + if err != nil { + log.Printf("[DEBUG] Received error: %#v", err) + return err + } + log.Printf("[INFO] Received resource quota: %#v", resQuota) + + // This is to work around K8S bug + // See https://github.com/kubernetes/kubernetes/issues/44539 + if resQuota.ObjectMeta.GenerateName == "" { + if v, ok := d.GetOk("metadata.0.generate_name"); ok { + resQuota.ObjectMeta.GenerateName = v.(string) + } + } + + err = d.Set("metadata", flattenMetadata(resQuota.ObjectMeta)) + if err != nil { + return err + } + err = d.Set("spec", flattenResourceQuotaSpec(resQuota.Spec)) + if err != nil { + return err + } + + return nil +} + +func resourceKubernetesResourceQuotaUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*kubernetes.Clientset) + + namespace, name := idParts(d.Id()) + + ops := patchMetadata("metadata.0.", "/metadata/", d) + var spec api.ResourceQuotaSpec + waitForChangedSpec := false + if d.HasChange("spec") { + var err error + spec, err = expandResourceQuotaSpec(d.Get("spec").([]interface{})) + if err != nil { + return err + } + ops = append(ops, &ReplaceOperation{ + Path: "/spec", + Value: spec, + }) + waitForChangedSpec = true + } + data, err := ops.MarshalJSON() + if err != nil { + return fmt.Errorf("Failed to marshal update operations: %s", err) + } + log.Printf("[INFO] Updating resource quota %q: %v", name, string(data)) + out, err := conn.CoreV1().ResourceQuotas(namespace).Patch(name, pkgApi.JSONPatchType, data) + if err != nil { + return fmt.Errorf("Failed to update resource quota: %s", err) + } + log.Printf("[INFO] Submitted updated resource quota: %#v", out) + d.SetId(buildId(out.ObjectMeta)) + + if waitForChangedSpec { + err = resource.Retry(1*time.Minute, func() *resource.RetryError { + quota, err := conn.CoreV1().ResourceQuotas(namespace).Get(name) + if err != nil { + return resource.NonRetryableError(err) + } + if resourceListEquals(spec.Hard, quota.Status.Hard) { + return nil + } + err = fmt.Errorf("Quotas don't match after update.\nExpected: %#v\nGiven: %#v", + spec.Hard, quota.Status.Hard) + return resource.RetryableError(err) + }) + if err != nil { + return err + } + } + + return resourceKubernetesResourceQuotaRead(d, meta) +} + +func resourceKubernetesResourceQuotaDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*kubernetes.Clientset) + + namespace, name := idParts(d.Id()) + log.Printf("[INFO] Deleting resource quota: %#v", name) + err := conn.CoreV1().ResourceQuotas(namespace).Delete(name, &api.DeleteOptions{}) + if err != nil { + return err + } + + log.Printf("[INFO] Resource quota %s deleted", name) + + d.SetId("") + return nil +} + +func resourceKubernetesResourceQuotaExists(d *schema.ResourceData, meta interface{}) (bool, error) { + conn := meta.(*kubernetes.Clientset) + + namespace, name := idParts(d.Id()) + log.Printf("[INFO] Checking resource quota %s", name) + _, err := conn.CoreV1().ResourceQuotas(namespace).Get(name) + if err != nil { + if statusErr, ok := err.(*errors.StatusError); ok && statusErr.ErrStatus.Code == 404 { + return false, nil + } + log.Printf("[DEBUG] Received error: %#v", err) + } + return true, err +} diff --git a/builtin/providers/kubernetes/resource_kubernetes_resource_quota_test.go b/builtin/providers/kubernetes/resource_kubernetes_resource_quota_test.go new file mode 100644 index 000000000000..5935f7ea562d --- /dev/null +++ b/builtin/providers/kubernetes/resource_kubernetes_resource_quota_test.go @@ -0,0 +1,352 @@ +package kubernetes + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/acctest" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + api "k8s.io/kubernetes/pkg/api/v1" + kubernetes "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5" +) + +func TestAccKubernetesResourceQuota_basic(t *testing.T) { + var conf api.ResourceQuota + name := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_resource_quota.test", + Providers: testAccProviders, + CheckDestroy: testAccCheckKubernetesResourceQuotaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesResourceQuotaConfig_basic(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesResourceQuotaExists("kubernetes_resource_quota.test", &conf), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.annotations.%", "1"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.annotations.TestAnnotationOne", "one"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one"}), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.%", "3"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.TestLabelOne", "one"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.TestLabelThree", "three"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.TestLabelFour", "four"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelThree": "three", "TestLabelFour": "four"}), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.%", "3"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.limits.cpu", "2"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.limits.memory", "2Gi"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.pods", "4"), + ), + }, + { + Config: testAccKubernetesResourceQuotaConfig_metaModified(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesResourceQuotaExists("kubernetes_resource_quota.test", &conf), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.annotations.%", "2"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.annotations.TestAnnotationOne", "one"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.annotations.TestAnnotationTwo", "two"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{"TestAnnotationOne": "one", "TestAnnotationTwo": "two"}), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.%", "3"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.TestLabelOne", "one"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.TestLabelTwo", "two"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.TestLabelThree", "three"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{"TestLabelOne": "one", "TestLabelTwo": "two", "TestLabelThree": "three"}), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.%", "3"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.limits.cpu", "2"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.limits.memory", "2Gi"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.pods", "4"), + ), + }, + { + Config: testAccKubernetesResourceQuotaConfig_specModified(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesResourceQuotaExists("kubernetes_resource_quota.test", &conf), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.annotations.%", "0"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.%", "0"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.%", "4"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.limits.cpu", "4"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.requests.cpu", "1"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.limits.memory", "4Gi"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.pods", "10"), + ), + }, + }, + }) +} + +func TestAccKubernetesResourceQuota_generatedName(t *testing.T) { + var conf api.ResourceQuota + prefix := "tf-acc-test-" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_resource_quota.test", + Providers: testAccProviders, + CheckDestroy: testAccCheckKubernetesResourceQuotaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesResourceQuotaConfig_generatedName(prefix), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesResourceQuotaExists("kubernetes_resource_quota.test", &conf), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.annotations.%", "0"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.%", "0"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.generate_name", prefix), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.%", "1"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.pods", "10"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.scopes.#", "0"), + ), + }, + }, + }) +} + +func TestAccKubernetesResourceQuota_withScopes(t *testing.T) { + var conf api.ResourceQuota + name := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: "kubernetes_resource_quota.test", + Providers: testAccProviders, + CheckDestroy: testAccCheckKubernetesResourceQuotaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesResourceQuotaConfig_withScopes(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesResourceQuotaExists("kubernetes_resource_quota.test", &conf), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.annotations.%", "0"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.%", "0"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.%", "1"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.pods", "10"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.scopes.#", "1"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.scopes.193563370", "BestEffort"), + ), + }, + { + Config: testAccKubernetesResourceQuotaConfig_withScopesModified(name), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckKubernetesResourceQuotaExists("kubernetes_resource_quota.test", &conf), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.annotations.%", "0"), + testAccCheckMetaAnnotations(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.labels.%", "0"), + testAccCheckMetaLabels(&conf.ObjectMeta, map[string]string{}), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "metadata.0.name", name), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.generation"), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.resource_version"), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.self_link"), + resource.TestCheckResourceAttrSet("kubernetes_resource_quota.test", "metadata.0.uid"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.%", "1"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.hard.pods", "10"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.scopes.#", "1"), + resource.TestCheckResourceAttr("kubernetes_resource_quota.test", "spec.0.scopes.3022121741", "NotBestEffort"), + ), + }, + }, + }) +} + +func TestAccKubernetesResourceQuota_importBasic(t *testing.T) { + resourceName := "kubernetes_resource_quota.test" + name := fmt.Sprintf("tf-acc-test-%s", acctest.RandString(10)) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckKubernetesResourceQuotaDestroy, + Steps: []resource.TestStep{ + { + Config: testAccKubernetesResourceQuotaConfig_basic(name), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckKubernetesResourceQuotaDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*kubernetes.Clientset) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "kubernetes_resource_quota" { + continue + } + namespace, name := idParts(rs.Primary.ID) + resp, err := conn.CoreV1().ResourceQuotas(namespace).Get(name) + if err == nil { + if resp.Namespace == namespace && resp.Name == name { + return fmt.Errorf("Resource Quota still exists: %s", rs.Primary.ID) + } + } + } + + return nil +} + +func testAccCheckKubernetesResourceQuotaExists(n string, obj *api.ResourceQuota) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := testAccProvider.Meta().(*kubernetes.Clientset) + namespace, name := idParts(rs.Primary.ID) + out, err := conn.CoreV1().ResourceQuotas(namespace).Get(name) + if err != nil { + return err + } + + *obj = *out + return nil + } +} + +func testAccKubernetesResourceQuotaConfig_basic(name string) string { + return fmt.Sprintf(` +resource "kubernetes_resource_quota" "test" { + metadata { + annotations { + TestAnnotationOne = "one" + } + labels { + TestLabelOne = "one" + TestLabelThree = "three" + TestLabelFour = "four" + } + name = "%s" + } + spec { + hard { + "limits.cpu" = 2 + "limits.memory" = "2Gi" + pods = 4 + } + } +} +`, name) +} + +func testAccKubernetesResourceQuotaConfig_metaModified(name string) string { + return fmt.Sprintf(` +resource "kubernetes_resource_quota" "test" { + metadata { + annotations { + TestAnnotationOne = "one" + TestAnnotationTwo = "two" + } + labels { + TestLabelOne = "one" + TestLabelTwo = "two" + TestLabelThree = "three" + } + name = "%s" + } + spec { + hard { + "limits.cpu" = 2 + "limits.memory" = "2Gi" + pods = 4 + } + } +} +`, name) +} + +func testAccKubernetesResourceQuotaConfig_specModified(name string) string { + return fmt.Sprintf(` +resource "kubernetes_resource_quota" "test" { + metadata { + name = "%s" + } + spec { + hard { + "limits.cpu" = 4 + "requests.cpu" = 1 + "limits.memory" = "4Gi" + pods = 10 + } + } +} +`, name) +} + +func testAccKubernetesResourceQuotaConfig_generatedName(prefix string) string { + return fmt.Sprintf(` +resource "kubernetes_resource_quota" "test" { + metadata { + generate_name = "%s" + } + spec { + hard { + pods = 10 + } + } +} +`, prefix) +} + +func testAccKubernetesResourceQuotaConfig_withScopes(name string) string { + return fmt.Sprintf(` +resource "kubernetes_resource_quota" "test" { + metadata { + name = "%s" + } + spec { + hard { + pods = 10 + } + scopes = ["BestEffort"] + } +} +`, name) +} + +func testAccKubernetesResourceQuotaConfig_withScopesModified(name string) string { + return fmt.Sprintf(` +resource "kubernetes_resource_quota" "test" { + metadata { + name = "%s" + } + spec { + hard { + pods = 10 + } + scopes = ["NotBestEffort"] + } +} +`, name) +} diff --git a/builtin/providers/kubernetes/structures.go b/builtin/providers/kubernetes/structures.go index 19b73aabd3b2..d02a9ea5d3c1 100644 --- a/builtin/providers/kubernetes/structures.go +++ b/builtin/providers/kubernetes/structures.go @@ -163,11 +163,21 @@ func flattenResourceList(l api.ResourceList) map[string]string { func expandMapToResourceList(m map[string]interface{}) (api.ResourceList, error) { out := make(map[api.ResourceName]resource.Quantity) - for stringKey, v := range m { + for stringKey, origValue := range m { key := api.ResourceName(stringKey) - value, err := resource.ParseQuantity(v.(string)) - if err != nil { - return out, err + var value resource.Quantity + + if v, ok := origValue.(int); ok { + q := resource.NewQuantity(int64(v), resource.DecimalExponent) + value = *q + } else if v, ok := origValue.(string); ok { + var err error + value, err = resource.ParseQuantity(v) + if err != nil { + return out, err + } + } else { + return out, fmt.Errorf("Unexpected value type: %#v", origValue) } out[key] = value @@ -191,6 +201,55 @@ func expandPersistentVolumeAccessModes(s []interface{}) []api.PersistentVolumeAc return out } +func flattenResourceQuotaSpec(in api.ResourceQuotaSpec) []interface{} { + out := make([]interface{}, 1) + + m := make(map[string]interface{}, 0) + m["hard"] = flattenResourceList(in.Hard) + m["scopes"] = flattenResourceQuotaScopes(in.Scopes) + + out[0] = m + return out +} + +func expandResourceQuotaSpec(s []interface{}) (api.ResourceQuotaSpec, error) { + out := api.ResourceQuotaSpec{} + if len(s) < 1 { + return out, nil + } + m := s[0].(map[string]interface{}) + + if v, ok := m["hard"]; ok { + list, err := expandMapToResourceList(v.(map[string]interface{})) + if err != nil { + return out, err + } + out.Hard = list + } + + if v, ok := m["scopes"]; ok { + out.Scopes = expandResourceQuotaScopes(v.(*schema.Set).List()) + } + + return out, nil +} + +func flattenResourceQuotaScopes(in []api.ResourceQuotaScope) *schema.Set { + out := make([]string, len(in), len(in)) + for i, scope := range in { + out[i] = string(scope) + } + return newStringSet(schema.HashString, out) +} + +func expandResourceQuotaScopes(s []interface{}) []api.ResourceQuotaScope { + out := make([]api.ResourceQuotaScope, len(s), len(s)) + for i, scope := range s { + out[i] = api.ResourceQuotaScope(scope.(string)) + } + return out +} + func newStringSet(f schema.SchemaSetFunc, in []string) *schema.Set { var out = make([]interface{}, len(in), len(in)) for i, v := range in { @@ -198,3 +257,119 @@ func newStringSet(f schema.SchemaSetFunc, in []string) *schema.Set { } return schema.NewSet(f, out) } + +func resourceListEquals(x, y api.ResourceList) bool { + for k, v := range x { + yValue, ok := y[k] + if !ok { + return false + } + if v.Cmp(yValue) != 0 { + return false + } + } + for k, v := range y { + xValue, ok := x[k] + if !ok { + return false + } + if v.Cmp(xValue) != 0 { + return false + } + } + return true +} + +func expandLimitRangeSpec(s []interface{}, isNew bool) (api.LimitRangeSpec, error) { + out := api.LimitRangeSpec{} + if len(s) < 1 || s[0] == nil { + return out, nil + } + m := s[0].(map[string]interface{}) + + if limits, ok := m["limit"].([]interface{}); ok { + newLimits := make([]api.LimitRangeItem, len(limits), len(limits)) + + for i, l := range limits { + lrItem := api.LimitRangeItem{} + limit := l.(map[string]interface{}) + + if v, ok := limit["type"]; ok { + lrItem.Type = api.LimitType(v.(string)) + } + + // defaultRequest is forbidden for Pod limits, even though it's set & returned by API + // this is how we avoid sending it back + if v, ok := limit["default_request"]; ok { + drm := v.(map[string]interface{}) + if lrItem.Type == api.LimitTypePod && len(drm) > 0 { + if isNew { + return out, fmt.Errorf("limit.%d.default_request cannot be set for Pod limit", i) + } + } else { + el, err := expandMapToResourceList(drm) + if err != nil { + return out, err + } + lrItem.DefaultRequest = el + } + } + + if v, ok := limit["default"]; ok { + el, err := expandMapToResourceList(v.(map[string]interface{})) + if err != nil { + return out, err + } + lrItem.Default = el + } + if v, ok := limit["max"]; ok { + el, err := expandMapToResourceList(v.(map[string]interface{})) + if err != nil { + return out, err + } + lrItem.Max = el + } + if v, ok := limit["max_limit_request_ratio"]; ok { + el, err := expandMapToResourceList(v.(map[string]interface{})) + if err != nil { + return out, err + } + lrItem.MaxLimitRequestRatio = el + } + if v, ok := limit["min"]; ok { + el, err := expandMapToResourceList(v.(map[string]interface{})) + if err != nil { + return out, err + } + lrItem.Min = el + } + + newLimits[i] = lrItem + } + + out.Limits = newLimits + } + + return out, nil +} + +func flattenLimitRangeSpec(in api.LimitRangeSpec) []interface{} { + out := make([]interface{}, 1) + limits := make([]interface{}, len(in.Limits), len(in.Limits)) + + for i, l := range in.Limits { + m := make(map[string]interface{}, 0) + m["default"] = flattenResourceList(l.Default) + m["default_request"] = flattenResourceList(l.DefaultRequest) + m["max"] = flattenResourceList(l.Max) + m["max_limit_request_ratio"] = flattenResourceList(l.MaxLimitRequestRatio) + m["min"] = flattenResourceList(l.Min) + m["type"] = string(l.Type) + + limits[i] = m + } + out[0] = map[string]interface{}{ + "limit": limits, + } + return out +} diff --git a/builtin/providers/kubernetes/validators.go b/builtin/providers/kubernetes/validators.go index f1dde20293dc..4fe12328ba6d 100644 --- a/builtin/providers/kubernetes/validators.go +++ b/builtin/providers/kubernetes/validators.go @@ -62,12 +62,21 @@ func validateLabels(value interface{}, key string) (ws []string, es []error) { func validateResourceList(value interface{}, key string) (ws []string, es []error) { m := value.(map[string]interface{}) - for k, v := range m { - val := v.(string) - _, err := resource.ParseQuantity(val) - if err != nil { - es = append(es, fmt.Errorf("%s.%s (%q): %s", key, k, val, err)) + for k, value := range m { + if _, ok := value.(int); ok { + continue + } + + if v, ok := value.(string); ok { + _, err := resource.ParseQuantity(v) + if err != nil { + es = append(es, fmt.Errorf("%s.%s (%q): %s", key, k, v, err)) + } + continue } + + err := "Value can be either string or int" + es = append(es, fmt.Errorf("%s.%s (%#v): %s", key, k, value, err)) } return } diff --git a/builtin/providers/nomad/resource_job_test.go b/builtin/providers/nomad/resource_job_test.go index c43f5aa1f64e..7e71ce6b9708 100644 --- a/builtin/providers/nomad/resource_job_test.go +++ b/builtin/providers/nomad/resource_job_test.go @@ -207,15 +207,17 @@ func testResourceJob_checkExists(s *terraform.State) error { func testResourceJob_checkDestroy(jobID string) r.TestCheckFunc { return func(*terraform.State) error { client := testProvider.Meta().(*api.Client) - _, _, err := client.Jobs().Info(jobID, nil) - if err != nil && strings.Contains(err.Error(), "404") { + job, _, err := client.Jobs().Info(jobID, nil) + // This should likely never happen, due to how nomad caches jobs + if err != nil && strings.Contains(err.Error(), "404") || job == nil { return nil } - if err == nil { - err = errors.New("not destroyed") + + if job.Status != "dead" { + return fmt.Errorf("Job %q has not been stopped. Status: %s", jobID, job.Status) } - return err + return nil } } @@ -284,9 +286,16 @@ func testResourceJob_updateCheck(s *terraform.State) error { { // Verify foo doesn't exist - _, _, err := client.Jobs().Info("foo", nil) - if err == nil { - return errors.New("reading foo success") + job, _, err := client.Jobs().Info("foo", nil) + if err != nil { + // Job could have already been purged from nomad server + if !strings.Contains(err.Error(), "(job not found)") { + return fmt.Errorf("error reading %q job: %s", "foo", err) + } + return nil + } + if job.Status != "dead" { + return fmt.Errorf("%q job is not dead. Status: %q", "foo", job.Status) } } diff --git a/builtin/providers/openstack/resource_openstack_compute_floatingip_associate_v2.go b/builtin/providers/openstack/resource_openstack_compute_floatingip_associate_v2.go index f3e0f56316ae..c2e1d6888fc5 100644 --- a/builtin/providers/openstack/resource_openstack_compute_floatingip_associate_v2.go +++ b/builtin/providers/openstack/resource_openstack_compute_floatingip_associate_v2.go @@ -110,7 +110,7 @@ func resourceComputeFloatingIPAssociateV2Delete(d *schema.ResourceData, meta int err = floatingips.DisassociateInstance(computeClient, instanceId, disassociateOpts).ExtractErr() if err != nil { - return fmt.Errorf("Error disassociating floating IP: %s", err) + return CheckDeleted(d, err, "floating ip association") } return nil diff --git a/builtin/providers/profitbricks/data_source_image_test.go b/builtin/providers/profitbricks/data_source_image_test.go index 3efe6d32594e..3f8f151a4703 100644 --- a/builtin/providers/profitbricks/data_source_image_test.go +++ b/builtin/providers/profitbricks/data_source_image_test.go @@ -17,7 +17,7 @@ func TestAccDataSourceImage_basic(t *testing.T) { Config: testAccDataSourceProfitBricksImage_basic, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.profitbricks_image.img", "location", "us/las"), - resource.TestCheckResourceAttr("data.profitbricks_image.img", "name", "Ubuntu-16.04-LTS-server-2017-02-01"), + resource.TestCheckResourceAttr("data.profitbricks_image.img", "name", "Ubuntu-16.04-LTS-server-2017-05-01"), resource.TestCheckResourceAttr("data.profitbricks_image.img", "type", "HDD"), ), }, diff --git a/builtin/providers/profitbricks/resource_profitbricks_server.go b/builtin/providers/profitbricks/resource_profitbricks_server.go index bfcd1678a001..c617f691d480 100644 --- a/builtin/providers/profitbricks/resource_profitbricks_server.go +++ b/builtin/providers/profitbricks/resource_profitbricks_server.go @@ -254,34 +254,38 @@ func resourceProfitBricksServerCreate(d *schema.ResourceData, meta interface{}) var sshkey_path []interface{} var image, licenceType, availabilityZone string - if !IsValidUUID(rawMap["image_name"].(string)) { - if rawMap["image_name"] != nil { - image = getImageId(d.Get("datacenter_id").(string), rawMap["image_name"].(string), rawMap["disk_type"].(string)) - if image == "" { - dc := profitbricks.GetDatacenter(d.Get("datacenter_id").(string)) - return fmt.Errorf("Image '%s' doesn't exist. in location %s", rawMap["image_name"], dc.Properties.Location) - - } - } - } else { - image = rawMap["image_name"].(string) - } - - if rawMap["licence_type"] != nil { - licenceType = rawMap["licence_type"].(string) - } - if rawMap["image_password"] != nil { imagePassword = rawMap["image_password"].(string) } if rawMap["ssh_key_path"] != nil { sshkey_path = rawMap["ssh_key_path"].([]interface{}) } - if rawMap["image_name"] != nil { + + image_name := rawMap["image_name"].(string) + if !IsValidUUID(image_name) { if imagePassword == "" && len(sshkey_path) == 0 { - return fmt.Errorf("Either 'image_password' or 'ssh_key_path' must be provided.") + return fmt.Errorf("Either 'image_password' or 'sshkey' must be provided.") + } + image = getImageId(d.Get("datacenter_id").(string), image_name, rawMap["disk_type"].(string)) + } else { + img := profitbricks.GetImage(image_name) + if img.StatusCode > 299 { + return fmt.Errorf("Error fetching image: %s", img.Response) + } + if img.Properties.Public == true { + if imagePassword == "" && len(sshkey_path) == 0 { + return fmt.Errorf("Either 'image_password' or 'sshkey' must be provided.") + } + image = image_name + } else { + image = image_name } } + + if rawMap["licence_type"] != nil { + licenceType = rawMap["licence_type"].(string) + } + var publicKeys []string if len(sshkey_path) != 0 { for _, path := range sshkey_path { diff --git a/builtin/providers/profitbricks/resource_profitbricks_volume.go b/builtin/providers/profitbricks/resource_profitbricks_volume.go index 8fca17854a00..46d8ff47df82 100644 --- a/builtin/providers/profitbricks/resource_profitbricks_volume.go +++ b/builtin/providers/profitbricks/resource_profitbricks_volume.go @@ -77,12 +77,6 @@ func resourceProfitBricksVolumeCreate(d *schema.ResourceData, meta interface{}) ssh_keypath = d.Get("ssh_key_path").([]interface{}) image_name := d.Get("image_name").(string) - if image_name != "" { - if imagePassword == "" && len(ssh_keypath) == 0 { - return fmt.Errorf("Either 'image_password' or 'sshkey' must be provided.") - } - } - licenceType := d.Get("licence_type").(string) if image_name == "" && licenceType == "" { @@ -102,10 +96,26 @@ func resourceProfitBricksVolumeCreate(d *schema.ResourceData, meta interface{}) } var image string - if !IsValidUUID(image_name) { - image = getImageId(d.Get("datacenter_id").(string), image_name, d.Get("disk_type").(string)) - } else { - image = image_name + if image_name != "" { + if !IsValidUUID(image_name) { + if imagePassword == "" && len(ssh_keypath) == 0 { + return fmt.Errorf("Either 'image_password' or 'sshkey' must be provided.") + } + image = getImageId(d.Get("datacenter_id").(string), image_name, d.Get("disk_type").(string)) + } else { + img := profitbricks.GetImage(image_name) + if img.StatusCode > 299 { + return fmt.Errorf("Error fetching image: %s", img.Response) + } + if img.Properties.Public == true { + if imagePassword == "" && len(ssh_keypath) == 0 { + return fmt.Errorf("Either 'image_password' or 'sshkey' must be provided.") + } + image = image_name + } else { + image = image_name + } + } } volume := profitbricks.Volume{ diff --git a/builtin/providers/vault/provider.go b/builtin/providers/vault/provider.go index ceebd4acfa8e..d9c7719e9a9b 100644 --- a/builtin/providers/vault/provider.go +++ b/builtin/providers/vault/provider.go @@ -87,6 +87,7 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ + "vault_auth_backend": authBackendResource(), "vault_generic_secret": genericSecretResource(), "vault_policy": policyResource(), }, diff --git a/builtin/providers/vault/resource_auth_backend.go b/builtin/providers/vault/resource_auth_backend.go new file mode 100644 index 000000000000..800155040502 --- /dev/null +++ b/builtin/providers/vault/resource_auth_backend.go @@ -0,0 +1,121 @@ +package vault + +import ( + "errors" + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/vault/api" +) + +func authBackendResource() *schema.Resource { + return &schema.Resource{ + Create: authBackendWrite, + Delete: authBackendDelete, + Read: authBackendRead, + + Schema: map[string]*schema.Schema{ + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Name of the auth backend", + }, + + "path": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + Description: "path to mount the backend. This defaults to the type.", + ValidateFunc: func(v interface{}, k string) (ws []string, errs []error) { + value := v.(string) + if strings.HasSuffix(value, "/") { + errs = append(errs, errors.New("cannot write to a path ending in '/'")) + } + return + }, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + ForceNew: true, + Optional: true, + Description: "The description of the auth backend", + }, + }, + } +} + +func authBackendWrite(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + + name := d.Get("type").(string) + desc := d.Get("description").(string) + path := d.Get("path").(string) + + log.Printf("[DEBUG] Writing auth %s to Vault", name) + + var err error + + if path == "" { + path = name + err = d.Set("path", name) + if err != nil { + return fmt.Errorf("unable to set state: %s", err) + } + } + + err = client.Sys().EnableAuth(path, name, desc) + + if err != nil { + return fmt.Errorf("error writing to Vault: %s", err) + } + + d.SetId(name) + + return nil +} + +func authBackendDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + + name := d.Id() + + log.Printf("[DEBUG] Deleting auth %s from Vault", name) + + err := client.Sys().DisableAuth(name) + + if err != nil { + return fmt.Errorf("error disabling auth from Vault: %s", err) + } + + return nil +} + +func authBackendRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*api.Client) + + name := d.Id() + + auths, err := client.Sys().ListAuth() + + if err != nil { + return fmt.Errorf("error reading from Vault: %s", err) + } + + for path, auth := range auths { + configuredPath := d.Get("path").(string) + + vaultPath := configuredPath + "/" + if auth.Type == name && path == vaultPath { + return nil + } + } + + // If we fell out here then we didn't find our Auth in the list. + d.SetId("") + return nil +} diff --git a/builtin/providers/vault/resource_auth_backend_test.go b/builtin/providers/vault/resource_auth_backend_test.go new file mode 100644 index 000000000000..344eafbd53e8 --- /dev/null +++ b/builtin/providers/vault/resource_auth_backend_test.go @@ -0,0 +1,129 @@ +package vault + +import ( + "fmt" + "testing" + + r "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/hashicorp/vault/api" +) + +func TestResourceAuth(t *testing.T) { + r.Test(t, r.TestCase{ + Providers: testProviders, + PreCheck: func() { testAccPreCheck(t) }, + Steps: []r.TestStep{ + r.TestStep{ + Config: testResourceAuth_initialConfig, + Check: testResourceAuth_initialCheck, + }, + r.TestStep{ + Config: testResourceAuth_updateConfig, + Check: testResourceAuth_updateCheck, + }, + }, + }) +} + +var testResourceAuth_initialConfig = ` + +resource "vault_auth_backend" "test" { + type = "github" +} + +` + +func testResourceAuth_initialCheck(s *terraform.State) error { + resourceState := s.Modules[0].Resources["vault_auth_backend.test"] + if resourceState == nil { + return fmt.Errorf("resource not found in state") + } + + instanceState := resourceState.Primary + if instanceState == nil { + return fmt.Errorf("resource has no primary instance") + } + + name := instanceState.ID + + if name != instanceState.Attributes["type"] { + return fmt.Errorf("id doesn't match name") + } + + if name != "github" { + return fmt.Errorf("unexpected auth name %s", name) + } + + client := testProvider.Meta().(*api.Client) + auths, err := client.Sys().ListAuth() + + if err != nil { + return fmt.Errorf("error reading back auth: %s", err) + } + + found := false + for _, auth := range auths { + if auth.Type == name { + found = true + break + } + } + + if !found { + return fmt.Errorf("could not find auth backend %s in %+v", name, auths) + } + + return nil +} + +var testResourceAuth_updateConfig = ` + +resource "vault_auth_backend" "test" { + type = "ldap" +} + +` + +func testResourceAuth_updateCheck(s *terraform.State) error { + resourceState := s.Modules[0].Resources["vault_auth_backend.test"] + if resourceState == nil { + return fmt.Errorf("resource not found in state") + } + + instanceState := resourceState.Primary + if instanceState == nil { + return fmt.Errorf("resource has no primary instance") + } + + name := instanceState.ID + + if name != instanceState.Attributes["type"] { + return fmt.Errorf("id doesn't match name") + } + + if name != "ldap" { + return fmt.Errorf("unexpected auth name") + } + + client := testProvider.Meta().(*api.Client) + auths, err := client.Sys().ListAuth() + + if err != nil { + return fmt.Errorf("error reading back auth: %s", err) + } + + found := false + for _, auth := range auths { + if auth.Type == name { + found = true + break + } + } + + if !found { + return fmt.Errorf("could not find auth backend %s in %+v", name, auths) + } + + return nil +} diff --git a/command/internal_plugin_list.go b/command/internal_plugin_list.go index 1b3302f0a8cf..4f6ca949061a 100644 --- a/command/internal_plugin_list.go +++ b/command/internal_plugin_list.go @@ -35,6 +35,7 @@ import ( googleprovider "github.com/hashicorp/terraform/builtin/providers/google" grafanaprovider "github.com/hashicorp/terraform/builtin/providers/grafana" herokuprovider "github.com/hashicorp/terraform/builtin/providers/heroku" + httpprovider "github.com/hashicorp/terraform/builtin/providers/http" icinga2provider "github.com/hashicorp/terraform/builtin/providers/icinga2" ignitionprovider "github.com/hashicorp/terraform/builtin/providers/ignition" influxdbprovider "github.com/hashicorp/terraform/builtin/providers/influxdb" @@ -115,6 +116,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{ "google": googleprovider.Provider, "grafana": grafanaprovider.Provider, "heroku": herokuprovider.Provider, + "http": httpprovider.Provider, "icinga2": icinga2provider.Provider, "ignition": ignitionprovider.Provider, "influxdb": influxdbprovider.Provider, diff --git a/config/interpolate_funcs.go b/config/interpolate_funcs.go index b79334718b60..f1f97b04e4e5 100644 --- a/config/interpolate_funcs.go +++ b/config/interpolate_funcs.go @@ -4,6 +4,7 @@ import ( "crypto/md5" "crypto/sha1" "crypto/sha256" + "crypto/sha512" "encoding/base64" "encoding/hex" "encoding/json" @@ -57,6 +58,7 @@ func Funcs() map[string]ast.Function { "base64decode": interpolationFuncBase64Decode(), "base64encode": interpolationFuncBase64Encode(), "base64sha256": interpolationFuncBase64Sha256(), + "base64sha512": interpolationFuncBase64Sha512(), "ceil": interpolationFuncCeil(), "chomp": interpolationFuncChomp(), "cidrhost": interpolationFuncCidrHost(), @@ -79,6 +81,7 @@ func Funcs() map[string]ast.Function { "jsonencode": interpolationFuncJSONEncode(), "length": interpolationFuncLength(), "list": interpolationFuncList(), + "log": interpolationFuncLog(), "lower": interpolationFuncLower(), "map": interpolationFuncMap(), "max": interpolationFuncMax(), @@ -90,6 +93,7 @@ func Funcs() map[string]ast.Function { "replace": interpolationFuncReplace(), "sha1": interpolationFuncSha1(), "sha256": interpolationFuncSha256(), + "sha512": interpolationFuncSha512(), "signum": interpolationFuncSignum(), "slice": interpolationFuncSlice(), "sort": interpolationFuncSort(), @@ -486,6 +490,17 @@ func interpolationFuncCeil() ast.Function { } } +// interpolationFuncLog returns the logarithnm. +func interpolationFuncLog() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeFloat, ast.TypeFloat}, + ReturnType: ast.TypeFloat, + Callback: func(args []interface{}) (interface{}, error) { + return math.Log(args[0].(float64)) / math.Log(args[1].(float64)), nil + }, + } +} + // interpolationFuncChomp removes trailing newlines from the given string func interpolationFuncChomp() ast.Function { newlines := regexp.MustCompile(`(?:\r\n?|\n)*\z`) @@ -1240,6 +1255,20 @@ func interpolationFuncSha256() ast.Function { } } +func interpolationFuncSha512() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeString}, + ReturnType: ast.TypeString, + Callback: func(args []interface{}) (interface{}, error) { + s := args[0].(string) + h := sha512.New() + h.Write([]byte(s)) + hash := hex.EncodeToString(h.Sum(nil)) + return hash, nil + }, + } +} + func interpolationFuncTrimSpace() ast.Function { return ast.Function{ ArgTypes: []ast.Type{ast.TypeString}, @@ -1266,6 +1295,21 @@ func interpolationFuncBase64Sha256() ast.Function { } } +func interpolationFuncBase64Sha512() ast.Function { + return ast.Function{ + ArgTypes: []ast.Type{ast.TypeString}, + ReturnType: ast.TypeString, + Callback: func(args []interface{}) (interface{}, error) { + s := args[0].(string) + h := sha512.New() + h.Write([]byte(s)) + shaSum := h.Sum(nil) + encoded := base64.StdEncoding.EncodeToString(shaSum[:]) + return encoded, nil + }, + } +} + func interpolationFuncUUID() ast.Function { return ast.Function{ ArgTypes: []ast.Type{}, diff --git a/config/interpolate_funcs_test.go b/config/interpolate_funcs_test.go index 57e59afa67ad..6ed65a6f4c45 100644 --- a/config/interpolate_funcs_test.go +++ b/config/interpolate_funcs_test.go @@ -370,6 +370,34 @@ func TestInterpolateFuncCeil(t *testing.T) { }) } +func TestInterpolateFuncLog(t *testing.T) { + testFunction(t, testFunctionConfig{ + Cases: []testFunctionCase{ + { + `${log(1, 10)}`, + "0", + false, + }, + { + `${log(10, 10)}`, + "1", + false, + }, + + { + `${log(0, 10)}`, + "-Inf", + false, + }, + { + `${log(10, 0)}`, + "-0", + false, + }, + }, + }) +} + func TestInterpolateFuncChomp(t *testing.T) { testFunction(t, testFunctionConfig{ Cases: []testFunctionCase{ @@ -2070,6 +2098,18 @@ func TestInterpolateFuncSha256(t *testing.T) { }) } +func TestInterpolateFuncSha512(t *testing.T) { + testFunction(t, testFunctionConfig{ + Cases: []testFunctionCase{ + { + `${sha512("test")}`, + "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", + false, + }, + }, + }) +} + func TestInterpolateFuncTitle(t *testing.T) { testFunction(t, testFunctionConfig{ Cases: []testFunctionCase{ @@ -2129,6 +2169,23 @@ func TestInterpolateFuncBase64Sha256(t *testing.T) { }) } +func TestInterpolateFuncBase64Sha512(t *testing.T) { + testFunction(t, testFunctionConfig{ + Cases: []testFunctionCase{ + { + `${base64sha512("test")}`, + "7iaw3Ur350mqGo7jwQrpkj9hiYB3Lkc/iBml1JQODbJ6wYX4oOHV+E+IvIh/1nsUNzLDBMxfqa2Ob1f1ACio/w==", + false, + }, + { // This will differ because we're base64-encoding hex represantiation, not raw bytes + `${base64encode(sha512("test"))}`, + "ZWUyNmIwZGQ0YWY3ZTc0OWFhMWE4ZWUzYzEwYWU5OTIzZjYxODk4MDc3MmU0NzNmODgxOWE1ZDQ5NDBlMGRiMjdhYzE4NWY4YTBlMWQ1Zjg0Zjg4YmM4ODdmZDY3YjE0MzczMmMzMDRjYzVmYTlhZDhlNmY1N2Y1MDAyOGE4ZmY=", + false, + }, + }, + }) +} + func TestInterpolateFuncMd5(t *testing.T) { testFunction(t, testFunctionConfig{ Cases: []testFunctionCase{ diff --git a/config/loader_hcl.go b/config/loader_hcl.go index a40ad5ba776b..9abb1960f30e 100644 --- a/config/loader_hcl.go +++ b/config/loader_hcl.go @@ -327,6 +327,10 @@ func loadAtlasHcl(list *ast.ObjectList) (*AtlasConfig, error) { // represents exactly one module definition in the HCL configuration. // We leave it up to another pass to merge them together. func loadModulesHcl(list *ast.ObjectList) ([]*Module, error) { + if err := assertAllBlocksHaveNames("module", list); err != nil { + return nil, err + } + list = list.Children() if len(list.Items) == 0 { return nil, nil @@ -391,12 +395,12 @@ func loadModulesHcl(list *ast.ObjectList) ([]*Module, error) { // LoadOutputsHcl recurses into the given HCL object and turns // it into a mapping of outputs. func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) { - list = list.Children() - if len(list.Items) == 0 { - return nil, fmt.Errorf( - "'output' must be followed by exactly one string: a name") + if err := assertAllBlocksHaveNames("output", list); err != nil { + return nil, err } + list = list.Children() + // Go through each object and turn it into an actual result. result := make([]*Output, 0, len(list.Items)) for _, item := range list.Items { @@ -450,12 +454,12 @@ func loadOutputsHcl(list *ast.ObjectList) ([]*Output, error) { // LoadVariablesHcl recurses into the given HCL object and turns // it into a list of variables. func loadVariablesHcl(list *ast.ObjectList) ([]*Variable, error) { - list = list.Children() - if len(list.Items) == 0 { - return nil, fmt.Errorf( - "'variable' must be followed by exactly one strings: a name") + if err := assertAllBlocksHaveNames("variable", list); err != nil { + return nil, err } + list = list.Children() + // hclVariable is the structure each variable is decoded into type hclVariable struct { DeclaredType string `hcl:"type"` @@ -531,6 +535,10 @@ func loadVariablesHcl(list *ast.ObjectList) ([]*Variable, error) { // LoadProvidersHcl recurses into the given HCL object and turns // it into a mapping of provider configs. func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) { + if err := assertAllBlocksHaveNames("provider", list); err != nil { + return nil, err + } + list = list.Children() if len(list.Items) == 0 { return nil, nil @@ -592,6 +600,10 @@ func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) { // represents exactly one data definition in the HCL configuration. // We leave it up to another pass to merge them together. func loadDataResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { + if err := assertAllBlocksHaveNames("data", list); err != nil { + return nil, err + } + list = list.Children() if len(list.Items) == 0 { return nil, nil @@ -901,6 +913,10 @@ func loadManagedResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { } func loadProvisionersHcl(list *ast.ObjectList, connInfo map[string]interface{}) ([]*Provisioner, error) { + if err := assertAllBlocksHaveNames("provisioner", list); err != nil { + return nil, err + } + list = list.Children() if len(list.Items) == 0 { return nil, nil @@ -1023,6 +1039,29 @@ func hclObjectMap(os *hclobj.Object) map[string]ast.ListNode { } */ +// assertAllBlocksHaveNames returns an error if any of the items in +// the given object list are blocks without keys (like "module {}") +// or simple assignments (like "module = 1"). It returns nil if +// neither of these things are true. +// +// The given name is used in any generated error messages, and should +// be the name of the block we're dealing with. The given list should +// be the result of calling .Filter on an object list with that same +// name. +func assertAllBlocksHaveNames(name string, list *ast.ObjectList) error { + if elem := list.Elem(); len(elem.Items) != 0 { + switch et := elem.Items[0].Val.(type) { + case *ast.ObjectType: + pos := et.Lbrace + return fmt.Errorf("%s: %q must be followed by a name", pos, name) + default: + pos := elem.Items[0].Val.Pos() + return fmt.Errorf("%s: %q must be a configuration block", pos, name) + } + } + return nil +} + func checkHCLKeys(node ast.Node, valid []string) error { var list *ast.ObjectList switch n := node.(type) { diff --git a/config/loader_test.go b/config/loader_test.go index a2a2929f2638..a3aeb7321ebd 100644 --- a/config/loader_test.go +++ b/config/loader_test.go @@ -314,6 +314,18 @@ func TestLoadFileBasic_modules(t *testing.T) { } } +func TestLoadFile_unnamedModule(t *testing.T) { + _, err := LoadFile(filepath.Join(fixtureDir, "module-unnamed.tf")) + if err == nil { + t.Fatalf("bad: expected error") + } + + errorStr := err.Error() + if !strings.Contains(errorStr, `"module" must be followed`) { + t.Fatalf("bad: expected error has wrong text: %s", errorStr) + } +} + func TestLoadFile_outputDependsOn(t *testing.T) { c, err := LoadFile(filepath.Join(fixtureDir, "output-depends-on.tf")) if err != nil { @@ -696,7 +708,7 @@ func TestLoadFile_variableNoName(t *testing.T) { } errorStr := err.Error() - if !strings.Contains(errorStr, "'variable' must be followed") { + if !strings.Contains(errorStr, `"variable" must be followed`) { t.Fatalf("bad: expected error has wrong text: %s", errorStr) } } @@ -740,7 +752,7 @@ func TestLoadFile_unnamedOutput(t *testing.T) { } errorStr := err.Error() - if !strings.Contains(errorStr, "'output' must be followed") { + if !strings.Contains(errorStr, `"output" must be followed`) { t.Fatalf("bad: expected error has wrong text: %s", errorStr) } } diff --git a/config/test-fixtures/module-unnamed.tf b/config/test-fixtures/module-unnamed.tf new file mode 100644 index 000000000000..e285519bf674 --- /dev/null +++ b/config/test-fixtures/module-unnamed.tf @@ -0,0 +1,7 @@ +module "okay" { + source = "./okay" +} + +module { + source = "./not-okay" +} diff --git a/config/test-fixtures/output-unnamed.tf b/config/test-fixtures/output-unnamed.tf index 7e7529153b03..7ef8ebe1ba7e 100644 --- a/config/test-fixtures/output-unnamed.tf +++ b/config/test-fixtures/output-unnamed.tf @@ -1,3 +1,7 @@ +output "okay" { + value = "bar" +} + output { value = "foo" } diff --git a/config/test-fixtures/variable-no-name.tf b/config/test-fixtures/variable-no-name.tf index f3856886f782..7f09d1e64331 100644 --- a/config/test-fixtures/variable-no-name.tf +++ b/config/test-fixtures/variable-no-name.tf @@ -1,3 +1,6 @@ +variable "okay" { +} + variable { name = "test" default = "test_value" diff --git a/docs/maintainer-etiquette.md b/docs/maintainer-etiquette.md new file mode 100644 index 000000000000..eb3f90064e09 --- /dev/null +++ b/docs/maintainer-etiquette.md @@ -0,0 +1,91 @@ +# Maintainer's Etiquette + +Are you a core maintainer of Terraform? Great! Here's a few notes +to help you get comfortable when working on the project. + +## Expectations + +We value the time you spend on the project and as such your maintainer status +doesn't imply any obligations to do any specific work. + +### Your PRs + +These apply to all contributors, but maintainers should lead by examples! :wink: + + - for `provider/*` PRs it's useful to attach test results & advise on how to run the relevant tests + - for `bug`fixes it's useful to attach repro case, ideally in a form of a test + +### PRs/issues from others + + - you're welcomed to triage (attach labels to) other PRs and issues + - we generally use 2-label system (= at least 2 labels per issue/PR) where one label is generic and other one API-specific, e.g. `enhancement` & `provider/aws` + +## Merging + + - you're free to review PRs from the community or other HC employees and give :+1: / :-1: + - if the PR submitter has push privileges (recognizable via `Collaborator`, `Member` or `Owner` badge) - we expect **the submitter** to merge their own PR after receiving a positive review from either HC employee or another maintainer. _Exceptions apply - see below._ + - we prefer to use the Github's interface or API to do this, just click the green button + - squash? + - squash when you think the commit history is irrelevant (will not be helpful for any readers in T+6mons) + - Add the new PR to the **Changelog** if it may affect the user (almost any PR except test changes and docs updates) + - we prefer to use the Github's web interface to modify the Changelog and use `[GH-12345]` to format the PR number. These will be turned into links as part of the release process. Breaking changes should be always documented separately. + +## Release process + + - HC employees are responsible for cutting new releases + - The employee cutting the release will always notify all maintainers via Slack channel before & after each release + so you can avoid merging PRs during the release process. + +## Exceptions + +Any PR that is significantly changing or even breaking user experience cross-providers should always get at least one :+1: from a HC employee prior to merge. + +It is generally advisable to leave PRs labelled as `core` for HC employees to review and merge. + +Examples include: + - adding/changing/removing a CLI (sub)command or a [flag](https://github.com/hashicorp/terraform/pull/12939) + - introduce a new feature like [Environments](https://github.com/hashicorp/terraform/pull/12182) or [Shadow Graph](https://github.com/hashicorp/terraform/pull/9334) + - changing config (HCL) like [adding support for lists](https://github.com/hashicorp/terraform/pull/6322) + - change of the [build process or test environment](https://github.com/hashicorp/terraform/pull/9355) + +## Breaking Changes + + - we always try to avoid breaking changes where possible and/or defer them to the nearest major release + - [state migration](https://github.com/hashicorp/terraform/blob/2fe5976aec290f4b53f07534f4cde13f6d877a3f/helper/schema/resource.go#L33-L56) may help you avoid breaking changes, see [example](https://github.com/hashicorp/terraform/blob/351c6bed79abbb40e461d3f7d49fe4cf20bced41/builtin/providers/aws/resource_aws_route53_record_migrate.go) + - either way BCs should be clearly documented in special section of the Changelog + - Any BC must always receive at least one :+1: from HC employee prior to merge, two :+1:s are advisable + + ### Examples of Breaking Changes + + - https://github.com/hashicorp/terraform/pull/12396 + - https://github.com/hashicorp/terraform/pull/13872 + - https://github.com/hashicorp/terraform/pull/13752 + +## Unsure? + +If you're unsure about anything, ask in the committer's Slack channel. + +## New Providers + +These will require :+1: and some extra effort from HC employee. + +We expect all acceptance tests to be as self-sustainable as possible +to keep the bar for running any acceptance test low for anyone +outside of HashiCorp or core maintainers team. + +We expect any test to run **in parallel** alongside any other test (even the same test). +To ensure this is possible, we need all tests to avoid sharing namespaces or using static unique names. +In rare occasions this may require the use of mutexes in the resource code. + +### New Remote-API-based provider (e.g. AWS, Google Cloud, PagerDuty, Atlas) + +We will need some details about who to contact or where to register for a new account +and generally we can't merge providers before ensuring we have a way to test them nightly, +which usually involves setting up a new account and obtaining API credentials. + +### Local provider (e.g. MySQL, PostgreSQL, Kubernetes, Vault) + +We will need either Terraform configs that will set up the underlying test infrastructure +(e.g. GKE cluster for Kubernetes) or Dockerfile(s) that will prepare test environment (e.g. MySQL) +and expose the endpoint for testing. + diff --git a/examples/azure-2-vms-loadbalancer-lbrules/README.md b/examples/azure-2-vms-loadbalancer-lbrules/README.md deleted file mode 100644 index d7ca3d4d3cb6..000000000000 --- a/examples/azure-2-vms-loadbalancer-lbrules/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Create 2 Virtual Machines under a Load balancer and configures Load Balancing rules for the VMs - -This Terraform template was based on [this](https://github.com/Azure/azure-quickstart-templates/tree/master/201-2-vms-loadbalancer-lbrules) Azure Quickstart Template. Changes to the ARM template may have occured since the creation of this example may not be reflected here. - - - - - -This template allows you to create 2 Virtual Machines under a Load balancer and configure a load balancing rule on Port 80. This template also deploys a Storage Account, Virtual Network, Public IP address, Availability Set, and Network Interfaces. - -## main.tf -The `main.tf` file contains the actual resources that will be deployed. It also contains the Azure Resource Group definition and any defined variables. - -## outputs.tf -This data is outputted when `terraform apply` is called, and can be queried using the `terraform output` command. - -## provider.tf -Azure requires that an application is added to Azure Active Directory to generate the `client_id`, `client_secret`, and `tenant_id` needed by Terraform (`subscription_id` can be recovered from your Azure account details). Please go [here](https://www.terraform.io/docs/providers/azurerm/) for full instructions on how to create this to populate your `provider.tf` file. - -## terraform.tfvars -If a `terraform.tfvars` file is present in the current directory, Terraform automatically loads it to populate variables. We don't recommend saving usernames and password to version control, but you can create a local secret variables file and use `-var-file` to load it. - -If you are committing this template to source control, please insure that you add this file to your .gitignore file. - -## variables.tf -The `variables.tf` file contains all of the input parameters that the user can specify when deploying this Terraform template. diff --git a/examples/azure-2-vms-loadbalancer-lbrules/deploy.ci.sh b/examples/azure-2-vms-loadbalancer-lbrules/deploy.ci.sh deleted file mode 100755 index 57e0dc9b1a42..000000000000 --- a/examples/azure-2-vms-loadbalancer-lbrules/deploy.ci.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/bash - -set -o errexit -o nounset - -docker run --rm -it \ - -e ARM_CLIENT_ID \ - -e ARM_CLIENT_SECRET \ - -e ARM_SUBSCRIPTION_ID \ - -e ARM_TENANT_ID \ - -v $(pwd):/data \ - --entrypoint "/bin/sh" \ - hashicorp/terraform:light \ - -c "cd /data; \ - /bin/terraform get; \ - /bin/terraform validate; \ - /bin/terraform plan -out=out.tfplan -var dns_name=$KEY -var hostname=$KEY -var lb_ip_dns_name=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD; \ - /bin/terraform apply out.tfplan" - -# cleanup deployed azure resources via azure-cli -docker run --rm -it \ - azuresdk/azure-cli-python \ - sh -c "az login --service-principal -u $ARM_CLIENT_ID -p $ARM_CLIENT_SECRET --tenant $ARM_TENANT_ID > /dev/null; \ - az network lb show -g $KEY -n rglb; \ - az network lb rule list -g $KEY --lb-name rglb;" - -# cleanup deployed azure resources via terraform -docker run --rm -it \ - -e ARM_CLIENT_ID \ - -e ARM_CLIENT_SECRET \ - -e ARM_SUBSCRIPTION_ID \ - -e ARM_TENANT_ID \ - -v $(pwd):/data \ - --workdir=/data \ - --entrypoint "/bin/sh" \ - hashicorp/terraform:light \ - -c "/bin/terraform destroy -force -var dns_name=$KEY -var hostname=$KEY -var lb_ip_dns_name=$KEY -var resource_group=$KEY -var admin_password=$PASSWORD;" diff --git a/examples/azure-2-vms-loadbalancer-lbrules/deploy.mac.sh b/examples/azure-2-vms-loadbalancer-lbrules/deploy.mac.sh deleted file mode 100755 index cf5cdc32279f..000000000000 --- a/examples/azure-2-vms-loadbalancer-lbrules/deploy.mac.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -set -o errexit -o nounset - -if docker -v; then - - # generate a unique string for CI deployment - export KEY=$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'a-z' | head -c 12) - export PASSWORD=$KEY$(cat /dev/urandom | env LC_CTYPE=C tr -cd 'A-Z' | head -c 2)$(cat /dev/urandom | env LC_CTYPE=C tr -cd '0-9' | head -c 2) - -/bin/sh ./deploy.ci.sh - -else - echo "Docker is used to run terraform commands, please install before run: https://docs.docker.com/docker-for-mac/install/" -fi \ No newline at end of file diff --git a/examples/azure-2-vms-loadbalancer-lbrules/main.tf b/examples/azure-2-vms-loadbalancer-lbrules/main.tf deleted file mode 100644 index 24981118d3e2..000000000000 --- a/examples/azure-2-vms-loadbalancer-lbrules/main.tf +++ /dev/null @@ -1,138 +0,0 @@ -resource "azurerm_resource_group" "rg" { - name = "${var.resource_group}" - location = "${var.location}" -} - -resource "azurerm_storage_account" "stor" { - name = "${var.dns_name}stor" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - account_type = "${var.storage_account_type}" -} - -resource "azurerm_availability_set" "avset" { - name = "${var.dns_name}avset" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - platform_fault_domain_count = 2 - platform_update_domain_count = 2 - managed = true -} - -resource "azurerm_public_ip" "lbpip" { - name = "${var.rg_prefix}-ip" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - public_ip_address_allocation = "dynamic" - domain_name_label = "${var.lb_ip_dns_name}" -} - -resource "azurerm_virtual_network" "vnet" { - name = "${var.virtual_network_name}" - location = "${var.location}" - address_space = ["${var.address_space}"] - resource_group_name = "${azurerm_resource_group.rg.name}" -} - -resource "azurerm_subnet" "subnet" { - name = "${var.rg_prefix}subnet" - virtual_network_name = "${azurerm_virtual_network.vnet.name}" - resource_group_name = "${azurerm_resource_group.rg.name}" - address_prefix = "${var.subnet_prefix}" -} - -resource "azurerm_lb" "lb" { - resource_group_name = "${azurerm_resource_group.rg.name}" - name = "${var.rg_prefix}lb" - location = "${var.location}" - - frontend_ip_configuration { - name = "LoadBalancerFrontEnd" - public_ip_address_id = "${azurerm_public_ip.lbpip.id}" - } -} - -resource "azurerm_lb_backend_address_pool" "backend_pool" { - resource_group_name = "${azurerm_resource_group.rg.name}" - loadbalancer_id = "${azurerm_lb.lb.id}" - name = "BackendPool1" -} - -resource "azurerm_lb_nat_rule" "tcp" { - resource_group_name = "${azurerm_resource_group.rg.name}" - loadbalancer_id = "${azurerm_lb.lb.id}" - name = "RDP-VM-${count.index}" - protocol = "tcp" - frontend_port = "5000${count.index + 1}" - backend_port = 3389 - frontend_ip_configuration_name = "LoadBalancerFrontEnd" - count = 2 -} - -resource "azurerm_lb_rule" "lb_rule" { - resource_group_name = "${azurerm_resource_group.rg.name}" - loadbalancer_id = "${azurerm_lb.lb.id}" - name = "LBRule" - protocol = "tcp" - frontend_port = 80 - backend_port = 80 - frontend_ip_configuration_name = "LoadBalancerFrontEnd" - enable_floating_ip = false - backend_address_pool_id = "${azurerm_lb_backend_address_pool.backend_pool.id}" - idle_timeout_in_minutes = 5 - probe_id = "${azurerm_lb_probe.lb_probe.id}" - depends_on = ["azurerm_lb_probe.lb_probe"] -} - -resource "azurerm_lb_probe" "lb_probe" { - resource_group_name = "${azurerm_resource_group.rg.name}" - loadbalancer_id = "${azurerm_lb.lb.id}" - name = "tcpProbe" - protocol = "tcp" - port = 80 - interval_in_seconds = 5 - number_of_probes = 2 -} - -resource "azurerm_network_interface" "nic" { - name = "nic${count.index}" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - count = 2 - - ip_configuration { - name = "ipconfig${count.index}" - subnet_id = "${azurerm_subnet.subnet.id}" - private_ip_address_allocation = "Dynamic" - load_balancer_backend_address_pools_ids = ["${azurerm_lb_backend_address_pool.backend_pool.id}"] - load_balancer_inbound_nat_rules_ids = ["${element(azurerm_lb_nat_rule.tcp.*.id, count.index)}"] - } -} - -resource "azurerm_virtual_machine" "vm" { - name = "vm${count.index}" - location = "${var.location}" - resource_group_name = "${azurerm_resource_group.rg.name}" - availability_set_id = "${azurerm_availability_set.avset.id}" - vm_size = "${var.vm_size}" - network_interface_ids = ["${element(azurerm_network_interface.nic.*.id, count.index)}"] - count = 2 - - storage_image_reference { - publisher = "${var.image_publisher}" - offer = "${var.image_offer}" - sku = "${var.image_sku}" - version = "${var.image_version}" - } - - storage_os_disk { - name = "osdisk${count.index}" - create_option = "FromImage" - } - - os_profile { - computer_name = "${var.hostname}" - admin_username = "${var.admin_username}" - admin_password = "${var.admin_password}" - } -} diff --git a/examples/azure-2-vms-loadbalancer-lbrules/outputs.tf b/examples/azure-2-vms-loadbalancer-lbrules/outputs.tf deleted file mode 100644 index a21a9568b688..000000000000 --- a/examples/azure-2-vms-loadbalancer-lbrules/outputs.tf +++ /dev/null @@ -1,11 +0,0 @@ -output "hostname" { - value = "${var.hostname}" -} - -output "vm_fqdn" { - value = "${azurerm_public_ip.lbpip.fqdn}" -} - -output "sshCommand" { - value = "ssh ${var.admin_username}@${azurerm_public_ip.lbpip.fqdn}" -} diff --git a/examples/azure-2-vms-loadbalancer-lbrules/provider.tf b/examples/azure-2-vms-loadbalancer-lbrules/provider.tf deleted file mode 100644 index bdf0583f3259..000000000000 --- a/examples/azure-2-vms-loadbalancer-lbrules/provider.tf +++ /dev/null @@ -1,6 +0,0 @@ -# provider "azurerm" { -# subscription_id = "REPLACE-WITH-YOUR-SUBSCRIPTION-ID" -# client_id = "REPLACE-WITH-YOUR-CLIENT-ID" -# client_secret = "REPLACE-WITH-YOUR-CLIENT-SECRET" -# tenant_id = "REPLACE-WITH-YOUR-TENANT-ID" -# } diff --git a/examples/azure-2-vms-loadbalancer-lbrules/terraform.tfvars b/examples/azure-2-vms-loadbalancer-lbrules/terraform.tfvars deleted file mode 100644 index 824d4a691b61..000000000000 --- a/examples/azure-2-vms-loadbalancer-lbrules/terraform.tfvars +++ /dev/null @@ -1,6 +0,0 @@ -# resource_group = "myresourcegroup" -# rg_prefix = "rg" -# hostname = "myvm" -# dns_name = "mydnsname" -# location = "southcentralus" -# admin_password = "T3rr@f0rmP@ssword" diff --git a/examples/azure-2-vms-loadbalancer-lbrules/variables.tf b/examples/azure-2-vms-loadbalancer-lbrules/variables.tf deleted file mode 100644 index 55320c2653a4..000000000000 --- a/examples/azure-2-vms-loadbalancer-lbrules/variables.tf +++ /dev/null @@ -1,80 +0,0 @@ -variable "resource_group" { - description = "The name of the resource group in which to create the virtual network." -} - -variable "rg_prefix" { - description = "The shortened abbreviation to represent your resource group that will go on the front of some resources." - default = "rg" -} - -variable "hostname" { - description = "VM name referenced also in storage-related names." -} - -variable "dns_name" { - description = " Label for the Domain Name. Will be used to make up the FQDN. If a domain name label is specified, an A DNS record is created for the public IP in the Microsoft Azure DNS system." -} - -variable "lb_ip_dns_name" { - description = "DNS for Load Balancer IP" -} - -variable "location" { - description = "The location/region where the virtual network is created. Changing this forces a new resource to be created." - default = "southcentralus" -} - -variable "virtual_network_name" { - description = "The name for the virtual network." - default = "vnet" -} - -variable "address_space" { - description = "The address space that is used by the virtual network. You can supply more than one address space. Changing this forces a new resource to be created." - default = "10.0.0.0/16" -} - -variable "subnet_prefix" { - description = "The address prefix to use for the subnet." - default = "10.0.10.0/24" -} - -variable "storage_account_type" { - description = "Defines the type of storage account to be created. Valid options are Standard_LRS, Standard_ZRS, Standard_GRS, Standard_RAGRS, Premium_LRS. Changing this is sometimes valid - see the Azure documentation for more information on which types of accounts can be converted into other types." - default = "Standard_LRS" -} - -variable "vm_size" { - description = "Specifies the size of the virtual machine." - default = "Standard_D1" -} - -variable "image_publisher" { - description = "name of the publisher of the image (az vm image list)" - default = "MicrosoftWindowsServer" -} - -variable "image_offer" { - description = "the name of the offer (az vm image list)" - default = "WindowsServer" -} - -variable "image_sku" { - description = "image sku to apply (az vm image list)" - default = "2012-R2-Datacenter" -} - -variable "image_version" { - description = "version of the image to apply (az vm image list)" - default = "latest" -} - -variable "admin_username" { - description = "administrator user name" - default = "vmadmin" -} - -variable "admin_password" { - description = "administrator password (recommended to disable password auth)" - default = "T3rr@f0rmP@ssword" -} diff --git a/examples/azure-cdn-with-storage-account/deploy.ci.sh b/examples/azure-cdn-with-storage-account/deploy.ci.sh index 3fd0efac3f6d..406ac1d9e9f3 100755 --- a/examples/azure-cdn-with-storage-account/deploy.ci.sh +++ b/examples/azure-cdn-with-storage-account/deploy.ci.sh @@ -16,8 +16,6 @@ docker run --rm -it \ /bin/terraform plan -out=out.tfplan -var resource_group=$KEY; \ /bin/terraform apply out.tfplan" -#TODO: how do we validate? - # cleanup deployed azure resources via terraform docker run --rm -it \ -e ARM_CLIENT_ID \ @@ -28,4 +26,4 @@ docker run --rm -it \ --workdir=/data \ --entrypoint "/bin/sh" \ hashicorp/terraform:light \ - -c "/bin/terraform destroy -force -var resource_group=$KEY;" + -c "/bin/terraform destroy -force -var resource_group=$KEY;" \ No newline at end of file diff --git a/examples/azure-cdn-with-storage-account/deploy.mac.sh b/examples/azure-cdn-with-storage-account/deploy.mac.sh index 9c6563f07d71..dfc34c2be2fc 100755 --- a/examples/azure-cdn-with-storage-account/deploy.mac.sh +++ b/examples/azure-cdn-with-storage-account/deploy.mac.sh @@ -12,4 +12,4 @@ if docker -v; then else echo "Docker is used to run terraform commands, please install before run: https://docs.docker.com/docker-for-mac/install/" -fi +fi \ No newline at end of file diff --git a/examples/azure-cdn-with-storage-account/graph.png b/examples/azure-cdn-with-storage-account/graph.png new file mode 100644 index 0000000000000000000000000000000000000000..b68b0087f4f5b3201ccb10c9af21965d95c24e76 GIT binary patch literal 69826 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