Merge pull request #2922 from hashicorp/azure-settings-file
provider/azure: Allow settings_file to accept XML string
This commit is contained in:
commit
27b1aa6637
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"`
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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" {
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue