From b05e36a6e378008adf2c435942651e269fd9c586 Mon Sep 17 00:00:00 2001 From: Kirill Shirinkin Date: Wed, 27 May 2015 16:46:12 +0200 Subject: [PATCH 01/91] Add module outputs --- command/output.go | 26 ++++++++++++-- command/output_test.go | 77 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 2 deletions(-) diff --git a/command/output.go b/command/output.go index 2e3c1bee0..9e99cce5f 100644 --- a/command/output.go +++ b/command/output.go @@ -15,9 +15,12 @@ type OutputCommand struct { func (c *OutputCommand) Run(args []string) int { args = c.Meta.process(args, false) + var module string cmdFlags := flag.NewFlagSet("output", flag.ContinueOnError) cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") + cmdFlags.StringVar(&module, "module", "", "module") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } + if err := cmdFlags.Parse(args); err != nil { return 1 } @@ -38,15 +41,34 @@ func (c *OutputCommand) Run(args []string) int { return 1 } + if module == "" { + module = "root" + } else { + module = "root." + module + } + + // Get the proper module we want to get outputs for + modPath := strings.Split(module, ".") + state := stateStore.State() - if state.Empty() || len(state.RootModule().Outputs) == 0 { + mod := state.ModuleByPath(modPath) + + if mod == nil { + c.Ui.Error(fmt.Sprintf( + "The module %s could not be found. There is nothing to taint.", + module)) + return 1 + } + + if state.Empty() || len(mod.Outputs) == 0 { c.Ui.Error(fmt.Sprintf( "The state file has no outputs defined. Define an output\n" + "in your configuration with the `output` directive and re-run\n" + "`terraform apply` for it to become available.")) return 1 } - v, ok := state.RootModule().Outputs[name] + + v, ok := mod.Outputs[name] if !ok { c.Ui.Error(fmt.Sprintf( "The output variable requested could not be found in the state\n" + diff --git a/command/output_test.go b/command/output_test.go index d3444c389..f84642269 100644 --- a/command/output_test.go +++ b/command/output_test.go @@ -47,6 +47,83 @@ func TestOutput(t *testing.T) { } } +func TestModuleOutput(t *testing.T) { + originalState := &terraform.State{ + Modules: []*terraform.ModuleState{ + &terraform.ModuleState{ + Path: []string{"root"}, + Outputs: map[string]string{ + "foo": "bar", + }, + }, + &terraform.ModuleState{ + Path: []string{"root", "my_module"}, + Outputs: map[string]string{ + "blah": "tastatur", + }, + }, + }, + } + + statePath := testStateFile(t, originalState) + + ui := new(cli.MockUi) + c := &OutputCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, + } + + args := []string{ + "-state", statePath, + "-module", "my_module", + "blah", + } + + if code := c.Run(args); code != 0 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } + + actual := strings.TrimSpace(ui.OutputWriter.String()) + if actual != "tastatur" { + t.Fatalf("bad: %#v", actual) + } +} + +func TestMissingModuleOutput(t *testing.T) { + originalState := &terraform.State{ + Modules: []*terraform.ModuleState{ + &terraform.ModuleState{ + Path: []string{"root"}, + Outputs: map[string]string{ + "foo": "bar", + }, + }, + }, + } + + statePath := testStateFile(t, originalState) + + ui := new(cli.MockUi) + c := &OutputCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(testProvider()), + Ui: ui, + }, + } + + args := []string{ + "-state", statePath, + "-module", "not_existing_module", + "blah", + } + + if code := c.Run(args); code != 1 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } +} + func TestOutput_badVar(t *testing.T) { originalState := &terraform.State{ Modules: []*terraform.ModuleState{ From b945d1ee0ba3fbb132f323324697b38b2aade3eb Mon Sep 17 00:00:00 2001 From: Kirill Shirinkin Date: Wed, 27 May 2015 17:11:31 +0200 Subject: [PATCH 02/91] Update docs --- website/source/docs/commands/output.html.markdown | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/website/source/docs/commands/output.html.markdown b/website/source/docs/commands/output.html.markdown index ac1ab5a23..f1a70394e 100644 --- a/website/source/docs/commands/output.html.markdown +++ b/website/source/docs/commands/output.html.markdown @@ -21,4 +21,8 @@ current directory for the state file to query. The command-line flags are all optional. The list of available flags are: * `-state=path` - Path to the state file. Defaults to "terraform.tfstate". - +* `-module=module_name` - The module path which has needed output. + By default this is the root path. Other modules can be specified by + a period-separated list. Example: "foo" would reference the module + "foo" but "foo.bar" would reference the "bar" module in the "foo" + module. From 84a870a255aa9625a9eeb00ba1210886e737ab8f Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Fri, 24 Apr 2015 18:18:24 +0200 Subject: [PATCH 03/91] First few azure resources... MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Only the azure_instance is fully working (for both Linux and Windows instances) now, but needs some tests. network and disk and pretty much empty, but the idea is clear so will not take too much timeā€¦ --- builtin/bins/provider-azure/main.go | 12 + builtin/bins/provider-azure/main_test.go | 1 + builtin/providers/azure/config.go | 29 + builtin/providers/azure/provider.go | 42 ++ builtin/providers/azure/provider_test.go | 35 ++ .../azure/resource_azure_instance.go | 497 ++++++++++++++++++ .../azure/resource_azure_instance_test.go | 1 + .../providers/azure/resource_azure_network.go | 51 ++ .../azure/resource_azure_network_test.go | 1 + builtin/providers/azure/resource_disk.go | 44 ++ builtin/providers/azure/resource_disk_test.go | 1 + builtin/providers/azure/resources.go | 1 + 12 files changed, 715 insertions(+) create mode 100644 builtin/bins/provider-azure/main.go create mode 100644 builtin/bins/provider-azure/main_test.go create mode 100644 builtin/providers/azure/config.go create mode 100644 builtin/providers/azure/provider.go create mode 100644 builtin/providers/azure/provider_test.go create mode 100644 builtin/providers/azure/resource_azure_instance.go create mode 100644 builtin/providers/azure/resource_azure_instance_test.go create mode 100644 builtin/providers/azure/resource_azure_network.go create mode 100644 builtin/providers/azure/resource_azure_network_test.go create mode 100644 builtin/providers/azure/resource_disk.go create mode 100644 builtin/providers/azure/resource_disk_test.go create mode 100644 builtin/providers/azure/resources.go diff --git a/builtin/bins/provider-azure/main.go b/builtin/bins/provider-azure/main.go new file mode 100644 index 000000000..45af21656 --- /dev/null +++ b/builtin/bins/provider-azure/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/hashicorp/terraform/builtin/providers/azure" + "github.com/hashicorp/terraform/plugin" +) + +func main() { + plugin.Serve(&plugin.ServeOpts{ + ProviderFunc: azure.Provider, + }) +} diff --git a/builtin/bins/provider-azure/main_test.go b/builtin/bins/provider-azure/main_test.go new file mode 100644 index 000000000..06ab7d0f9 --- /dev/null +++ b/builtin/bins/provider-azure/main_test.go @@ -0,0 +1 @@ +package main diff --git a/builtin/providers/azure/config.go b/builtin/providers/azure/config.go new file mode 100644 index 000000000..ad4dc60c0 --- /dev/null +++ b/builtin/providers/azure/config.go @@ -0,0 +1,29 @@ +package azure + +import ( + "fmt" + "os" + + "github.com/MSOpenTech/azure-sdk-for-go/management" +) + +// Config is the configuration structure used to instantiate a +// new Azure management client. +type Config struct { + SettingsFile string + SubscriptionID string +} + +// NewClient returns a new Azure management client +func (c *Config) NewClient() (*management.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 { + return nil, fmt.Errorf("Error creating management client: %s", err) + } + + return &mc, nil +} diff --git a/builtin/providers/azure/provider.go b/builtin/providers/azure/provider.go new file mode 100644 index 000000000..83e577a47 --- /dev/null +++ b/builtin/providers/azure/provider.go @@ -0,0 +1,42 @@ +package azure + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +// Provider returns a terraform.ResourceProvider. +func Provider() terraform.ResourceProvider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "settings_file": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("AZURE_SETTINGS_FILE", nil), + }, + + "subscription_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("AZURE_SUBSCRIPTION_ID", ""), + }, + }, + + ResourcesMap: map[string]*schema.Resource{ + "azure_disk": resourceAzureDisk(), + "azure_instance": resourceAzureInstance(), + "azure_network": resourceAzureNetwork(), + }, + + ConfigureFunc: providerConfigure, + } +} + +func providerConfigure(d *schema.ResourceData) (interface{}, error) { + config := Config{ + SettingsFile: d.Get("settings_file").(string), + SubscriptionID: d.Get("subscription_id").(string), + } + + return config.NewClient() +} diff --git a/builtin/providers/azure/provider_test.go b/builtin/providers/azure/provider_test.go new file mode 100644 index 000000000..5606dcdad --- /dev/null +++ b/builtin/providers/azure/provider_test.go @@ -0,0 +1,35 @@ +package azure + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +var testAccProviders map[string]terraform.ResourceProvider +var testAccProvider *schema.Provider + +func init() { + testAccProvider = Provider().(*schema.Provider) + testAccProviders = map[string]terraform.ResourceProvider{ + "azure": testAccProvider, + } +} + +func TestProvider(t *testing.T) { + if err := Provider().(*schema.Provider).InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestProvider_impl(t *testing.T) { + var _ terraform.ResourceProvider = Provider() +} + +func testAccPreCheck(t *testing.T) { + if v := os.Getenv("AZURE_SETTINGS_FILE"); v == "" { + t.Fatal("AZURE_SETTINGS_FILE must be set for acceptance tests") + } +} diff --git a/builtin/providers/azure/resource_azure_instance.go b/builtin/providers/azure/resource_azure_instance.go new file mode 100644 index 000000000..9c2e461ff --- /dev/null +++ b/builtin/providers/azure/resource_azure_instance.go @@ -0,0 +1,497 @@ +package azure + +import ( + "bytes" + "fmt" + "log" + "strings" + + "github.com/MSOpenTech/azure-sdk-for-go/management" + "github.com/MSOpenTech/azure-sdk-for-go/management/hostedservice" + "github.com/MSOpenTech/azure-sdk-for-go/management/osimage" + "github.com/MSOpenTech/azure-sdk-for-go/management/virtualmachine" + "github.com/MSOpenTech/azure-sdk-for-go/management/vmutils" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +const ( + linux = "Linux" + windows = "Windows" +) + +func resourceAzureInstance() *schema.Resource { + return &schema.Resource{ + Create: resourceAzureInstanceCreate, + Read: resourceAzureInstanceRead, + Update: resourceAzureInstanceUpdate, + Delete: resourceAzureInstanceDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "image": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "size": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "network": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "storage": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "reverse_dns": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "location": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "automatic_updates": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "time_zone": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "public_rdp": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "public_ssh": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "username": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "password": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "ssh_key_thumbprint": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "endpoint": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "protocol": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + + "local_port": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + }, + }, + Set: resourceAzureEndpointHash, + }, + + "ip_address": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "vip_address": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceAzureInstanceCreate(d *schema.ResourceData, meta interface{}) (err error) { + mc := meta.(*management.Client) + + name := d.Get("name").(string) + + // Compute/set the description + description := d.Get("description").(string) + if description == "" { + description = name + } + + // Retrieve the needed details of the image + imageName, imageURL, osType, err := retrieveImageDetails(mc, d.Get("image").(string)) + if err != nil { + 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 + if err := verifyParameters(d, osType); err != nil { + return err + } + + log.Printf("[DEBUG] Creating Cloud Service for instance: %s", name) + req, err := hostedservice.NewClient(*mc). + CreateHostedService( + name, + d.Get("location").(string), + d.Get("reverse_dns").(string), + name, + fmt.Sprintf("Cloud Service created automatically for instance %s", name), + ) + if err != nil { + return fmt.Errorf("Error creating Cloud Service for instance %s: %s", name, err) + } + + // Wait until the Cloud Service is created + if err := mc.WaitAsyncOperation(req); err != nil { + return fmt.Errorf( + "Error waiting for Cloud Service of instance %s to be created: %s", name, err) + } + + // Put in this defer here, so we are sure to cleanup already created parts + // when we exit with an error + defer func(mc *management.Client) { + if err != nil { + req, err := hostedservice.NewClient(*mc).DeleteHostedService(name, true) + if err != nil { + log.Printf("[DEBUG] Error cleaning up Cloud Service of instance %s: %s", name, err) + } + + // Wait until the Cloud Service is deleted + if err := mc.WaitAsyncOperation(req); err != nil { + log.Printf( + "[DEBUG] Error waiting for Cloud Service of instance %s to be deleted : %s", name, err) + } + } + }(mc) + + // Create a new role for the instance + role := vmutils.NewVmConfiguration(name, d.Get("size").(string)) + + log.Printf("[DEBUG] Configuring deployment from image...") + err = vmutils.ConfigureDeploymentFromPlatformImage( + &role, + imageName, + imageURL, + d.Get("image").(string), + ) + if err != nil { + return fmt.Errorf("Error configuring the deployment for %s: %s", name, err) + } + + if osType == linux { + // This is pretty ugly, but the Azure SDK leaves me no other choice... + if tp, ok := d.GetOk("ssh_key_thumbprint"); ok { + err = vmutils.ConfigureForLinux( + &role, + name, + d.Get("username").(string), + d.Get("password").(string), + tp.(string), + ) + } else { + err = vmutils.ConfigureForLinux( + &role, + name, + d.Get("username").(string), + d.Get("password").(string), + ) + } + if err != nil { + return fmt.Errorf("Error configuring %s for Linux: %s", name, err) + } + + if d.Get("public_ssh").(bool) { + if err := vmutils.ConfigureWithPublicSSH(&role); err != nil { + return fmt.Errorf("Error configuring %s for public SSH: %s", name, err) + } + } + } + + if osType == windows { + err = vmutils.ConfigureForWindows( + &role, + name, + d.Get("username").(string), + d.Get("password").(string), + d.Get("automatic_updates").(bool), + d.Get("time_zone").(string), + ) + if err != nil { + return fmt.Errorf("Error configuring %s for Windows: %s", name, err) + } + + if d.Get("public_rdp").(bool) { + if err := vmutils.ConfigureWithPublicRDP(&role); err != nil { + return fmt.Errorf("Error configuring %s for public RDP: %s", name, err) + } + } + } + + log.Printf("[DEBUG] Creating the new instance...") + req, err = virtualmachine.NewClient(*mc).CreateDeployment(role, name) + if err != nil { + return fmt.Errorf("Error creating instance %s: %s", name, err) + } + + log.Printf("[DEBUG] Waiting for the new instance to be created...") + if err := mc.WaitAsyncOperation(req); err != nil { + return fmt.Errorf( + "Error waiting for instance %s to be created: %s", name, err) + } + + /* + if v := d.Get("endpoint").(*schema.Set); v.Len() > 0 { + log.Printf("[DEBUG] Adding Endpoints to the Azure Virtual Machine...") + endpoints := make([]vmClient.InputEndpoint, v.Len()) + for i, v := range v.List() { + m := v.(map[string]interface{}) + endpoint := vmClient.InputEndpoint{} + endpoint.Name = m["name"].(string) + endpoint.Protocol = m["protocol"].(string) + endpoint.Port = m["port"].(int) + endpoint.LocalPort = m["local_port"].(int) + endpoints[i] = endpoint + } + + configSets := vmConfig.ConfigurationSets.ConfigurationSet + if len(configSets) == 0 { + return fmt.Errorf("Azure virtual machine does not have configuration sets") + } + for i := 0; i < len(configSets); i++ { + if configSets[i].ConfigurationSetType != "NetworkConfiguration" { + continue + } + configSets[i].InputEndpoints.InputEndpoint = + append(configSets[i].InputEndpoints.InputEndpoint, endpoints...) + } + } + + */ + + d.SetId(name) + + return resourceAzureInstanceRead(d, meta) +} + +func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error { + mc := meta.(*management.Client) + + log.Printf("[DEBUG] Retrieving Cloud Service for instance: %s", d.Id()) + cs, err := hostedservice.NewClient(*mc).GetHostedService(d.Id()) + if err != nil { + return fmt.Errorf("Error retrieving Cloud Service of instance %s: %s", d.Id(), err) + } + + d.Set("reverse_dns", cs.ReverseDnsFqdn) + d.Set("location", cs.Location) + + log.Printf("[DEBUG] Retrieving instance: %s", d.Id()) + wi, err := virtualmachine.NewClient(*mc).GetDeployment(d.Id(), d.Id()) + if err != nil { + return fmt.Errorf("Error retrieving instance %s: %s", d.Id(), err) + } + + if len(wi.RoleList) == 0 { + return fmt.Errorf("Instance %s does not have VIP addresses", d.Id()) + } + role := wi.RoleList[0] + + d.Set("size", role.RoleSize) + + if len(wi.RoleInstanceList) == 0 { + return fmt.Errorf("Instance %s does not have IP addresses", d.Id()) + } + d.Set("ip_address", wi.RoleInstanceList[0].IpAddress) + + if len(wi.VirtualIPs) == 0 { + return fmt.Errorf("Instance %s does not have VIP addresses", d.Id()) + } + d.Set("vip_address", wi.VirtualIPs[0].Address) + + connType := "ssh" + if role.OSVirtualHardDisk.OS == windows { + connType = windows + } + + // Set the connection info for any configured provisioners + d.SetConnInfo(map[string]string{ + "type": connType, + "host": wi.VirtualIPs[0].Address, + "user": d.Get("username").(string), + }) + + return nil +} + +func resourceAzureInstanceUpdate(d *schema.ResourceData, meta interface{}) error { + mc := meta.(*management.Client) + + if d.HasChange("size") { + role, err := virtualmachine.NewClient(*mc).GetRole(d.Id(), d.Id(), d.Id()) + if err != nil { + return fmt.Errorf("Error retrieving role of instance %s: %s", d.Id(), err) + } + + role.RoleSize = d.Get("size").(string) + + req, err := virtualmachine.NewClient(*mc).UpdateRole(d.Id(), d.Id(), d.Id(), *role) + if err != nil { + return fmt.Errorf("Error updating role of instance %s: %s", d.Id(), err) + } + + if err := mc.WaitAsyncOperation(req); err != nil { + return fmt.Errorf( + "Error waiting for role of instance %s to be updated: %s", d.Id(), err) + } + } + + if d.HasChange("endpoint") { + + } + + return resourceAzureInstanceRead(d, meta) +} + +func resourceAzureInstanceDelete(d *schema.ResourceData, meta interface{}) error { + mc := meta.(*management.Client) + + log.Printf("[DEBUG] Deleting instance: %s", d.Id()) + req, err := hostedservice.NewClient(*mc).DeleteHostedService(d.Id(), true) + if err != nil { + return fmt.Errorf("Error deleting instance %s: %s", d.Id(), err) + } + + // Wait until the instance is deleted + if err := mc.WaitAsyncOperation(req); err != nil { + return fmt.Errorf( + "Error waiting for instance %s to be deleted: %s", d.Id(), err) + } + + d.SetId("") + + return nil +} + +func resourceAzureEndpointHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string))) + buf.WriteString(fmt.Sprintf("%d-", m["port"].(int))) + buf.WriteString(fmt.Sprintf("%d-", m["local_port"].(int))) + + return hashcode.String(buf.String()) +} + +func retrieveImageDetails(mc *management.Client, label string) (string, string, string, error) { + imgs, err := osimage.NewClient(*mc).GetImageList() + if err != nil { + return "", "", "", fmt.Errorf("Error retrieving image details: %s", err) + } + + var labels []string + for _, img := range imgs { + if img.Label == label { + if img.OS != linux && img.OS != windows { + return "", "", "", fmt.Errorf("Unsupported image OS: %s", img.OS) + } + return img.Name, img.MediaLink, img.OS, nil + } + labels = append(labels, img.Label) + } + + return "", "", "", + fmt.Errorf("Could not find image with label '%s', available labels are: %s", + label, strings.Join(labels, ",")) +} + +func verifyParameters(d *schema.ResourceData, osType string) error { + if osType == linux { + _, pass := d.GetOk("password") + _, key := d.GetOk("ssh_key_thumbprint") + + if !pass && !key { + return fmt.Errorf( + "You must supply a 'password' and/or a 'ssh_key_thumbprint' when using a Linux image") + } + + if key { + // check if it's a file of a string containing the key + } + } + + if osType == windows { + if _, ok := d.GetOk("password"); !ok { + return fmt.Errorf("You must supply a 'password' when using a Windows image") + } + + if _, ok := d.GetOk("time_zone"); !ok { + return fmt.Errorf("You must supply a 'time_zone' when using a Windows image") + } + } + + return nil +} diff --git a/builtin/providers/azure/resource_azure_instance_test.go b/builtin/providers/azure/resource_azure_instance_test.go new file mode 100644 index 000000000..6512f735e --- /dev/null +++ b/builtin/providers/azure/resource_azure_instance_test.go @@ -0,0 +1 @@ +package azure diff --git a/builtin/providers/azure/resource_azure_network.go b/builtin/providers/azure/resource_azure_network.go new file mode 100644 index 000000000..33d1d15a5 --- /dev/null +++ b/builtin/providers/azure/resource_azure_network.go @@ -0,0 +1,51 @@ +package azure + +import "github.com/hashicorp/terraform/helper/schema" + +func resourceAzureNetwork() *schema.Resource { + return &schema.Resource{ + Create: resourceAzureNetworkCreate, + Read: resourceAzureNetworkRead, + Update: resourceAzureNetworkUpdate, + Delete: resourceAzureNetworkDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "virtual": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + ForceNew: true, + }, + }, + } +} + +func resourceAzureNetworkCreate(d *schema.ResourceData, meta interface{}) (err error) { + //mc := meta.(*management.Client) + + return resourceAzureNetworkRead(d, meta) +} + +func resourceAzureNetworkRead(d *schema.ResourceData, meta interface{}) error { + //mc := meta.(*management.Client) + + return nil +} + +func resourceAzureNetworkUpdate(d *schema.ResourceData, meta interface{}) error { + //mc := meta.(*management.Client) + + return resourceAzureNetworkRead(d, meta) +} + +func resourceAzureNetworkDelete(d *schema.ResourceData, meta interface{}) error { + //mc := meta.(*management.Client) + + return nil +} diff --git a/builtin/providers/azure/resource_azure_network_test.go b/builtin/providers/azure/resource_azure_network_test.go new file mode 100644 index 000000000..6512f735e --- /dev/null +++ b/builtin/providers/azure/resource_azure_network_test.go @@ -0,0 +1 @@ +package azure diff --git a/builtin/providers/azure/resource_disk.go b/builtin/providers/azure/resource_disk.go new file mode 100644 index 000000000..8b0b09aae --- /dev/null +++ b/builtin/providers/azure/resource_disk.go @@ -0,0 +1,44 @@ +package azure + +import "github.com/hashicorp/terraform/helper/schema" + +func resourceAzureDisk() *schema.Resource { + return &schema.Resource{ + Create: resourceAzureNetworkCreate, + Read: resourceAzureNetworkRead, + Update: resourceAzureNetworkUpdate, + Delete: resourceAzureNetworkDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceAzureDiskCreate(d *schema.ResourceData, meta interface{}) (err error) { + //mc := meta.(*management.Client) + + return resourceAzureDiskRead(d, meta) +} + +func resourceAzureDiskRead(d *schema.ResourceData, meta interface{}) error { + //mc := meta.(*management.Client) + + return nil +} + +func resourceAzureDiskUpdate(d *schema.ResourceData, meta interface{}) error { + //mc := meta.(*management.Client) + + return resourceAzureDiskRead(d, meta) +} + +func resourceAzureDiskDelete(d *schema.ResourceData, meta interface{}) error { + //mc := meta.(*management.Client) + + return nil +} diff --git a/builtin/providers/azure/resource_disk_test.go b/builtin/providers/azure/resource_disk_test.go new file mode 100644 index 000000000..6512f735e --- /dev/null +++ b/builtin/providers/azure/resource_disk_test.go @@ -0,0 +1 @@ +package azure diff --git a/builtin/providers/azure/resources.go b/builtin/providers/azure/resources.go new file mode 100644 index 000000000..6512f735e --- /dev/null +++ b/builtin/providers/azure/resources.go @@ -0,0 +1 @@ +package azure From 4e33d898e9aa1e6806abcfb2e018f44d62e024c6 Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Thu, 30 Apr 2015 10:58:45 +0200 Subject: [PATCH 04/91] Needs more testing and tests, but it's becoming a nice provider --- builtin/providers/azure/provider.go | 7 +- ...esource_disk.go => resource_azure_disk.go} | 10 +- ...rk_test.go => resource_azure_disk_test.go} | 0 .../azure/resource_azure_instance.go | 471 ++++++++++-------- .../providers/azure/resource_azure_network.go | 51 -- .../azure/resource_azure_security_group.go | 304 +++++++++++ ... => resource_azure_security_group_test.go} | 0 .../azure/resource_azure_virtual_network.go | 205 ++++++++ .../resource_azure_virtual_network_test.go | 1 + 9 files changed, 791 insertions(+), 258 deletions(-) rename builtin/providers/azure/{resource_disk.go => resource_azure_disk.go} (83%) rename builtin/providers/azure/{resource_azure_network_test.go => resource_azure_disk_test.go} (100%) delete mode 100644 builtin/providers/azure/resource_azure_network.go create mode 100644 builtin/providers/azure/resource_azure_security_group.go rename builtin/providers/azure/{resource_disk_test.go => resource_azure_security_group_test.go} (100%) create mode 100644 builtin/providers/azure/resource_azure_virtual_network.go create mode 100644 builtin/providers/azure/resource_azure_virtual_network_test.go diff --git a/builtin/providers/azure/provider.go b/builtin/providers/azure/provider.go index 83e577a47..c4f117391 100644 --- a/builtin/providers/azure/provider.go +++ b/builtin/providers/azure/provider.go @@ -23,9 +23,10 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "azure_disk": resourceAzureDisk(), - "azure_instance": resourceAzureInstance(), - "azure_network": resourceAzureNetwork(), + "azure_disk": resourceAzureDisk(), + "azure_instance": resourceAzureInstance(), + "azure_security_group": resourceAzureSecurityGroup(), + "azure_virtual_network": resourceAzureVirtualNetwork(), }, ConfigureFunc: providerConfigure, diff --git a/builtin/providers/azure/resource_disk.go b/builtin/providers/azure/resource_azure_disk.go similarity index 83% rename from builtin/providers/azure/resource_disk.go rename to builtin/providers/azure/resource_azure_disk.go index 8b0b09aae..3b67a39c7 100644 --- a/builtin/providers/azure/resource_disk.go +++ b/builtin/providers/azure/resource_azure_disk.go @@ -4,10 +4,10 @@ import "github.com/hashicorp/terraform/helper/schema" func resourceAzureDisk() *schema.Resource { return &schema.Resource{ - Create: resourceAzureNetworkCreate, - Read: resourceAzureNetworkRead, - Update: resourceAzureNetworkUpdate, - Delete: resourceAzureNetworkDelete, + Create: resourceAzureDiskCreate, + Read: resourceAzureDiskRead, + Update: resourceAzureDiskUpdate, + Delete: resourceAzureDiskDelete, Schema: map[string]*schema.Schema{ "name": &schema.Schema{ @@ -19,7 +19,7 @@ func resourceAzureDisk() *schema.Resource { } } -func resourceAzureDiskCreate(d *schema.ResourceData, meta interface{}) (err error) { +func resourceAzureDiskCreate(d *schema.ResourceData, meta interface{}) error { //mc := meta.(*management.Client) return resourceAzureDiskRead(d, meta) diff --git a/builtin/providers/azure/resource_azure_network_test.go b/builtin/providers/azure/resource_azure_disk_test.go similarity index 100% rename from builtin/providers/azure/resource_azure_network_test.go rename to builtin/providers/azure/resource_azure_disk_test.go diff --git a/builtin/providers/azure/resource_azure_instance.go b/builtin/providers/azure/resource_azure_instance.go index 9c2e461ff..9fe45eee1 100644 --- a/builtin/providers/azure/resource_azure_instance.go +++ b/builtin/providers/azure/resource_azure_instance.go @@ -52,7 +52,13 @@ func resourceAzureInstance() *schema.Resource { Required: true, }, - "network": &schema.Schema{ + "subnet": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "virtual_network": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, @@ -88,18 +94,6 @@ func resourceAzureInstance() *schema.Resource { ForceNew: true, }, - "public_rdp": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - ForceNew: true, - }, - - "public_ssh": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - ForceNew: true, - }, - "username": &schema.Schema{ Type: schema.TypeString, Required: true, @@ -134,12 +128,12 @@ func resourceAzureInstance() *schema.Resource { Required: true, }, - "port": &schema.Schema{ + "public_port": &schema.Schema{ Type: schema.TypeInt, Required: true, }, - "local_port": &schema.Schema{ + "private_port": &schema.Schema{ Type: schema.TypeInt, Required: true, }, @@ -148,6 +142,16 @@ func resourceAzureInstance() *schema.Resource { Set: resourceAzureEndpointHash, }, + "security_groups": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: func(v interface{}) int { + return hashcode.String(v.(string)) + }, + }, + "ip_address": &schema.Schema{ Type: schema.TypeString, Computed: true, @@ -162,177 +166,165 @@ func resourceAzureInstance() *schema.Resource { } func resourceAzureInstanceCreate(d *schema.ResourceData, meta interface{}) (err error) { - mc := meta.(*management.Client) + /* + mc := meta.(*management.Client) - name := d.Get("name").(string) + name := d.Get("name").(string) - // Compute/set the description - description := d.Get("description").(string) - if description == "" { - description = name - } - - // Retrieve the needed details of the image - imageName, imageURL, osType, err := retrieveImageDetails(mc, d.Get("image").(string)) - if err != nil { - return err - } - - if imageURL == "" { - storage, ok := d.GetOk("storage") - if !ok { - return fmt.Errorf("When using a platform image, the 'storage' parameter is required") + // Compute/set the description + description := d.Get("description").(string) + if description == "" { + description = name } - 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 - if err := verifyParameters(d, osType); err != nil { - return err - } - - log.Printf("[DEBUG] Creating Cloud Service for instance: %s", name) - req, err := hostedservice.NewClient(*mc). - CreateHostedService( - name, - d.Get("location").(string), - d.Get("reverse_dns").(string), - name, - fmt.Sprintf("Cloud Service created automatically for instance %s", name), - ) - if err != nil { - return fmt.Errorf("Error creating Cloud Service for instance %s: %s", name, err) - } - - // Wait until the Cloud Service is created - if err := mc.WaitAsyncOperation(req); err != nil { - return fmt.Errorf( - "Error waiting for Cloud Service of instance %s to be created: %s", name, err) - } - - // Put in this defer here, so we are sure to cleanup already created parts - // when we exit with an error - defer func(mc *management.Client) { + // Retrieve the needed details of the image + imageName, imageURL, osType, err := retrieveImageDetails(mc, d.Get("image").(string)) if err != nil { - req, err := hostedservice.NewClient(*mc).DeleteHostedService(name, true) - if err != nil { - log.Printf("[DEBUG] Error cleaning up Cloud Service of instance %s: %s", name, 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") } - - // Wait until the Cloud Service is deleted - if err := mc.WaitAsyncOperation(req); err != nil { - log.Printf( - "[DEBUG] Error waiting for Cloud Service of instance %s to be deleted : %s", name, err) - } - } - }(mc) - - // Create a new role for the instance - role := vmutils.NewVmConfiguration(name, d.Get("size").(string)) - - log.Printf("[DEBUG] Configuring deployment from image...") - err = vmutils.ConfigureDeploymentFromPlatformImage( - &role, - imageName, - imageURL, - d.Get("image").(string), - ) - if err != nil { - return fmt.Errorf("Error configuring the deployment for %s: %s", name, err) - } - - if osType == linux { - // This is pretty ugly, but the Azure SDK leaves me no other choice... - if tp, ok := d.GetOk("ssh_key_thumbprint"); ok { - err = vmutils.ConfigureForLinux( - &role, - name, - d.Get("username").(string), - d.Get("password").(string), - tp.(string), - ) - } else { - err = vmutils.ConfigureForLinux( - &role, - name, - d.Get("username").(string), - d.Get("password").(string), - ) - } - if err != nil { - return fmt.Errorf("Error configuring %s for Linux: %s", name, err) + imageURL = fmt.Sprintf("http://%s.blob.core.windows.net/vhds/%s.vhd", storage, name) } - if d.Get("public_ssh").(bool) { - if err := vmutils.ConfigureWithPublicSSH(&role); err != nil { - return fmt.Errorf("Error configuring %s for public SSH: %s", name, err) - } + // Verify if we have all parameters required for the image OS type + if err := verifyParameters(d, osType); err != nil { + return err } - } - if osType == windows { - err = vmutils.ConfigureForWindows( - &role, + log.Printf("[DEBUG] Creating Cloud Service for instance: %s", name) + req, err := hostedservice.NewClient(*mc). + CreateHostedService( name, - d.Get("username").(string), - d.Get("password").(string), - d.Get("automatic_updates").(bool), - d.Get("time_zone").(string), + d.Get("location").(string), + d.Get("reverse_dns").(string), + name, + fmt.Sprintf("Cloud Service created automatically for instance %s", name), ) if err != nil { - return fmt.Errorf("Error configuring %s for Windows: %s", name, err) + return fmt.Errorf("Error creating Cloud Service for instance %s: %s", name, err) } - if d.Get("public_rdp").(bool) { - if err := vmutils.ConfigureWithPublicRDP(&role); err != nil { - return fmt.Errorf("Error configuring %s for public RDP: %s", name, err) - } + // Wait until the Cloud Service is created + if err := mc.WaitAsyncOperation(req); err != nil { + return fmt.Errorf( + "Error waiting for Cloud Service of instance %s to be created: %s", name, err) } - } - log.Printf("[DEBUG] Creating the new instance...") - req, err = virtualmachine.NewClient(*mc).CreateDeployment(role, name) - if err != nil { - return fmt.Errorf("Error creating instance %s: %s", name, err) - } - - log.Printf("[DEBUG] Waiting for the new instance to be created...") - if err := mc.WaitAsyncOperation(req); err != nil { - return fmt.Errorf( - "Error waiting for instance %s to be created: %s", name, err) - } - - /* - if v := d.Get("endpoint").(*schema.Set); v.Len() > 0 { - log.Printf("[DEBUG] Adding Endpoints to the Azure Virtual Machine...") - endpoints := make([]vmClient.InputEndpoint, v.Len()) - for i, v := range v.List() { - m := v.(map[string]interface{}) - endpoint := vmClient.InputEndpoint{} - endpoint.Name = m["name"].(string) - endpoint.Protocol = m["protocol"].(string) - endpoint.Port = m["port"].(int) - endpoint.LocalPort = m["local_port"].(int) - endpoints[i] = endpoint - } - - configSets := vmConfig.ConfigurationSets.ConfigurationSet - if len(configSets) == 0 { - return fmt.Errorf("Azure virtual machine does not have configuration sets") - } - for i := 0; i < len(configSets); i++ { - if configSets[i].ConfigurationSetType != "NetworkConfiguration" { - continue + // Put in this defer here, so we are sure to cleanup already created parts + // when we exit with an error + defer func(mc *management.Client) { + if err != nil { + req, err := hostedservice.NewClient(*mc).DeleteHostedService(name, true) + if err != nil { + log.Printf("[DEBUG] Error cleaning up Cloud Service of instance %s: %s", name, err) } - configSets[i].InputEndpoints.InputEndpoint = - append(configSets[i].InputEndpoints.InputEndpoint, endpoints...) + + // Wait until the Cloud Service is deleted + if err := mc.WaitAsyncOperation(req); err != nil { + log.Printf( + "[DEBUG] Error waiting for Cloud Service of instance %s to be deleted : %s", name, err) + } + } + }(mc) + + // Create a new role for the instance + role := vmutils.NewVmConfiguration(name, d.Get("size").(string)) + + log.Printf("[DEBUG] Configuring deployment from image...") + err = vmutils.ConfigureDeploymentFromPlatformImage( + &role, + imageName, + imageURL, + d.Get("image").(string), + ) + if err != nil { + return fmt.Errorf("Error configuring the deployment for %s: %s", name, err) + } + + if osType == linux { + // This is pretty ugly, but the Azure SDK leaves me no other choice... + if tp, ok := d.GetOk("ssh_key_thumbprint"); ok { + err = vmutils.ConfigureForLinux( + &role, + name, + d.Get("username").(string), + d.Get("password").(string), + tp.(string), + ) + } else { + err = vmutils.ConfigureForLinux( + &role, + name, + d.Get("username").(string), + d.Get("password").(string), + ) + } + if err != nil { + return fmt.Errorf("Error configuring %s for Linux: %s", name, err) } } + if osType == windows { + err = vmutils.ConfigureForWindows( + &role, + name, + d.Get("username").(string), + d.Get("password").(string), + d.Get("automatic_updates").(bool), + d.Get("time_zone").(string), + ) + if err != nil { + return fmt.Errorf("Error configuring %s for Windows: %s", name, err) + } + } + + if s := d.Get("endpoint").(*schema.Set); s.Len() > 0 { + for _, v := range s.List() { + m := v.(map[string]interface{}) + err := vmutils.ConfigureWithExternalPort( + &role, + m["name"].(string), + m["private_port"].(int), + m["public_port"].(int), + endpointProtocol(m["protocol"].(string)), + ) + if err != nil { + return fmt.Errorf( + "Error adding endpoint %s for instance %s: %s", m["name"].(string), name, err) + } + } + } + + err = vmutils.ConfigureForSubnet(&role, d.Get("subnet").(string)) + if err != nil { + return fmt.Errorf( + "Error adding role to subnet %s for instance %s: %s", d.Get("subnet").(string), name, err) + } + + options := &virtualmachine.CreateDeploymentOptions{ + Subnet: d.Get("subnet").(string), + VirtualNetworkName: d.Get("virtual_network").(string), + } + + log.Printf("[DEBUG] Creating the new instance...") + req, err = virtualmachine.NewClient(*mc).CreateDeployment(role, name, options) + if err != nil { + return fmt.Errorf("Error creating instance %s: %s", name, err) + } + + log.Printf("[DEBUG] Waiting for the new instance to be created...") + if err := mc.WaitAsyncOperation(req); err != nil { + return fmt.Errorf( + "Error waiting for instance %s to be created: %s", name, err) + } + + d.SetId(name) */ - - d.SetId(name) - return resourceAzureInstanceRead(d, meta) } @@ -349,37 +341,59 @@ func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error { d.Set("location", cs.Location) log.Printf("[DEBUG] Retrieving instance: %s", d.Id()) - wi, err := virtualmachine.NewClient(*mc).GetDeployment(d.Id(), d.Id()) + dpmt, err := virtualmachine.NewClient(*mc).GetDeployment(d.Id(), d.Id()) if err != nil { return fmt.Errorf("Error retrieving instance %s: %s", d.Id(), err) } - if len(wi.RoleList) == 0 { - return fmt.Errorf("Instance %s does not have VIP addresses", d.Id()) + if len(dpmt.RoleList) != 1 { + return fmt.Errorf( + "Instance %s has an unexpected number of roles: %d", d.Id(), len(dpmt.RoleList)) } - role := wi.RoleList[0] + d.Set("size", dpmt.RoleList[0].RoleSize) - d.Set("size", role.RoleSize) - - if len(wi.RoleInstanceList) == 0 { - return fmt.Errorf("Instance %s does not have IP addresses", d.Id()) + if len(dpmt.RoleInstanceList) != 1 { + return fmt.Errorf( + "Instance %s has an unexpected number of role instances %d", d.Id(), len(dpmt.RoleInstanceList)) } - d.Set("ip_address", wi.RoleInstanceList[0].IpAddress) + d.Set("ip_address", dpmt.RoleInstanceList[0].IpAddress) - if len(wi.VirtualIPs) == 0 { - return fmt.Errorf("Instance %s does not have VIP addresses", d.Id()) + if len(dpmt.RoleInstanceList[0].InstanceEndpoints) > 0 { + d.Set("vip_address", dpmt.RoleInstanceList[0].InstanceEndpoints[0].Vip) + } + + // Create a new set to hold all configured endpoints + endpoints := &schema.Set{ + F: resourceAzureEndpointHash, + } + + // Loop through all endpoints and add them to the set + for _, c := range *dpmt.RoleList[0].ConfigurationSets { + if c.ConfigurationSetType == virtualmachine.ConfigurationSetTypeNetwork { + for _, ep := range *c.InputEndpoints { + endpoint := map[string]interface{}{} + + // Update the values + endpoint["name"] = ep.Name + endpoint["protocol"] = string(ep.Protocol) + endpoint["public_port"] = ep.Port + endpoint["private_port"] = ep.LocalPort + endpoints.Add(endpoint) + } + + d.Set("endpoint", endpoints) + } } - d.Set("vip_address", wi.VirtualIPs[0].Address) connType := "ssh" - if role.OSVirtualHardDisk.OS == windows { + if dpmt.RoleList[0].OSVirtualHardDisk.OS == windows { connType = windows } // Set the connection info for any configured provisioners d.SetConnInfo(map[string]string{ "type": connType, - "host": wi.VirtualIPs[0].Address, + "host": dpmt.VirtualIPs[0].Address, "user": d.Get("username").(string), }) @@ -389,27 +403,65 @@ func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error { func resourceAzureInstanceUpdate(d *schema.ResourceData, meta interface{}) error { mc := meta.(*management.Client) + // First check if anything we can update changed, and if not just return + if !d.HasChange("size") && !d.HasChange("endpoint") { + return nil + } + + // Get the current role + role, err := virtualmachine.NewClient(*mc).GetRole(d.Id(), d.Id(), d.Id()) + if err != nil { + return fmt.Errorf("Error retrieving role of instance %s: %s", d.Id(), err) + } + + // Verify if we have all parameters required for the image OS type + if err := verifyParameters(d, role.OSVirtualHardDisk.OS); err != nil { + return err + } + if d.HasChange("size") { - role, err := virtualmachine.NewClient(*mc).GetRole(d.Id(), d.Id(), d.Id()) - if err != nil { - return fmt.Errorf("Error retrieving role of instance %s: %s", d.Id(), err) - } - role.RoleSize = d.Get("size").(string) - - req, err := virtualmachine.NewClient(*mc).UpdateRole(d.Id(), d.Id(), d.Id(), *role) - if err != nil { - return fmt.Errorf("Error updating role of instance %s: %s", d.Id(), err) - } - - if err := mc.WaitAsyncOperation(req); err != nil { - return fmt.Errorf( - "Error waiting for role of instance %s to be updated: %s", d.Id(), err) - } } if d.HasChange("endpoint") { + _, n := d.GetChange("endpoint") + // Delete the existing endpoints + for i, c := range *role.ConfigurationSets { + if c.ConfigurationSetType == virtualmachine.ConfigurationSetTypeNetwork { + c.InputEndpoints = nil + (*role.ConfigurationSets)[i] = c + } + } + + // And add the ones we still want + if s := n.(*schema.Set); s.Len() > 0 { + for _, v := range s.List() { + m := v.(map[string]interface{}) + err := vmutils.ConfigureWithExternalPort( + role, + m["name"].(string), + m["private_port"].(int), + m["public_port"].(int), + endpointProtocol(m["protocol"].(string)), + ) + if err != nil { + return fmt.Errorf( + "Error adding endpoint %s for instance %s: %s", m["name"].(string), d.Id(), err) + } + } + } + } + + // Update the adjusted role + req, err := virtualmachine.NewClient(*mc).UpdateRole(d.Id(), d.Id(), d.Id(), *role) + if err != nil { + return fmt.Errorf("Error updating role of instance %s: %s", d.Id(), err) + } + + if err := mc.WaitAsyncOperation(req); err != nil { + return fmt.Errorf( + "Error waiting for role of instance %s to be updated: %s", d.Id(), err) } return resourceAzureInstanceRead(d, meta) @@ -440,8 +492,8 @@ func resourceAzureEndpointHash(v interface{}) int { m := v.(map[string]interface{}) buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string))) - buf.WriteString(fmt.Sprintf("%d-", m["port"].(int))) - buf.WriteString(fmt.Sprintf("%d-", m["local_port"].(int))) + buf.WriteString(fmt.Sprintf("%d-", m["public_port"].(int))) + buf.WriteString(fmt.Sprintf("%d-", m["private_port"].(int))) return hashcode.String(buf.String()) } @@ -468,6 +520,14 @@ func retrieveImageDetails(mc *management.Client, label string) (string, string, label, strings.Join(labels, ",")) } +func endpointProtocol(p string) virtualmachine.InputEndpointProtocol { + if p == "tcp" { + return virtualmachine.InputEndpointProtocolTcp + } + + return virtualmachine.InputEndpointProtocolUdp +} + func verifyParameters(d *schema.ResourceData, osType string) error { if osType == linux { _, pass := d.GetOk("password") @@ -477,10 +537,6 @@ func verifyParameters(d *schema.ResourceData, osType string) error { return fmt.Errorf( "You must supply a 'password' and/or a 'ssh_key_thumbprint' when using a Linux image") } - - if key { - // check if it's a file of a string containing the key - } } if osType == windows { @@ -493,5 +549,22 @@ func verifyParameters(d *schema.ResourceData, osType string) error { } } + if _, ok := d.GetOk("subnet"); ok { + if _, ok := d.GetOk("virtual_network"); !ok { + return fmt.Errorf("You must also supply a 'virtual_network' when supplying a 'subnet'") + } + } + + if s := d.Get("endpoint").(*schema.Set); s.Len() > 0 { + for _, v := range s.List() { + protocol := v.(map[string]interface{})["protocol"].(string) + + if protocol != "tcp" && protocol != "udp" { + return fmt.Errorf( + "Invalid endpoint protocol %s! Valid options are 'tcp' and 'udp'.", protocol) + } + } + } + return nil } diff --git a/builtin/providers/azure/resource_azure_network.go b/builtin/providers/azure/resource_azure_network.go deleted file mode 100644 index 33d1d15a5..000000000 --- a/builtin/providers/azure/resource_azure_network.go +++ /dev/null @@ -1,51 +0,0 @@ -package azure - -import "github.com/hashicorp/terraform/helper/schema" - -func resourceAzureNetwork() *schema.Resource { - return &schema.Resource{ - Create: resourceAzureNetworkCreate, - Read: resourceAzureNetworkRead, - Update: resourceAzureNetworkUpdate, - Delete: resourceAzureNetworkDelete, - - Schema: map[string]*schema.Schema{ - "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "virtual": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - Default: true, - ForceNew: true, - }, - }, - } -} - -func resourceAzureNetworkCreate(d *schema.ResourceData, meta interface{}) (err error) { - //mc := meta.(*management.Client) - - return resourceAzureNetworkRead(d, meta) -} - -func resourceAzureNetworkRead(d *schema.ResourceData, meta interface{}) error { - //mc := meta.(*management.Client) - - return nil -} - -func resourceAzureNetworkUpdate(d *schema.ResourceData, meta interface{}) error { - //mc := meta.(*management.Client) - - return resourceAzureNetworkRead(d, meta) -} - -func resourceAzureNetworkDelete(d *schema.ResourceData, meta interface{}) error { - //mc := meta.(*management.Client) - - return nil -} diff --git a/builtin/providers/azure/resource_azure_security_group.go b/builtin/providers/azure/resource_azure_security_group.go new file mode 100644 index 000000000..f288fc0e5 --- /dev/null +++ b/builtin/providers/azure/resource_azure_security_group.go @@ -0,0 +1,304 @@ +package azure + +import ( + "fmt" + "log" + "strconv" + + "github.com/MSOpenTech/azure-sdk-for-go/management" + "github.com/MSOpenTech/azure-sdk-for-go/management/networksecuritygroup" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAzureSecurityGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceAzureSecurityGroupCreate, + Read: resourceAzureSecurityGroupRead, + Update: resourceAzureSecurityGroupUpdate, + Delete: resourceAzureSecurityGroupDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "label": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "subnet": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "location": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "rule": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "type": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "inbound", + }, + + "priority": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + + "action": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "allow", + }, + + "source_cidr": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "source_port": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "destination_cidr": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "destination_port": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "protocol": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "tcp", + }, + }, + }, + Set: resourceAzureSecurityGroupRuleHash, + }, + }, + } +} + +func resourceAzureSecurityGroupCreate(d *schema.ResourceData, meta interface{}) (err error) { + mc := meta.(*management.Client) + + name := d.Get("name").(string) + + req, err := networksecuritygroup.NewClient(*mc).CreateNetworkSecurityGroup( + name, + d.Get("label").(string), + d.Get("location").(string), + ) + if err != nil { + return fmt.Errorf("Error creating Network Security Group %s: %s", name, err) + } + + if err := mc.WaitAsyncOperation(req); err != nil { + return fmt.Errorf( + "Error waiting for Network Security Group %s to be created: %s", name, err) + } + + d.SetId(name) + + // Create all rules that are configured + if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 { + + // Create an empty schema.Set to hold all rules + rules := &schema.Set{ + F: resourceAzureSecurityGroupRuleHash, + } + + for _, rule := range rs.List() { + // Create a single rule + err := resourceAzureSecurityGroupRuleCreate(d, meta, rule.(map[string]interface{})) + + // We need to update this first to preserve the correct state + rules.Add(rule) + d.Set("rule", rules) + + if err != nil { + return err + } + } + } + + return resourceAzureSecurityGroupRead(d, meta) +} + +func resourceAzureSecurityGroupRuleCreate( + d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error { + mc := meta.(*management.Client) + + // Make sure all required parameters are there + if err := verifySecurityGroupRuleParams(rule); err != nil { + return err + } + + name := rule["name"].(string) + + // Create the rule + req, err := networksecuritygroup.NewClient(*mc).SetNetworkSecurityGroupRule(d.Id(), + &networksecuritygroup.Rule{ + Name: name, + Type: rule["type"].(string), + Priority: rule["priority"].(int), + Action: rule["action"].(string), + SourceAddressPrefix: rule["source_cidr"].(string), + SourcePortRange: rule["source_port"].(string), + DestinationAddressPrefix: rule["destination_cidr"].(string), + DestinationPortRange: rule["destination_port"].(string), + Protocol: rule["protocol"].(string), + }, + ) + if err != nil { + return fmt.Errorf("Error creating Network Security Group rule %s: %s", name, err) + } + + if err := mc.WaitAsyncOperation(req); err != nil { + return fmt.Errorf( + "Error waiting for Network Security Group rule %s to be created: %s", name, err) + } + + return nil +} + +func resourceAzureSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { + mc := meta.(*management.Client) + + sg, err := networksecuritygroup.NewClient(*mc).GetNetworkSecurityGroup(d.Id()) + if err != nil { + return fmt.Errorf("Error retrieving Network Security Group %s: %s", d.Id(), err) + } + + d.Set("label", sg.Label) + d.Set("location", sg.Location) + + return nil +} + +func resourceAzureSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error { + // Check if the rule set as a whole has changed + if d.HasChange("rule") { + o, n := d.GetChange("rule") + ors := o.(*schema.Set).Difference(n.(*schema.Set)) + nrs := n.(*schema.Set).Difference(o.(*schema.Set)) + + // Now first loop through all the old rules and delete any obsolete ones + for _, rule := range ors.List() { + // Delete the rule as it no longer exists in the config + err := resourceAzureSecurityGroupRuleDelete(d, meta, rule.(map[string]interface{})) + if err != nil { + return err + } + } + + // Make sure we save the state of the currently configured rules + rules := o.(*schema.Set).Intersection(n.(*schema.Set)) + d.Set("rule", rules) + + // Then loop through al the currently configured rules and create the new ones + for _, rule := range nrs.List() { + err := resourceAzureSecurityGroupRuleCreate(d, meta, rule.(map[string]interface{})) + + // We need to update this first to preserve the correct state + rules.Add(rule) + d.Set("rule", rules) + + if err != nil { + return err + } + } + } + + return resourceAzureSecurityGroupRead(d, meta) +} + +func resourceAzureSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error { + mc := meta.(*management.Client) + + log.Printf("[DEBUG] Deleting Network Security Group: %s", d.Id()) + req, err := networksecuritygroup.NewClient(*mc).DeleteNetworkSecurityGroup(d.Id()) + if err != nil { + return fmt.Errorf("Error deleting Network Security Group %s: %s", d.Id(), err) + } + + // Wait until the network security group is deleted + if err := mc.WaitAsyncOperation(req); err != nil { + return fmt.Errorf( + "Error waiting for Network Security Group %s to be deleted: %s", d.Id(), err) + } + + d.SetId("") + + return nil +} + +func resourceAzureSecurityGroupRuleDelete( + d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error { + mc := meta.(*management.Client) + + name := rule["name"].(string) + + // Delete the rule + req, err := networksecuritygroup.NewClient(*mc).DeleteNetworkSecurityGroupRule(d.Id(), name) + if err != nil { + return fmt.Errorf("Error deleting Network Security Group rule %s: %s", name, err) + } + + if err := mc.WaitAsyncOperation(req); err != nil { + return fmt.Errorf( + "Error waiting for Network Security Group rule %s to be deleted: %s", name, err) + } + + return nil +} + +func resourceAzureSecurityGroupRuleHash(v interface{}) int { + return 0 +} + +func verifySecurityGroupRuleParams(rule map[string]interface{}) error { + typ := rule["type"].(string) + if typ != "inbound" && typ != "outbound" { + return fmt.Errorf("Parameter type only accepts 'inbound' or 'outbound' as values") + } + + action := rule["action"].(string) + if action != "allow" && action != "deny" { + return fmt.Errorf("Parameter action only accepts 'allow' or 'deny' as values") + } + + protocol := rule["protocol"].(string) + if protocol != "tcp" && protocol != "udp" && protocol != "*" { + _, err := strconv.ParseInt(protocol, 0, 0) + if err != nil { + return fmt.Errorf( + "Parameter type only accepts 'tcp', 'udp' or '*' as values") + } + } + + return nil +} diff --git a/builtin/providers/azure/resource_disk_test.go b/builtin/providers/azure/resource_azure_security_group_test.go similarity index 100% rename from builtin/providers/azure/resource_disk_test.go rename to builtin/providers/azure/resource_azure_security_group_test.go diff --git a/builtin/providers/azure/resource_azure_virtual_network.go b/builtin/providers/azure/resource_azure_virtual_network.go new file mode 100644 index 000000000..0de456113 --- /dev/null +++ b/builtin/providers/azure/resource_azure_virtual_network.go @@ -0,0 +1,205 @@ +package azure + +import ( + "fmt" + "log" + "strings" + + "github.com/MSOpenTech/azure-sdk-for-go/management" + "github.com/MSOpenTech/azure-sdk-for-go/management/virtualnetwork" + "github.com/hashicorp/terraform/helper/schema" + "github.com/mitchellh/mapstructure" +) + +const ( + virtualNetworkRetrievalError = "Error retrieving Virtual Network Configuration: %s" +) + +func resourceAzureVirtualNetwork() *schema.Resource { + return &schema.Resource{ + Create: resourceAzureVirtualNetworkCreate, + Read: resourceAzureVirtualNetworkRead, + Update: resourceAzureVirtualNetworkUpdate, + Delete: resourceAzureVirtualNetworkDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "address_space": &schema.Schema{ + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "subnet": &schema.Schema{ + Type: schema.TypeMap, + Required: true, + }, + + "location": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceAzureVirtualNetworkCreate(d *schema.ResourceData, meta interface{}) error { + mc := meta.(*management.Client) + + name := d.Get("name").(string) + + nc, err := virtualnetwork.NewClient(*mc).GetVirtualNetworkConfiguration() + if err != nil { + if strings.Contains(err.Error(), "ResourceNotFound") { + nc = virtualnetwork.NetworkConfiguration{} + } else { + return fmt.Errorf(virtualNetworkRetrievalError, err) + } + } + + for _, n := range nc.Configuration.VirtualNetworkSites { + if n.Name == name { + return fmt.Errorf("Virtual Network %s already exists!", name) + } + } + + network, err := createVirtualNetwork(d) + if err != nil { + return err + } + + nc.Configuration.VirtualNetworkSites = append(nc.Configuration.VirtualNetworkSites, network) + + err = virtualnetwork.NewClient(*mc).SetVirtualNetworkConfiguration(nc) + if err != nil { + return fmt.Errorf("Error creating Virtual Network %s: %s", name, err) + } + + d.SetId(name) + + return resourceAzureVirtualNetworkRead(d, meta) +} + +func resourceAzureVirtualNetworkRead(d *schema.ResourceData, meta interface{}) error { + mc := meta.(*management.Client) + + nc, err := virtualnetwork.NewClient(*mc).GetVirtualNetworkConfiguration() + if err != nil { + return fmt.Errorf(virtualNetworkRetrievalError, err) + } + + for _, n := range nc.Configuration.VirtualNetworkSites { + if n.Name == d.Id() { + d.Set("address_space", n.AddressSpace.AddressPrefix) + d.Set("location", n.Location) + + subnets := map[string]interface{}{} + for _, s := range n.Subnets { + subnets[s.Name] = s.AddressPrefix + } + + d.Set("subnet", subnets) + + return nil + } + } + + log.Printf("[DEBUG] Virtual Network %s does no longer exist", d.Id()) + d.SetId("") + + return nil +} + +func resourceAzureVirtualNetworkUpdate(d *schema.ResourceData, meta interface{}) error { + mc := meta.(*management.Client) + + nc, err := virtualnetwork.NewClient(*mc).GetVirtualNetworkConfiguration() + if err != nil { + return fmt.Errorf(virtualNetworkRetrievalError, err) + } + + found := false + for i, n := range nc.Configuration.VirtualNetworkSites { + if n.Name == d.Id() { + network, err := createVirtualNetwork(d) + if err != nil { + return err + } + + nc.Configuration.VirtualNetworkSites[i] = network + + found = true + } + } + + if !found { + return fmt.Errorf("Virtual Network %s does not exists!", d.Id()) + } + + err = virtualnetwork.NewClient(*mc).SetVirtualNetworkConfiguration(nc) + if err != nil { + return fmt.Errorf("Error updating Virtual Network %s: %s", d.Id(), err) + } + + return resourceAzureVirtualNetworkRead(d, meta) +} + +func resourceAzureVirtualNetworkDelete(d *schema.ResourceData, meta interface{}) error { + mc := meta.(*management.Client) + + nc, err := virtualnetwork.NewClient(*mc).GetVirtualNetworkConfiguration() + if err != nil { + return fmt.Errorf(virtualNetworkRetrievalError, err) + } + + filtered := nc.Configuration.VirtualNetworkSites[:0] + for _, n := range nc.Configuration.VirtualNetworkSites { + if n.Name != d.Id() { + filtered = append(filtered, n) + } + } + + nc.Configuration.VirtualNetworkSites = filtered + + err = virtualnetwork.NewClient(*mc).SetVirtualNetworkConfiguration(nc) + if err != nil { + return fmt.Errorf("Error deleting Virtual Network %s: %s", d.Id(), err) + } + + d.SetId("") + + return nil +} + +func createVirtualNetwork(d *schema.ResourceData) (virtualnetwork.VirtualNetworkSite, error) { + var addressPrefix []string + err := mapstructure.WeakDecode(d.Get("address_space"), &addressPrefix) + if err != nil { + return virtualnetwork.VirtualNetworkSite{}, fmt.Errorf("Error decoding address_space: %s", err) + } + + addressSpace := virtualnetwork.AddressSpace{ + AddressPrefix: addressPrefix, + } + + subnets := []virtualnetwork.Subnet{} + for n, p := range d.Get("subnet").(map[string]interface{}) { + subnets = append(subnets, virtualnetwork.Subnet{ + Name: n, + AddressPrefix: p.(string), + }) + } + + return virtualnetwork.VirtualNetworkSite{ + Name: d.Get("name").(string), + Location: d.Get("location").(string), + AddressSpace: addressSpace, + Subnets: subnets, + }, nil +} diff --git a/builtin/providers/azure/resource_azure_virtual_network_test.go b/builtin/providers/azure/resource_azure_virtual_network_test.go new file mode 100644 index 000000000..6512f735e --- /dev/null +++ b/builtin/providers/azure/resource_azure_virtual_network_test.go @@ -0,0 +1 @@ +package azure From f8a56ad3d704fcbbe1554c59308fcdbce9444f9d Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Thu, 30 Apr 2015 17:12:05 +0200 Subject: [PATCH 05/91] Little refactoring and fixing some issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Starting to look pretty niceā€¦ --- builtin/providers/azure/config.go | 2 +- .../azure/resource_azure_instance.go | 10 ++-- .../azure/resource_azure_security_group.go | 46 ++++++++++++++----- .../azure/resource_azure_virtual_network.go | 4 +- 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/builtin/providers/azure/config.go b/builtin/providers/azure/config.go index ad4dc60c0..62e302a27 100644 --- a/builtin/providers/azure/config.go +++ b/builtin/providers/azure/config.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/MSOpenTech/azure-sdk-for-go/management" + "github.com/Azure/azure-sdk-for-go/management" ) // Config is the configuration structure used to instantiate a diff --git a/builtin/providers/azure/resource_azure_instance.go b/builtin/providers/azure/resource_azure_instance.go index 9fe45eee1..b3eb5c5af 100644 --- a/builtin/providers/azure/resource_azure_instance.go +++ b/builtin/providers/azure/resource_azure_instance.go @@ -6,11 +6,11 @@ import ( "log" "strings" - "github.com/MSOpenTech/azure-sdk-for-go/management" - "github.com/MSOpenTech/azure-sdk-for-go/management/hostedservice" - "github.com/MSOpenTech/azure-sdk-for-go/management/osimage" - "github.com/MSOpenTech/azure-sdk-for-go/management/virtualmachine" - "github.com/MSOpenTech/azure-sdk-for-go/management/vmutils" + "github.com/Azure/azure-sdk-for-go/management" + "github.com/Azure/azure-sdk-for-go/management/hostedservice" + "github.com/Azure/azure-sdk-for-go/management/osimage" + "github.com/Azure/azure-sdk-for-go/management/virtualmachine" + "github.com/Azure/azure-sdk-for-go/management/vmutils" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" ) diff --git a/builtin/providers/azure/resource_azure_security_group.go b/builtin/providers/azure/resource_azure_security_group.go index f288fc0e5..b6fe4c9a2 100644 --- a/builtin/providers/azure/resource_azure_security_group.go +++ b/builtin/providers/azure/resource_azure_security_group.go @@ -5,8 +5,8 @@ import ( "log" "strconv" - "github.com/MSOpenTech/azure-sdk-for-go/management" - "github.com/MSOpenTech/azure-sdk-for-go/management/networksecuritygroup" + "github.com/Azure/azure-sdk-for-go/management" + "github.com/Azure/azure-sdk-for-go/management/networksecuritygroup" "github.com/hashicorp/terraform/helper/schema" ) @@ -55,7 +55,7 @@ func resourceAzureSecurityGroup() *schema.Resource { "type": &schema.Schema{ Type: schema.TypeString, Optional: true, - Default: "inbound", + Default: "Inbound", }, "priority": &schema.Schema{ @@ -66,7 +66,7 @@ func resourceAzureSecurityGroup() *schema.Resource { "action": &schema.Schema{ Type: schema.TypeString, Optional: true, - Default: "allow", + Default: "Allow", }, "source_cidr": &schema.Schema{ @@ -92,7 +92,7 @@ func resourceAzureSecurityGroup() *schema.Resource { "protocol": &schema.Schema{ Type: schema.TypeString, Optional: true, - Default: "tcp", + Default: "TCP", }, }, }, @@ -196,6 +196,30 @@ func resourceAzureSecurityGroupRead(d *schema.ResourceData, meta interface{}) er d.Set("label", sg.Label) d.Set("location", sg.Location) + // Create an empty schema.Set to hold all rules + rules := &schema.Set{ + F: resourceAzureSecurityGroupRuleHash, + } + + for _, r := range sg.Rules { + if !r.IsDefault { + rule := map[string]interface{}{ + "name": r.Name, + "type": r.Type, + "priority": r.Priority, + "action": r.Action, + "source_cidr": r.SourceAddressPrefix, + "source_port": r.SourcePortRange, + "destination_cidr": r.DestinationAddressPrefix, + "destination_port": r.DestinationPortRange, + "protocol": r.Protocol, + } + rules.Add(rule) + } + } + + d.Set("rule", rules) + return nil } @@ -282,21 +306,21 @@ func resourceAzureSecurityGroupRuleHash(v interface{}) int { func verifySecurityGroupRuleParams(rule map[string]interface{}) error { typ := rule["type"].(string) - if typ != "inbound" && typ != "outbound" { - return fmt.Errorf("Parameter type only accepts 'inbound' or 'outbound' as values") + if typ != "Inbound" && typ != "Outbound" { + return fmt.Errorf("Parameter type only accepts 'Inbound' or 'Outbound' as values") } action := rule["action"].(string) - if action != "allow" && action != "deny" { - return fmt.Errorf("Parameter action only accepts 'allow' or 'deny' as values") + if action != "Allow" && action != "Deny" { + return fmt.Errorf("Parameter action only accepts 'Allow' or 'Deny' as values") } protocol := rule["protocol"].(string) - if protocol != "tcp" && protocol != "udp" && protocol != "*" { + if protocol != "TCP" && protocol != "UDP" && protocol != "*" { _, err := strconv.ParseInt(protocol, 0, 0) if err != nil { return fmt.Errorf( - "Parameter type only accepts 'tcp', 'udp' or '*' as values") + "Parameter type only accepts 'TCP', 'UDP' or '*' as values") } } diff --git a/builtin/providers/azure/resource_azure_virtual_network.go b/builtin/providers/azure/resource_azure_virtual_network.go index 0de456113..39053cf2d 100644 --- a/builtin/providers/azure/resource_azure_virtual_network.go +++ b/builtin/providers/azure/resource_azure_virtual_network.go @@ -5,8 +5,8 @@ import ( "log" "strings" - "github.com/MSOpenTech/azure-sdk-for-go/management" - "github.com/MSOpenTech/azure-sdk-for-go/management/virtualnetwork" + "github.com/Azure/azure-sdk-for-go/management" + "github.com/Azure/azure-sdk-for-go/management/virtualnetwork" "github.com/hashicorp/terraform/helper/schema" "github.com/mitchellh/mapstructure" ) From d45f8ac52af7dbed5e5c307bb4e1ee534b282859 Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Mon, 11 May 2015 18:32:30 +0200 Subject: [PATCH 06/91] Small update after changes in the Azure SDK --- .../azure/resource_azure_security_group.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/builtin/providers/azure/resource_azure_security_group.go b/builtin/providers/azure/resource_azure_security_group.go index b6fe4c9a2..9406e9309 100644 --- a/builtin/providers/azure/resource_azure_security_group.go +++ b/builtin/providers/azure/resource_azure_security_group.go @@ -161,16 +161,16 @@ func resourceAzureSecurityGroupRuleCreate( // Create the rule req, err := networksecuritygroup.NewClient(*mc).SetNetworkSecurityGroupRule(d.Id(), - &networksecuritygroup.Rule{ + networksecuritygroup.RuleRequest{ Name: name, - Type: rule["type"].(string), + Type: networksecuritygroup.RuleType(rule["type"].(string)), Priority: rule["priority"].(int), - Action: rule["action"].(string), + Action: networksecuritygroup.RuleAction(rule["action"].(string)), SourceAddressPrefix: rule["source_cidr"].(string), SourcePortRange: rule["source_port"].(string), DestinationAddressPrefix: rule["destination_cidr"].(string), DestinationPortRange: rule["destination_port"].(string), - Protocol: rule["protocol"].(string), + Protocol: networksecuritygroup.RuleProtocol(rule["protocol"].(string)), }, ) if err != nil { @@ -205,14 +205,14 @@ func resourceAzureSecurityGroupRead(d *schema.ResourceData, meta interface{}) er if !r.IsDefault { rule := map[string]interface{}{ "name": r.Name, - "type": r.Type, + "type": string(r.Type), "priority": r.Priority, - "action": r.Action, + "action": string(r.Action), "source_cidr": r.SourceAddressPrefix, "source_port": r.SourcePortRange, "destination_cidr": r.DestinationAddressPrefix, "destination_port": r.DestinationPortRange, - "protocol": r.Protocol, + "protocol": string(r.Protocol), } rules.Add(rule) } From 123cd9239c294821a3f40015bf2e954ab7cbbdb1 Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Tue, 12 May 2015 08:30:47 +0200 Subject: [PATCH 07/91] Updated to use forked azure-sdk-for-go package --- builtin/providers/azure/config.go | 2 +- .../azure/resource_azure_instance.go | 277 +++++++++--------- .../azure/resource_azure_security_group.go | 4 +- .../azure/resource_azure_virtual_network.go | 4 +- 4 files changed, 143 insertions(+), 144 deletions(-) diff --git a/builtin/providers/azure/config.go b/builtin/providers/azure/config.go index 62e302a27..280866399 100644 --- a/builtin/providers/azure/config.go +++ b/builtin/providers/azure/config.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/Azure/azure-sdk-for-go/management" + "github.com/svanharmelen/azure-sdk-for-go/management" ) // Config is the configuration structure used to instantiate a diff --git a/builtin/providers/azure/resource_azure_instance.go b/builtin/providers/azure/resource_azure_instance.go index b3eb5c5af..d0a4e1186 100644 --- a/builtin/providers/azure/resource_azure_instance.go +++ b/builtin/providers/azure/resource_azure_instance.go @@ -6,13 +6,13 @@ import ( "log" "strings" - "github.com/Azure/azure-sdk-for-go/management" - "github.com/Azure/azure-sdk-for-go/management/hostedservice" - "github.com/Azure/azure-sdk-for-go/management/osimage" - "github.com/Azure/azure-sdk-for-go/management/virtualmachine" - "github.com/Azure/azure-sdk-for-go/management/vmutils" "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" + "github.com/svanharmelen/azure-sdk-for-go/management" + "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/virtualmachine" + "github.com/svanharmelen/azure-sdk-for-go/management/vmutils" ) const ( @@ -166,165 +166,164 @@ func resourceAzureInstance() *schema.Resource { } func resourceAzureInstanceCreate(d *schema.ResourceData, meta interface{}) (err error) { - /* - mc := meta.(*management.Client) + mc := meta.(*management.Client) - name := d.Get("name").(string) + name := d.Get("name").(string) - // Compute/set the description - description := d.Get("description").(string) - if description == "" { - description = name + // Compute/set the description + description := d.Get("description").(string) + if description == "" { + description = name + } + + // Retrieve the needed details of the image + imageName, imageURL, osType, err := retrieveImageDetails(mc, d.Get("image").(string)) + if err != nil { + 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) + } - // Retrieve the needed details of the image - imageName, imageURL, osType, err := retrieveImageDetails(mc, d.Get("image").(string)) + // Verify if we have all parameters required for the image OS type + if err := verifyParameters(d, osType); err != nil { + return err + } + + log.Printf("[DEBUG] Creating Cloud Service for instance: %s", name) + req, err := hostedservice.NewClient(*mc). + CreateHostedService( + name, + d.Get("location").(string), + d.Get("reverse_dns").(string), + name, + fmt.Sprintf("Cloud Service created automatically for instance %s", name), + ) + if err != nil { + return fmt.Errorf("Error creating Cloud Service for instance %s: %s", name, err) + } + + // Wait until the Cloud Service is created + if err := mc.WaitAsyncOperation(req); err != nil { + return fmt.Errorf( + "Error waiting for Cloud Service of instance %s to be created: %s", name, err) + } + + // Put in this defer here, so we are sure to cleanup already created parts + // when we exit with an error + defer func(mc *management.Client) { if err != nil { - 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 - if err := verifyParameters(d, osType); err != nil { - return err - } - - log.Printf("[DEBUG] Creating Cloud Service for instance: %s", name) - req, err := hostedservice.NewClient(*mc). - CreateHostedService( - name, - d.Get("location").(string), - d.Get("reverse_dns").(string), - name, - fmt.Sprintf("Cloud Service created automatically for instance %s", name), - ) - if err != nil { - return fmt.Errorf("Error creating Cloud Service for instance %s: %s", name, err) - } - - // Wait until the Cloud Service is created - if err := mc.WaitAsyncOperation(req); err != nil { - return fmt.Errorf( - "Error waiting for Cloud Service of instance %s to be created: %s", name, err) - } - - // Put in this defer here, so we are sure to cleanup already created parts - // when we exit with an error - defer func(mc *management.Client) { + req, err := hostedservice.NewClient(*mc).DeleteHostedService(name, true) if err != nil { - req, err := hostedservice.NewClient(*mc).DeleteHostedService(name, true) - if err != nil { - log.Printf("[DEBUG] Error cleaning up Cloud Service of instance %s: %s", name, err) - } - - // Wait until the Cloud Service is deleted - if err := mc.WaitAsyncOperation(req); err != nil { - log.Printf( - "[DEBUG] Error waiting for Cloud Service of instance %s to be deleted : %s", name, err) - } + log.Printf("[DEBUG] Error cleaning up Cloud Service of instance %s: %s", name, err) } - }(mc) - // Create a new role for the instance - role := vmutils.NewVmConfiguration(name, d.Get("size").(string)) - - log.Printf("[DEBUG] Configuring deployment from image...") - err = vmutils.ConfigureDeploymentFromPlatformImage( - &role, - imageName, - imageURL, - d.Get("image").(string), - ) - if err != nil { - return fmt.Errorf("Error configuring the deployment for %s: %s", name, err) - } - - if osType == linux { - // This is pretty ugly, but the Azure SDK leaves me no other choice... - if tp, ok := d.GetOk("ssh_key_thumbprint"); ok { - err = vmutils.ConfigureForLinux( - &role, - name, - d.Get("username").(string), - d.Get("password").(string), - tp.(string), - ) - } else { - err = vmutils.ConfigureForLinux( - &role, - name, - d.Get("username").(string), - d.Get("password").(string), - ) - } - if err != nil { - return fmt.Errorf("Error configuring %s for Linux: %s", name, err) + // Wait until the Cloud Service is deleted + if err := mc.WaitAsyncOperation(req); err != nil { + log.Printf( + "[DEBUG] Error waiting for Cloud Service of instance %s to be deleted : %s", name, err) } } + }(mc) - if osType == windows { - err = vmutils.ConfigureForWindows( + // Create a new role for the instance + role := vmutils.NewVmConfiguration(name, d.Get("size").(string)) + + log.Printf("[DEBUG] Configuring deployment from image...") + err = vmutils.ConfigureDeploymentFromPlatformImage( + &role, + imageName, + imageURL, + d.Get("image").(string), + ) + if err != nil { + return fmt.Errorf("Error configuring the deployment for %s: %s", name, err) + } + + if osType == linux { + // This is pretty ugly, but the Azure SDK leaves me no other choice... + if tp, ok := d.GetOk("ssh_key_thumbprint"); ok { + err = vmutils.ConfigureForLinux( &role, name, d.Get("username").(string), d.Get("password").(string), - d.Get("automatic_updates").(bool), - d.Get("time_zone").(string), + tp.(string), + ) + } else { + err = vmutils.ConfigureForLinux( + &role, + name, + d.Get("username").(string), + d.Get("password").(string), + ) + } + if err != nil { + return fmt.Errorf("Error configuring %s for Linux: %s", name, err) + } + } + + if osType == windows { + err = vmutils.ConfigureForWindows( + &role, + name, + d.Get("username").(string), + d.Get("password").(string), + d.Get("automatic_updates").(bool), + d.Get("time_zone").(string), + ) + if err != nil { + return fmt.Errorf("Error configuring %s for Windows: %s", name, err) + } + } + + if s := d.Get("endpoint").(*schema.Set); s.Len() > 0 { + for _, v := range s.List() { + m := v.(map[string]interface{}) + err := vmutils.ConfigureWithExternalPort( + &role, + m["name"].(string), + m["private_port"].(int), + m["public_port"].(int), + endpointProtocol(m["protocol"].(string)), ) if err != nil { - return fmt.Errorf("Error configuring %s for Windows: %s", name, err) + return fmt.Errorf( + "Error adding endpoint %s for instance %s: %s", m["name"].(string), name, err) } } + } - if s := d.Get("endpoint").(*schema.Set); s.Len() > 0 { - for _, v := range s.List() { - m := v.(map[string]interface{}) - err := vmutils.ConfigureWithExternalPort( - &role, - m["name"].(string), - m["private_port"].(int), - m["public_port"].(int), - endpointProtocol(m["protocol"].(string)), - ) - if err != nil { - return fmt.Errorf( - "Error adding endpoint %s for instance %s: %s", m["name"].(string), name, err) - } - } - } + err = vmutils.ConfigureForSubnet(&role, d.Get("subnet").(string)) + if err != nil { + return fmt.Errorf( + "Error adding role to subnet %s for instance %s: %s", d.Get("subnet").(string), name, err) + } - err = vmutils.ConfigureForSubnet(&role, d.Get("subnet").(string)) - if err != nil { - return fmt.Errorf( - "Error adding role to subnet %s for instance %s: %s", d.Get("subnet").(string), name, err) - } + options := virtualmachine.CreateDeploymentOptions{ + Subnet: d.Get("subnet").(string), + VirtualNetworkName: d.Get("virtual_network").(string), + } - options := &virtualmachine.CreateDeploymentOptions{ - Subnet: d.Get("subnet").(string), - VirtualNetworkName: d.Get("virtual_network").(string), - } + log.Printf("[DEBUG] Creating the new instance...") + req, err = virtualmachine.NewClient(*mc).CreateDeployment(role, name, options) + if err != nil { + return fmt.Errorf("Error creating instance %s: %s", name, err) + } - log.Printf("[DEBUG] Creating the new instance...") - req, err = virtualmachine.NewClient(*mc).CreateDeployment(role, name, options) - if err != nil { - return fmt.Errorf("Error creating instance %s: %s", name, err) - } + log.Printf("[DEBUG] Waiting for the new instance to be created...") + if err := mc.WaitAsyncOperation(req); err != nil { + return fmt.Errorf( + "Error waiting for instance %s to be created: %s", name, err) + } - log.Printf("[DEBUG] Waiting for the new instance to be created...") - if err := mc.WaitAsyncOperation(req); err != nil { - return fmt.Errorf( - "Error waiting for instance %s to be created: %s", name, err) - } + d.SetId(name) - d.SetId(name) - */ return resourceAzureInstanceRead(d, meta) } diff --git a/builtin/providers/azure/resource_azure_security_group.go b/builtin/providers/azure/resource_azure_security_group.go index 9406e9309..97351a91a 100644 --- a/builtin/providers/azure/resource_azure_security_group.go +++ b/builtin/providers/azure/resource_azure_security_group.go @@ -5,9 +5,9 @@ import ( "log" "strconv" - "github.com/Azure/azure-sdk-for-go/management" - "github.com/Azure/azure-sdk-for-go/management/networksecuritygroup" "github.com/hashicorp/terraform/helper/schema" + "github.com/svanharmelen/azure-sdk-for-go/management" + "github.com/svanharmelen/azure-sdk-for-go/management/networksecuritygroup" ) func resourceAzureSecurityGroup() *schema.Resource { diff --git a/builtin/providers/azure/resource_azure_virtual_network.go b/builtin/providers/azure/resource_azure_virtual_network.go index 39053cf2d..ce636044d 100644 --- a/builtin/providers/azure/resource_azure_virtual_network.go +++ b/builtin/providers/azure/resource_azure_virtual_network.go @@ -5,10 +5,10 @@ import ( "log" "strings" - "github.com/Azure/azure-sdk-for-go/management" - "github.com/Azure/azure-sdk-for-go/management/virtualnetwork" "github.com/hashicorp/terraform/helper/schema" "github.com/mitchellh/mapstructure" + "github.com/svanharmelen/azure-sdk-for-go/management" + "github.com/svanharmelen/azure-sdk-for-go/management/virtualnetwork" ) const ( From ca1eb1917b84127d31e55334c8c9dea1c3f822c7 Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Mon, 18 May 2015 15:40:45 +0200 Subject: [PATCH 08/91] Adding docs and tweaking the provider --- .../azure/resource_azure_instance.go | 152 +++++++++++++----- .../azure/resource_azure_security_group.go | 25 ++- website/source/assets/stylesheets/_docs.scss | 16 +- .../docs/providers/azure/index.html.markdown | 43 +++++ .../docs/providers/azure/r/disk.html.markdown | 58 +++++++ .../providers/azure/r/instance.html.markdown | 59 +++++++ .../providers/azure/r/security_group.markdown | 69 ++++++++ .../azure/r/virtual_network.html.markdown | 54 +++++++ website/source/layouts/azure.erb | 38 +++++ website/source/layouts/docs.erb | 10 +- 10 files changed, 467 insertions(+), 57 deletions(-) create mode 100644 website/source/docs/providers/azure/index.html.markdown create mode 100644 website/source/docs/providers/azure/r/disk.html.markdown create mode 100644 website/source/docs/providers/azure/r/instance.html.markdown create mode 100644 website/source/docs/providers/azure/r/security_group.markdown create mode 100644 website/source/docs/providers/azure/r/virtual_network.html.markdown create mode 100644 website/source/layouts/azure.erb diff --git a/builtin/providers/azure/resource_azure_instance.go b/builtin/providers/azure/resource_azure_instance.go index d0a4e1186..dd1752477 100644 --- a/builtin/providers/azure/resource_azure_instance.go +++ b/builtin/providers/azure/resource_azure_instance.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "log" - "strings" "github.com/hashicorp/terraform/helper/hashcode" "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/osimage" "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" ) @@ -55,6 +55,7 @@ func resourceAzureInstance() *schema.Resource { "subnet": &schema.Schema{ Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, }, @@ -142,14 +143,10 @@ func resourceAzureInstance() *schema.Resource { Set: resourceAzureEndpointHash, }, - "security_groups": &schema.Schema{ - Type: schema.TypeSet, + "security_group": &schema.Schema{ + Type: schema.TypeString, Optional: true, Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: func(v interface{}) int { - return hashcode.String(v.(string)) - }, }, "ip_address": &schema.Schema{ @@ -177,19 +174,11 @@ func resourceAzureInstanceCreate(d *schema.ResourceData, meta interface{}) (err } // 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 { 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 if err := verifyParameters(d, osType); err != nil { return err @@ -299,14 +288,23 @@ func resourceAzureInstanceCreate(d *schema.ResourceData, meta interface{}) (err } } - err = vmutils.ConfigureForSubnet(&role, d.Get("subnet").(string)) - if err != nil { - return fmt.Errorf( - "Error adding role to subnet %s for instance %s: %s", d.Get("subnet").(string), name, err) + if subnet, ok := d.GetOk("subnet"); ok { + err = vmutils.ConfigureWithSubnet(&role, subnet.(string)) + if err != nil { + 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{ - Subnet: d.Get("subnet").(string), VirtualNetworkName: d.Get("virtual_network").(string), } @@ -349,11 +347,14 @@ func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error { return fmt.Errorf( "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) if len(dpmt.RoleInstanceList) != 1 { 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) @@ -361,15 +362,16 @@ func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error { d.Set("vip_address", dpmt.RoleInstanceList[0].InstanceEndpoints[0].Vip) } - // Create a new set to hold all configured endpoints - endpoints := &schema.Set{ - F: resourceAzureEndpointHash, - } - - // Loop through all endpoints and add them to the set - for _, c := range *dpmt.RoleList[0].ConfigurationSets { + // Find the network configuration set + for _, c := range dpmt.RoleList[0].ConfigurationSets { 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{}{} // Update the values @@ -379,8 +381,22 @@ func resourceAzureInstanceRead(d *schema.ResourceData, meta interface{}) error { endpoint["private_port"] = ep.LocalPort endpoints.Add(endpoint) } - 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) // 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 } @@ -426,10 +442,10 @@ func resourceAzureInstanceUpdate(d *schema.ResourceData, meta interface{}) error _, n := d.GetChange("endpoint") // Delete the existing endpoints - for i, c := range *role.ConfigurationSets { + for i, c := range role.ConfigurationSets { if c.ConfigurationSetType == virtualmachine.ConfigurationSetTypeNetwork { 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 req, err := virtualmachine.NewClient(*mc).UpdateRole(d.Id(), d.Id(), d.Id(), *role) if err != nil { @@ -497,26 +522,65 @@ func resourceAzureEndpointHash(v interface{}) int { 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() + 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 { return "", "", "", fmt.Errorf("Error retrieving image details: %s", err) } - var labels []string for _, img := range imgs { - if img.Label == label { - if img.OS != linux && img.OS != windows { - return "", "", "", fmt.Errorf("Unsupported image OS: %s", img.OS) + if img.Name == name { + if img.OSDiskConfiguration.OS != linux && img.OSDiskConfiguration.OS != windows { + 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 "", "", "", - fmt.Errorf("Could not find image with label '%s', available labels are: %s", - label, strings.Join(labels, ",")) + return "", "", "", fmt.Errorf("Could not find image with name '%s'", name) } func endpointProtocol(p string) virtualmachine.InputEndpointProtocol { diff --git a/builtin/providers/azure/resource_azure_security_group.go b/builtin/providers/azure/resource_azure_security_group.go index 97351a91a..cbac36a3e 100644 --- a/builtin/providers/azure/resource_azure_security_group.go +++ b/builtin/providers/azure/resource_azure_security_group.go @@ -1,10 +1,12 @@ package azure import ( + "bytes" "fmt" "log" "strconv" + "github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/schema" "github.com/svanharmelen/azure-sdk-for-go/management" "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) + // Compute/set the label + label := d.Get("label").(string) + if label == "" { + label = name + } + req, err := networksecuritygroup.NewClient(*mc).CreateNetworkSecurityGroup( name, - d.Get("label").(string), + label, d.Get("location").(string), ) if err != nil { @@ -301,7 +309,20 @@ func resourceAzureSecurityGroupRuleDelete( } 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 { diff --git a/website/source/assets/stylesheets/_docs.scss b/website/source/assets/stylesheets/_docs.scss index 48a92b0a7..0cf800715 100755 --- a/website/source/assets/stylesheets/_docs.scss +++ b/website/source/assets/stylesheets/_docs.scss @@ -7,22 +7,23 @@ body.page-sub{ } body.layout-atlas, -body.layout-consul, -body.layout-dnsimple, -body.layout-dme, -body.layout-docker, +body.layout-aws, +body.layout-azure, body.layout-cloudflare, body.layout-cloudstack, +body.layout-consul, +body.layout-digitalocean, +body.layout-dme, +body.layout-dnsimple, +body.layout-docker, body.layout-google, body.layout-heroku, body.layout-mailgun, body.layout-openstack, body.layout-template, -body.layout-digitalocean, -body.layout-aws, body.layout-docs, -body.layout-inner, body.layout-downloads, +body.layout-inner, body.layout-intro{ background: $light-black image-url('sidebar-wire.png') left 62px no-repeat; @@ -287,4 +288,3 @@ body.layout-intro{ } } } - diff --git a/website/source/docs/providers/azure/index.html.markdown b/website/source/docs/providers/azure/index.html.markdown new file mode 100644 index 000000000..fd4081a67 --- /dev/null +++ b/website/source/docs/providers/azure/index.html.markdown @@ -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. diff --git a/website/source/docs/providers/azure/r/disk.html.markdown b/website/source/docs/providers/azure/r/disk.html.markdown new file mode 100644 index 000000000..53ae73656 --- /dev/null +++ b/website/source/docs/providers/azure/r/disk.html.markdown @@ -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. diff --git a/website/source/docs/providers/azure/r/instance.html.markdown b/website/source/docs/providers/azure/r/instance.html.markdown new file mode 100644 index 000000000..8779494e4 --- /dev/null +++ b/website/source/docs/providers/azure/r/instance.html.markdown @@ -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. diff --git a/website/source/docs/providers/azure/r/security_group.markdown b/website/source/docs/providers/azure/r/security_group.markdown new file mode 100644 index 000000000..f82b8f446 --- /dev/null +++ b/website/source/docs/providers/azure/r/security_group.markdown @@ -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. diff --git a/website/source/docs/providers/azure/r/virtual_network.html.markdown b/website/source/docs/providers/azure/r/virtual_network.html.markdown new file mode 100644 index 000000000..9819a8ce3 --- /dev/null +++ b/website/source/docs/providers/azure/r/virtual_network.html.markdown @@ -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. diff --git a/website/source/layouts/azure.erb b/website/source/layouts/azure.erb new file mode 100644 index 000000000..9d60e3f90 --- /dev/null +++ b/website/source/layouts/azure.erb @@ -0,0 +1,38 @@ +<% wrap_layout :inner do %> + <% content_for :sidebar do %> + + <% end %> + + <%= yield %> +<% end %> diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index dd60cad04..dbbf87b8a 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -119,19 +119,23 @@