Merge pull request #2922 from hashicorp/azure-settings-file

provider/azure: Allow settings_file to accept XML string
This commit is contained in:
Clint 2015-08-03 16:05:00 -05:00
commit 27b1aa6637
5 changed files with 151 additions and 28 deletions

View File

@ -2,7 +2,6 @@ package azure
import ( import (
"fmt" "fmt"
"os"
"sync" "sync"
"github.com/Azure/azure-sdk-for-go/management" "github.com/Azure/azure-sdk-for-go/management"
@ -22,7 +21,7 @@ import (
// Config is the configuration structure used to instantiate a // Config is the configuration structure used to instantiate a
// new Azure management client. // new Azure management client.
type Config struct { type Config struct {
SettingsFile string Settings []byte
SubscriptionID string SubscriptionID string
Certificate []byte Certificate []byte
ManagementURL string ManagementURL string
@ -96,14 +95,8 @@ func (c Client) getStorageServiceQueueClient(serviceName string) (storage.QueueS
return storageClient.GetQueueService(), err return storageClient.GetQueueService(), err
} }
// NewClientFromSettingsFile returns a new Azure management func (c *Config) NewClientFromSettingsData() (*Client, error) {
// client created using a publish settings file. mc, err := management.ClientFromPublishSettingsData(c.Settings, c.SubscriptionID)
func (c *Config) NewClientFromSettingsFile() (*Client, error) {
if _, err := os.Stat(c.SettingsFile); os.IsNotExist(err) {
return nil, fmt.Errorf("Publish Settings file %q does not exist!", c.SettingsFile)
}
mc, err := management.ClientFromPublishSettingsFile(c.SettingsFile, c.SubscriptionID)
if err != nil { if err != nil {
return nil, nil return nil, nil
} }

View File

@ -1,7 +1,10 @@
package azure package azure
import ( import (
"encoding/xml"
"fmt" "fmt"
"io/ioutil"
"os"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
@ -13,9 +16,10 @@ func Provider() terraform.ResourceProvider {
return &schema.Provider{ return &schema.Provider{
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"settings_file": &schema.Schema{ "settings_file": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
DefaultFunc: schema.EnvDefaultFunc("AZURE_SETTINGS_FILE", nil), DefaultFunc: schema.EnvDefaultFunc("AZURE_SETTINGS_FILE", nil),
ValidateFunc: validateSettingsFile,
}, },
"subscription_id": &schema.Schema{ "subscription_id": &schema.Schema{
@ -55,19 +59,28 @@ func Provider() terraform.ResourceProvider {
} }
func providerConfigure(d *schema.ResourceData) (interface{}, error) { func providerConfigure(d *schema.ResourceData) (interface{}, error) {
settingsFile, err := homedir.Expand(d.Get("settings_file").(string))
if err != nil {
return nil, fmt.Errorf("Error expanding the settings file path: %s", err)
}
config := Config{ config := Config{
SettingsFile: settingsFile,
SubscriptionID: d.Get("subscription_id").(string), SubscriptionID: d.Get("subscription_id").(string),
Certificate: []byte(d.Get("certificate").(string)), Certificate: []byte(d.Get("certificate").(string)),
} }
if config.SettingsFile != "" { settings := d.Get("settings_file").(string)
return config.NewClientFromSettingsFile()
if settings != "" {
if ok, _ := isFile(settings); ok {
settingsFile, err := homedir.Expand(settings)
if err != nil {
return nil, fmt.Errorf("Error expanding the settings file path: %s", err)
}
publishSettingsContent, err := ioutil.ReadFile(settingsFile)
if err != nil {
return nil, fmt.Errorf("Error reading settings file: %s", err)
}
config.Settings = publishSettingsContent
} else {
config.Settings = []byte(settings)
}
return config.NewClientFromSettingsData()
} }
if config.SubscriptionID != "" && len(config.Certificate) > 0 { if config.SubscriptionID != "" && len(config.Certificate) > 0 {
@ -78,3 +91,44 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
"Insufficient configuration data. Please specify either a 'settings_file'\n" + "Insufficient configuration data. Please specify either a 'settings_file'\n" +
"or both a 'subscription_id' and 'certificate'.") "or both a 'subscription_id' and 'certificate'.")
} }
func validateSettingsFile(v interface{}, k string) (warnings []string, errors []error) {
value := v.(string)
if value == "" {
return
}
var settings settingsData
if err := xml.Unmarshal([]byte(value), &settings); err != nil {
warnings = append(warnings, `
settings_file is not valid XML, so we are assuming it is a file path. This
support will be removed in the future. Please update your configuration to use
${file("filename.publishsettings")} instead.`)
} else {
return
}
if ok, err := isFile(value); !ok {
errors = append(errors,
fmt.Errorf(
"account_file path could not be read from '%s': %s",
value,
err))
}
return
}
func isFile(v string) (bool, error) {
if _, err := os.Stat(v); err != nil {
return false, err
}
return true, nil
}
// settingsData is a private struct used to test the unmarshalling of the
// settingsFile contents, to determine if the contents are valid XML
type settingsData struct {
XMLName xml.Name `xml:"PublishData"`
}

View File

@ -1,6 +1,9 @@
package azure package azure
import ( import (
"io"
"io/ioutil"
"log"
"os" "os"
"testing" "testing"
@ -58,3 +61,71 @@ func testAccPreCheck(t *testing.T) {
t.Fatal("AZURE_STORAGE must be set for acceptance tests") t.Fatal("AZURE_STORAGE must be set for acceptance tests")
} }
} }
func TestAzure_validateSettingsFile(t *testing.T) {
f, err := ioutil.TempFile("", "tf-test")
if err != nil {
t.Fatalf("Error creating temporary file in TestAzure_validateSettingsFile: %s", err)
}
fx, err := ioutil.TempFile("", "tf-test-xml")
if err != nil {
t.Fatalf("Error creating temporary file with XML in TestAzure_validateSettingsFile: %s", err)
}
_, err = io.WriteString(fx, "<PublishData></PublishData>")
if err != nil {
t.Fatalf("Error writing XML File: %s", err)
}
log.Printf("fx name: %s", fx.Name())
fx.Close()
cases := []struct {
Input string // String of XML or a path to an XML file
W int // expected count of warnings
E int // expected count of errors
}{
{"test", 1, 1},
{f.Name(), 1, 0},
{fx.Name(), 1, 0},
{"<PublishData></PublishData>", 0, 0},
}
for _, tc := range cases {
w, e := validateSettingsFile(tc.Input, "")
if len(w) != tc.W {
t.Errorf("Error in TestAzureValidateSettingsFile: input: %s , warnings: %#v, errors: %#v", tc.Input, w, e)
}
if len(e) != tc.E {
t.Errorf("Error in TestAzureValidateSettingsFile: input: %s , warnings: %#v, errors: %#v", tc.Input, w, e)
}
}
}
func TestAzure_isFile(t *testing.T) {
f, err := ioutil.TempFile("", "tf-test-file")
if err != nil {
t.Fatalf("Error creating temporary file with XML in TestAzure_isFile: %s", err)
}
cases := []struct {
Input string // String path to file
B bool // expected true/false
E bool // expect error
}{
{"test", false, true},
{f.Name(), true, false},
}
for _, tc := range cases {
x, y := isFile(tc.Input)
if tc.B != x {
t.Errorf("Error in TestAzure_isFile: input: %s , returned: %#v, expected: %#v", tc.Input, x, tc.B)
}
if tc.E != (y != nil) {
t.Errorf("Error in TestAzure_isFile: input: %s , returned: %#v, expected: %#v", tc.Input, y, tc.E)
}
}
}

View File

@ -2,7 +2,9 @@ package azure
import ( import (
"fmt" "fmt"
"math/rand"
"testing" "testing"
"time"
"github.com/Azure/azure-sdk-for-go/management" "github.com/Azure/azure-sdk-for-go/management"
"github.com/Azure/azure-sdk-for-go/management/virtualmachine" "github.com/Azure/azure-sdk-for-go/management/virtualmachine"
@ -10,6 +12,9 @@ import (
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
var randInt = rand.New(rand.NewSource(time.Now().UnixNano())).Int()
var instanceName = fmt.Sprintf("terraform-test-%d", randInt)
func TestAccAzureInstance_basic(t *testing.T) { func TestAccAzureInstance_basic(t *testing.T) {
var dpmt virtualmachine.DeploymentResponse var dpmt virtualmachine.DeploymentResponse
@ -25,9 +30,9 @@ func TestAccAzureInstance_basic(t *testing.T) {
"azure_instance.foo", "", &dpmt), "azure_instance.foo", "", &dpmt),
testAccCheckAzureInstanceBasicAttributes(&dpmt), testAccCheckAzureInstanceBasicAttributes(&dpmt),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"azure_instance.foo", "name", "terraform-test"), "azure_instance.foo", "name", instanceName),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"azure_instance.foo", "hosted_service_name", "terraform-test"), "azure_instance.foo", "hosted_service_name", instanceName),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"azure_instance.foo", "location", "West US"), "azure_instance.foo", "location", "West US"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
@ -194,7 +199,7 @@ func testAccCheckAzureInstanceBasicAttributes(
dpmt *virtualmachine.DeploymentResponse) resource.TestCheckFunc { dpmt *virtualmachine.DeploymentResponse) resource.TestCheckFunc {
return func(s *terraform.State) error { return func(s *terraform.State) error {
if dpmt.Name != "terraform-test" { if dpmt.Name != instanceName {
return fmt.Errorf("Bad name: %s", dpmt.Name) return fmt.Errorf("Bad name: %s", dpmt.Name)
} }
@ -363,7 +368,7 @@ func testAccCheckAzureInstanceDestroyed(hostedServiceName string) resource.TestC
var testAccAzureInstance_basic = fmt.Sprintf(` var testAccAzureInstance_basic = fmt.Sprintf(`
resource "azure_instance" "foo" { resource "azure_instance" "foo" {
name = "terraform-test" name = "%s"
image = "Ubuntu Server 14.04 LTS" image = "Ubuntu Server 14.04 LTS"
size = "Basic_A1" size = "Basic_A1"
storage_service_name = "%s" storage_service_name = "%s"
@ -377,7 +382,7 @@ resource "azure_instance" "foo" {
public_port = 22 public_port = 22
private_port = 22 private_port = 22
} }
}`, testAccStorageServiceName) }`, instanceName, testAccStorageServiceName)
var testAccAzureInstance_seperateHostedService = fmt.Sprintf(` var testAccAzureInstance_seperateHostedService = fmt.Sprintf(`
resource "azure_hosted_service" "foo" { resource "azure_hosted_service" "foo" {

View File

@ -20,7 +20,7 @@ Use the navigation to the left to read about the available resources.
``` ```
# Configure the Azure Provider # Configure the Azure Provider
provider "azure" { provider "azure" {
settings_file = "${var.azure_settings_file}" settings_file = "${file("credentials.publishsettings")}"
} }
# Create a web server # Create a web server
@ -33,7 +33,7 @@ resource "azure_instance" "web" {
The following arguments are supported: The following arguments are supported:
* `settings_file` - (Optional) The path to a publish settings file used to * `settings_file` - (Optional) Contents of a valid `publishsettings` file, used to
authenticate with the Azure API. You can download the settings file here: authenticate with the Azure API. You can download the settings file here:
https://manage.windowsazure.com/publishsettings. You must either provide https://manage.windowsazure.com/publishsettings. You must either provide
(or source from the `AZURE_SETTINGS_FILE` environment variable) a settings (or source from the `AZURE_SETTINGS_FILE` environment variable) a settings