Adding docs and tweaking the provider

This commit is contained in:
Sander van Harmelen 2015-05-18 15:40:45 +02:00
parent 123cd9239c
commit ca1eb1917b
10 changed files with 467 additions and 57 deletions

View File

@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"log" "log"
"strings"
"github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
@ -12,6 +11,7 @@ import (
"github.com/svanharmelen/azure-sdk-for-go/management/hostedservice" "github.com/svanharmelen/azure-sdk-for-go/management/hostedservice"
"github.com/svanharmelen/azure-sdk-for-go/management/osimage" "github.com/svanharmelen/azure-sdk-for-go/management/osimage"
"github.com/svanharmelen/azure-sdk-for-go/management/virtualmachine" "github.com/svanharmelen/azure-sdk-for-go/management/virtualmachine"
"github.com/svanharmelen/azure-sdk-for-go/management/virtualmachineimage"
"github.com/svanharmelen/azure-sdk-for-go/management/vmutils" "github.com/svanharmelen/azure-sdk-for-go/management/vmutils"
) )
@ -55,6 +55,7 @@ func resourceAzureInstance() *schema.Resource {
"subnet": &schema.Schema{ "subnet": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true,
ForceNew: true, ForceNew: true,
}, },
@ -142,14 +143,10 @@ func resourceAzureInstance() *schema.Resource {
Set: resourceAzureEndpointHash, Set: resourceAzureEndpointHash,
}, },
"security_groups": &schema.Schema{ "security_group": &schema.Schema{
Type: schema.TypeSet, Type: schema.TypeString,
Optional: true, Optional: true,
Computed: true, Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: func(v interface{}) int {
return hashcode.String(v.(string))
},
}, },
"ip_address": &schema.Schema{ "ip_address": &schema.Schema{
@ -177,19 +174,11 @@ func resourceAzureInstanceCreate(d *schema.ResourceData, meta interface{}) (err
} }
// Retrieve the needed details of the image // Retrieve the needed details of the image
imageName, imageURL, osType, err := retrieveImageDetails(mc, d.Get("image").(string)) configForImage, osType, err := retrieveImageDetails(mc, d.Get("image").(string))
if err != nil { if err != nil {
return err return err
} }
if imageURL == "" {
storage, ok := d.GetOk("storage")
if !ok {
return fmt.Errorf("When using a platform image, the 'storage' parameter is required")
}
imageURL = fmt.Sprintf("http://%s.blob.core.windows.net/vhds/%s.vhd", storage, name)
}
// Verify if we have all parameters required for the image OS type // Verify if we have all parameters required for the image OS type
if err := verifyParameters(d, osType); err != nil { if err := verifyParameters(d, osType); err != nil {
return err return err
@ -299,14 +288,23 @@ func resourceAzureInstanceCreate(d *schema.ResourceData, meta interface{}) (err
} }
} }
err = vmutils.ConfigureForSubnet(&role, d.Get("subnet").(string)) if subnet, ok := d.GetOk("subnet"); ok {
if err != nil { err = vmutils.ConfigureWithSubnet(&role, subnet.(string))
return fmt.Errorf( if err != nil {
"Error adding role to subnet %s for instance %s: %s", d.Get("subnet").(string), name, err) return fmt.Errorf(
"Error associating subnet %s with instance %s: %s", d.Get("subnet").(string), name, err)
}
}
if sg, ok := d.GetOk("security_group"); ok {
err = vmutils.ConfigureWithSecurityGroup(&role, sg.(string))
if err != nil {
return fmt.Errorf(
"Error associating security group %s with instance %s: %s", sg.(string), name, err)
}
} }
options := virtualmachine.CreateDeploymentOptions{ options := virtualmachine.CreateDeploymentOptions{
Subnet: d.Get("subnet").(string),
VirtualNetworkName: d.Get("virtual_network").(string), VirtualNetworkName: d.Get("virtual_network").(string),
} }
@ -349,11 +347,14 @@ func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error {
return fmt.Errorf( return fmt.Errorf(
"Instance %s has an unexpected number of roles: %d", d.Id(), len(dpmt.RoleList)) "Instance %s has an unexpected number of roles: %d", d.Id(), len(dpmt.RoleList))
} }
d.Set("image", dpmt.RoleList[0].VMImageName)
d.Set("size", dpmt.RoleList[0].RoleSize) d.Set("size", dpmt.RoleList[0].RoleSize)
if len(dpmt.RoleInstanceList) != 1 { if len(dpmt.RoleInstanceList) != 1 {
return fmt.Errorf( return fmt.Errorf(
"Instance %s has an unexpected number of role instances %d", d.Id(), len(dpmt.RoleInstanceList)) "Instance %s has an unexpected number of role instances: %d",
d.Id(), len(dpmt.RoleInstanceList))
} }
d.Set("ip_address", dpmt.RoleInstanceList[0].IpAddress) d.Set("ip_address", dpmt.RoleInstanceList[0].IpAddress)
@ -361,15 +362,16 @@ func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error {
d.Set("vip_address", dpmt.RoleInstanceList[0].InstanceEndpoints[0].Vip) d.Set("vip_address", dpmt.RoleInstanceList[0].InstanceEndpoints[0].Vip)
} }
// Create a new set to hold all configured endpoints // Find the network configuration set
endpoints := &schema.Set{ for _, c := range dpmt.RoleList[0].ConfigurationSets {
F: resourceAzureEndpointHash,
}
// Loop through all endpoints and add them to the set
for _, c := range *dpmt.RoleList[0].ConfigurationSets {
if c.ConfigurationSetType == virtualmachine.ConfigurationSetTypeNetwork { if c.ConfigurationSetType == virtualmachine.ConfigurationSetTypeNetwork {
for _, ep := range *c.InputEndpoints { // Create a new set to hold all configured endpoints
endpoints := &schema.Set{
F: resourceAzureEndpointHash,
}
// Loop through all endpoints
for _, ep := range c.InputEndpoints {
endpoint := map[string]interface{}{} endpoint := map[string]interface{}{}
// Update the values // Update the values
@ -379,8 +381,22 @@ func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error {
endpoint["private_port"] = ep.LocalPort endpoint["private_port"] = ep.LocalPort
endpoints.Add(endpoint) endpoints.Add(endpoint)
} }
d.Set("endpoint", endpoints) d.Set("endpoint", endpoints)
// Update the subnet
switch len(c.SubnetNames) {
case 1:
d.Set("subnet", c.SubnetNames[0])
case 0:
d.Set("subnet", "")
default:
return fmt.Errorf(
"Instance %s has an unexpected number of associated subnets %d",
d.Id(), len(dpmt.RoleInstanceList))
}
// Update the security group
d.Set("security_group", c.NetworkSecurityGroup)
} }
} }
@ -403,7 +419,7 @@ func resourceAzureInstanceUpdate(d *schema.ResourceData, meta interface{}) error
mc := meta.(*management.Client) mc := meta.(*management.Client)
// First check if anything we can update changed, and if not just return // First check if anything we can update changed, and if not just return
if !d.HasChange("size") && !d.HasChange("endpoint") { if !d.HasChange("size") && !d.HasChange("endpoint") && !d.HasChange("security_group") {
return nil return nil
} }
@ -426,10 +442,10 @@ func resourceAzureInstanceUpdate(d *schema.ResourceData, meta interface{}) error
_, n := d.GetChange("endpoint") _, n := d.GetChange("endpoint")
// Delete the existing endpoints // Delete the existing endpoints
for i, c := range *role.ConfigurationSets { for i, c := range role.ConfigurationSets {
if c.ConfigurationSetType == virtualmachine.ConfigurationSetTypeNetwork { if c.ConfigurationSetType == virtualmachine.ConfigurationSetTypeNetwork {
c.InputEndpoints = nil c.InputEndpoints = nil
(*role.ConfigurationSets)[i] = c role.ConfigurationSets[i] = c
} }
} }
@ -452,6 +468,15 @@ func resourceAzureInstanceUpdate(d *schema.ResourceData, meta interface{}) error
} }
} }
if d.HasChange("security_group") {
sg := d.Get("security_group").(string)
err := vmutils.ConfigureWithSecurityGroup(role, sg)
if err != nil {
return fmt.Errorf(
"Error associating security group %s with instance %s: %s", sg, d.Id(), err)
}
}
// Update the adjusted role // Update the adjusted role
req, err := virtualmachine.NewClient(*mc).UpdateRole(d.Id(), d.Id(), d.Id(), *role) req, err := virtualmachine.NewClient(*mc).UpdateRole(d.Id(), d.Id(), d.Id(), *role)
if err != nil { if err != nil {
@ -497,26 +522,65 @@ func resourceAzureEndpointHash(v interface{}) int {
return hashcode.String(buf.String()) return hashcode.String(buf.String())
} }
func retrieveImageDetails(mc *management.Client, label string) (string, string, string, error) { func retrieveImageDetails(mc *management.Client, id, storage string) (func() error, string, error) {
imageName, imageURL, osType, err := retrieveOSImageDetails(mc, id)
if err == nil {
return imageName, imageURL, osType, nil
}
imageName, imageURL, osType, err = retrieveVMImageDetails(mc, id)
if err == nil {
return imageName, imageURL, osType, nil
}
return "", "", "", fmt.Errorf("Could not find image with label or name '%s'", id)
}
func retrieveOSImageDetails(
mc *management.Client,
label,
storage string) (func() error, string, error) {
imgs, err := osimage.NewClient(*mc).GetImageList() imgs, err := osimage.NewClient(*mc).GetImageList()
if err != nil {
return nil, "", fmt.Errorf("Error retrieving image details: %s", err)
}
for _, img := range imgs {
if img.Label == label {
if img.OS != linux && img.OS != windows {
return nil, "", fmt.Errorf("Unsupported image OS: %s", img.OS)
}
if img.MediaLink == "" {
if storage == "" {
return nil, "",
fmt.Errorf("When using a platform image, the 'storage' parameter is required")
}
imageURL = fmt.Sprintf("http://%s.blob.core.windows.net/vhds/%s.vhd", storage, id)
}
return img.Name, img.MediaLink, img.OS, nil
}
}
return "", "", "", fmt.Errorf("Could not find image with label '%s'", label)
}
func retrieveVMImageDetails(mc *management.Client, name string) (string, string, string, error) {
imgs, err := virtualmachineimage.NewClient(*mc).GetImageList()
if err != nil { if err != nil {
return "", "", "", fmt.Errorf("Error retrieving image details: %s", err) return "", "", "", fmt.Errorf("Error retrieving image details: %s", err)
} }
var labels []string
for _, img := range imgs { for _, img := range imgs {
if img.Label == label { if img.Name == name {
if img.OS != linux && img.OS != windows { if img.OSDiskConfiguration.OS != linux && img.OSDiskConfiguration.OS != windows {
return "", "", "", fmt.Errorf("Unsupported image OS: %s", img.OS) return "", "", "", fmt.Errorf("Unsupported image OS: %s", img.OSDiskConfiguration.OS)
} }
return img.Name, img.MediaLink, img.OS, nil return img.Name, img.OSDiskConfiguration.MediaLink, img.OSDiskConfiguration.OS, nil
} }
labels = append(labels, img.Label)
} }
return "", "", "", return "", "", "", fmt.Errorf("Could not find image with name '%s'", name)
fmt.Errorf("Could not find image with label '%s', available labels are: %s",
label, strings.Join(labels, ","))
} }
func endpointProtocol(p string) virtualmachine.InputEndpointProtocol { func endpointProtocol(p string) virtualmachine.InputEndpointProtocol {

View File

@ -1,10 +1,12 @@
package azure package azure
import ( import (
"bytes"
"fmt" "fmt"
"log" "log"
"strconv" "strconv"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/svanharmelen/azure-sdk-for-go/management" "github.com/svanharmelen/azure-sdk-for-go/management"
"github.com/svanharmelen/azure-sdk-for-go/management/networksecuritygroup" "github.com/svanharmelen/azure-sdk-for-go/management/networksecuritygroup"
@ -107,9 +109,15 @@ func resourceAzureSecurityGroupCreate(d *schema.ResourceData, meta interface{})
name := d.Get("name").(string) name := d.Get("name").(string)
// Compute/set the label
label := d.Get("label").(string)
if label == "" {
label = name
}
req, err := networksecuritygroup.NewClient(*mc).CreateNetworkSecurityGroup( req, err := networksecuritygroup.NewClient(*mc).CreateNetworkSecurityGroup(
name, name,
d.Get("label").(string), label,
d.Get("location").(string), d.Get("location").(string),
) )
if err != nil { if err != nil {
@ -301,7 +309,20 @@ func resourceAzureSecurityGroupRuleDelete(
} }
func resourceAzureSecurityGroupRuleHash(v interface{}) int { func resourceAzureSecurityGroupRuleHash(v interface{}) int {
return 0 var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf(
"%s-%d-%s-%s-%s-%s-%s-%s",
m["type"].(string),
m["priority"].(int),
m["action"].(string),
m["source_cidr"].(string),
m["source_port"].(string),
m["destination_cidr"].(string),
m["destination_port"].(string),
m["protocol"].(string)))
return hashcode.String(buf.String())
} }
func verifySecurityGroupRuleParams(rule map[string]interface{}) error { func verifySecurityGroupRuleParams(rule map[string]interface{}) error {

View File

@ -7,22 +7,23 @@ body.page-sub{
} }
body.layout-atlas, body.layout-atlas,
body.layout-consul, body.layout-aws,
body.layout-dnsimple, body.layout-azure,
body.layout-dme,
body.layout-docker,
body.layout-cloudflare, body.layout-cloudflare,
body.layout-cloudstack, body.layout-cloudstack,
body.layout-consul,
body.layout-digitalocean,
body.layout-dme,
body.layout-dnsimple,
body.layout-docker,
body.layout-google, body.layout-google,
body.layout-heroku, body.layout-heroku,
body.layout-mailgun, body.layout-mailgun,
body.layout-openstack, body.layout-openstack,
body.layout-template, body.layout-template,
body.layout-digitalocean,
body.layout-aws,
body.layout-docs, body.layout-docs,
body.layout-inner,
body.layout-downloads, body.layout-downloads,
body.layout-inner,
body.layout-intro{ body.layout-intro{
background: $light-black image-url('sidebar-wire.png') left 62px no-repeat; background: $light-black image-url('sidebar-wire.png') left 62px no-repeat;
@ -287,4 +288,3 @@ body.layout-intro{
} }
} }
} }

View File

@ -0,0 +1,43 @@
---
layout: "azure"
page_title: "Provider: Azure"
sidebar_current: "docs-azure-index"
description: |-
The Azure provider is used to interact with the many resources supported by Azure. The provider needs to be configured with a publish settings file and optionally a subscription ID before it can be used.
---
# Azure Provider
The Azure provider is used to interact with the many resources supported
by Azure. The provider needs to be configured with a [publish settings
file](https://manage.windowsazure.com/publishsettings) and optionally a
subscription ID before it can be used.
Use the navigation to the left to read about the available resources.
## Example Usage
```
# Configure the Azure Provider
provider "azure" {
settings_file = "${var.azure_settings_file}"
}
# Create a web server
resource "azure_instance" "web" {
...
}
```
## Argument Reference
The following arguments are supported:
* `settings_file` - (Required) The path to a publish settings file used to
authenticate with the Azure API. You can download the settings file here:
https://manage.windowsazure.com/publishsettings. It must be provided, but
it can also be sourced from the `AZURE_SETTINGS_FILE` environment variable.
* `subscription_id` - (Optional) The subscription ID to use. If not provided
the first subscription ID in publish settings file will be used. It can
also be sourced from the `AZURE_SUBSCRIPTION_ID` environment variable.

View File

@ -0,0 +1,58 @@
---
layout: "cloudstack"
page_title: "CloudStack: cloudstack_disk"
sidebar_current: "docs-cloudstack-resource-disk"
description: |-
Creates a disk volume from a disk offering. This disk volume will be attached to a virtual machine if the optional parameters are configured.
---
# cloudstack\_disk
Creates a disk volume from a disk offering. This disk volume will be attached to
a virtual machine if the optional parameters are configured.
## Example Usage
```
resource "cloudstack_disk" "default" {
name = "test-disk"
attach = "true"
disk_offering = "custom"
size = 50
virtual-machine = "server-1"
zone = "zone-1"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the disk volume. Changing this forces a new
resource to be created.
* `attach` - (Optional) Determines whether or not to attach the disk volume to a
virtual machine (defaults false).
* `device` - (Optional) The device to map the disk volume to within the guest OS.
* `disk_offering` - (Required) The name or ID of the disk offering to use for
this disk volume.
* `size` - (Optional) The size of the disk volume in gigabytes.
* `shrink_ok` - (Optional) Verifies if the disk volume is allowed to shrink when
resizing (defaults false).
* `virtual_machine` - (Optional) The name of the virtual machine to which you
want to attach the disk volume.
* `zone` - (Required) The name or ID of the zone where this disk volume will be available.
Changing this forces a new resource to be created.
## Attributes Reference
The following attributes are exported:
* `id` - The ID of the disk volume.
* `device` - The device the disk volume is mapped to within the guest OS.

View File

@ -0,0 +1,59 @@
---
layout: "azure"
page_title: "Azure: azure_instance"
sidebar_current: "docs-azure-resource-instance"
description: |-
Creates and automatically starts a virtual machine based on a service offering, disk offering, and template.
---
# azure\_instance
Creates and automatically starts a virtual machine based on a service offering,
disk offering, and template.
## Example Usage
```
resource "azure_instance" "web" {
name = "server-1"
service_offering= "small"
network = "network-1"
template = "CentOS 6.5"
zone = "zone-1"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the instance. Changing this forces a new
resource to be created.
* `display_name` - (Optional) The display name of the instance.
* `service_offering` - (Required) The name or ID of the service offering used for this instance.
* `network` - (Optional) The name or ID of the network to connect this instance to.
Changing this forces a new resource to be created.
* `ipaddress` - (Optional) The IP address to assign to this instance. Changing
this forces a new resource to be created.
* `template` - (Required) The name or ID of the template used for this instance.
Changing this forces a new resource to be created.
* `zone` - (Required) The name of the zone where this instance will be created.
Changing this forces a new resource to be created.
* `user_data` - (Optional) The user data to provide when launching the instance.
* `expunge` - (Optional) This determines if the instance is expunged when it is
destroyed (defaults false)
## Attributes Reference
The following attributes are exported:
* `id` - The instance ID.
* `display_name` - The display name of the instance.

View File

@ -0,0 +1,69 @@
---
layout: "cloudstack"
page_title: "CloudStack: cloudstack_network_acl_rule"
sidebar_current: "docs-cloudstack-resource-network-acl-rule"
description: |-
Creates network ACL rules for a given network ACL.
---
# cloudstack\_network\_acl\_rule
Creates network ACL rules for a given network ACL.
## Example Usage
```
resource "cloudstack_network_acl_rule" "default" {
aclid = "f3843ce0-334c-4586-bbd3-0c2e2bc946c6"
rule {
action = "allow"
source_cidr = "10.0.0.0/8"
protocol = "tcp"
ports = ["80", "1000-2000"]
traffic_type = "ingress"
}
}
```
## Argument Reference
The following arguments are supported:
* `aclid` - (Required) The network ACL ID for which to create the rules.
Changing this forces a new resource to be created.
* `managed` - (Optional) USE WITH CAUTION! If enabled all the firewall rules for
this network ACL will be managed by this resource. This means it will delete
all firewall rules that are not in your config! (defaults false)
* `rule` - (Optional) Can be specified multiple times. Each rule block supports
fields documented below. If `managed = false` at least one rule is required!
The `rule` block supports:
* `action` - (Optional) The action for the rule. Valid options are: `allow` and
`deny` (defaults allow).
* `source_cidr` - (Required) The source CIDR to allow access to the given ports.
* `protocol` - (Required) The name of the protocol to allow. Valid options are:
`tcp`, `udp`, `icmp`, `all` or a valid protocol number.
* `icmp_type` - (Optional) The ICMP type to allow. This can only be specified if
the protocol is ICMP.
* `icmp_code` - (Optional) The ICMP code to allow. This can only be specified if
the protocol is ICMP.
* `ports` - (Optional) List of ports and/or port ranges to allow. This can only
be specified if the protocol is TCP, UDP, ALL or a valid protocol number.
* `traffic_type` - (Optional) The traffic type for the rule. Valid options are:
`ingress` or `egress` (defaults ingress).
## Attributes Reference
The following attributes are exported:
* `id` - The ACL ID for which the rules are created.

View File

@ -0,0 +1,54 @@
---
layout: "cloudstack"
page_title: "CloudStack: cloudstack_network"
sidebar_current: "docs-cloudstack-resource-network"
description: |-
Creates a network.
---
# cloudstack\_network
Creates a network.
## Example Usage
Basic usage:
```
resource "cloudstack_network" "default" {
name = "test-network"
cidr = "10.0.0.0/16"
network_offering = "Default Network"
zone = "zone-1"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the network.
* `display_text` - (Optional) The display text of the network.
* `cidr` - (Required) The CIDR block for the network. Changing this forces a new
resource to be created.
* `network_offering` - (Required) The name or ID of the network offering to use
for this network.
* `vpc` - (Optional) The name of the VPC to create this network for. Changing
this forces a new resource to be created.
* `aclid` - (Optional) The ID of a network ACL that should be attached to the
network. Changing this forces a new resource to be created.
* `zone` - (Required) The name or ID of the zone where this disk volume will be
available. Changing this forces a new resource to be created.
## Attributes Reference
The following attributes are exported:
* `id` - The ID of the network.
* `display_text` - The display text of the network.

View File

@ -0,0 +1,38 @@
<% wrap_layout :inner do %>
<% content_for :sidebar do %>
<div class="docs-sidebar hidden-print affix-top" role="complementary">
<ul class="nav docs-sidenav">
<li<%= sidebar_current("docs-home") %>>
<a href="/docs/providers/index.html">&laquo; Documentation Home</a>
</li>
<li<%= sidebar_current("docs-azure-index") %>>
<a href="/docs/providers/azure/index.html">Azure Provider</a>
</li>
<li<%= sidebar_current(/^docs-azure-resource/) %>>
<a href="#">Resources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-azure-resource-disk") %>>
<a href="/docs/providers/azure/r/disk.html">azure_disk</a>
</li>
<li<%= sidebar_current("docs-azure-resource-instance") %>>
<a href="/docs/providers/azure/r/instance.html">azure_instance</a>
</li>
<li<%= sidebar_current("docs-azure-resource-security-group") %>>
<a href="/docs/providers/azure/r/security_group.html">azure_security_group</a>
</li>
<li<%= sidebar_current("docs-azure-resource-virtual-network") %>>
<a href="/docs/providers/azure/r/virtual_network.html">azure_virtual_network</a>
</li>
</ul>
</li>
</ul>
</div>
<% end %>
<%= yield %>
<% end %>

View File

@ -119,19 +119,23 @@
<ul class="nav"> <ul class="nav">
<li<%= sidebar_current("docs-providers-atlas") %>> <li<%= sidebar_current("docs-providers-atlas") %>>
<a href="/docs/providers/atlas/index.html">Atlas</a> <a href="/docs/providers/atlas/index.html">Atlas</a>
</li> </li>
<li<%= sidebar_current("docs-providers-aws") %>> <li<%= sidebar_current("docs-providers-aws") %>>
<a href="/docs/providers/aws/index.html">AWS</a> <a href="/docs/providers/aws/index.html">AWS</a>
</li> </li>
<li<%= sidebar_current("docs-providers-azure") %>>
<a href="/docs/providers/azure/index.html">Azure</a>
</li>
<li<%= sidebar_current("docs-providers-cloudflare") %>> <li<%= sidebar_current("docs-providers-cloudflare") %>>
<a href="/docs/providers/cloudflare/index.html">CloudFlare</a> <a href="/docs/providers/cloudflare/index.html">CloudFlare</a>
</li> </li>
<li<%= sidebar_current("docs-providers-cloudstack") %>> <li<%= sidebar_current("docs-providers-cloudstack") %>>
<a href="/docs/providers/cloudstack/index.html">CloudStack</a> <a href="/docs/providers/cloudstack/index.html">CloudStack</a>
</li> </li>
<li<%= sidebar_current("docs-providers-consul") %>> <li<%= sidebar_current("docs-providers-consul") %>>
<a href="/docs/providers/consul/index.html">Consul</a> <a href="/docs/providers/consul/index.html">Consul</a>