Skip to content

Commit

Permalink
provider/azure: Allow settings_file to accept XML string
Browse files Browse the repository at this point in the history
  • Loading branch information
catsby committed Aug 3, 2015
1 parent a563fbc commit 4903f0f
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 27 deletions.
13 changes: 3 additions & 10 deletions builtin/providers/azure/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package azure

import (
"fmt"
"os"
"sync"

"github.com/Azure/azure-sdk-for-go/management"
Expand All @@ -22,7 +21,7 @@ import (
// Config is the configuration structure used to instantiate a
// new Azure management client.
type Config struct {
SettingsFile string
Settings []byte
SubscriptionID string
Certificate []byte
ManagementURL string
Expand Down Expand Up @@ -96,14 +95,8 @@ func (c Client) getStorageServiceQueueClient(serviceName string) (storage.QueueS
return storageClient.GetQueueService(), err
}

// NewClientFromSettingsFile returns a new Azure management
// client created using a publish settings file.
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)
func (c *Config) NewClientFromSettingsData() (*Client, error) {
mc, err := management.ClientFromPublishSettingsData(c.Settings, c.SubscriptionID)
if err != nil {
return nil, nil
}
Expand Down
76 changes: 65 additions & 11 deletions builtin/providers/azure/provider.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package azure

import (
"encoding/xml"
"fmt"
"io/ioutil"
"os"

"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
Expand All @@ -13,9 +16,10 @@ func Provider() terraform.ResourceProvider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
"settings_file": &schema.Schema{
Type: schema.TypeString,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("AZURE_SETTINGS_FILE", nil),
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("AZURE_SETTINGS_FILE", nil),
ValidateFunc: validateSettingsFile,
},

"subscription_id": &schema.Schema{
Expand Down Expand Up @@ -55,19 +59,28 @@ func Provider() terraform.ResourceProvider {
}

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{
SettingsFile: settingsFile,
SubscriptionID: d.Get("subscription_id").(string),
Certificate: []byte(d.Get("certificate").(string)),
}

if config.SettingsFile != "" {
return config.NewClientFromSettingsFile()
settings := d.Get("settings_file").(string)

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 {
Expand All @@ -78,3 +91,44 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
"Insufficient configuration data. Please specify either a 'settings_file'\n" +
"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"`
}
71 changes: 71 additions & 0 deletions builtin/providers/azure/provider_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package azure

import (
"io"
"io/ioutil"
"log"
"os"
"testing"

Expand Down Expand Up @@ -58,3 +61,71 @@ func testAccPreCheck(t *testing.T) {
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)
}
}
}
13 changes: 9 additions & 4 deletions builtin/providers/azure/resource_azure_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ package azure

import (
"fmt"
"math/rand"
"testing"
"time"

"github.com/Azure/azure-sdk-for-go/management"
"github.com/Azure/azure-sdk-for-go/management/virtualmachine"
"github.com/hashicorp/terraform/helper/resource"
"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) {
var dpmt virtualmachine.DeploymentResponse

Expand All @@ -25,7 +30,7 @@ func TestAccAzureInstance_basic(t *testing.T) {
"azure_instance.foo", "", &dpmt),
testAccCheckAzureInstanceBasicAttributes(&dpmt),
resource.TestCheckResourceAttr(
"azure_instance.foo", "name", "terraform-test"),
"azure_instance.foo", "name", instanceName),
resource.TestCheckResourceAttr(
"azure_instance.foo", "hosted_service_name", "terraform-test"),
resource.TestCheckResourceAttr(
Expand Down Expand Up @@ -194,7 +199,7 @@ func testAccCheckAzureInstanceBasicAttributes(
dpmt *virtualmachine.DeploymentResponse) resource.TestCheckFunc {
return func(s *terraform.State) error {

if dpmt.Name != "terraform-test" {
if dpmt.Name != instanceName {
return fmt.Errorf("Bad name: %s", dpmt.Name)
}

Expand Down Expand Up @@ -363,7 +368,7 @@ func testAccCheckAzureInstanceDestroyed(hostedServiceName string) resource.TestC

var testAccAzureInstance_basic = fmt.Sprintf(`
resource "azure_instance" "foo" {
name = "terraform-test"
name = "%s"
image = "Ubuntu Server 14.04 LTS"
size = "Basic_A1"
storage_service_name = "%s"
Expand All @@ -377,7 +382,7 @@ resource "azure_instance" "foo" {
public_port = 22
private_port = 22
}
}`, testAccStorageServiceName)
}`, instanceName, testAccStorageServiceName)

var testAccAzureInstance_seperateHostedService = fmt.Sprintf(`
resource "azure_hosted_service" "foo" {
Expand Down
4 changes: 2 additions & 2 deletions website/source/docs/providers/azure/index.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Use the navigation to the left to read about the available resources.
```
# Configure the Azure Provider
provider "azure" {
settings_file = "${var.azure_settings_file}"
settings_file = "${file("credentials.publishsettings")}"
}
# Create a web server
Expand All @@ -33,7 +33,7 @@ resource "azure_instance" "web" {

The following arguments are supported:

* `settings_file` - (Optional) The path to a publish settings file used to
* `settings_file` - (Required) Contents of a valid `publishsettings` file, used to
authenticate with the Azure API. You can download the settings file here:
https://manage.windowsazure.com/publishsettings. You must either provide
(or source from the `AZURE_SETTINGS_FILE` environment variable) a settings
Expand Down

0 comments on commit 4903f0f

Please sign in to comment.