github.com/skyscape-cloud-services/terraform@v0.9.2-0.20170609144644-7ece028a1747/builtin/providers/azurerm/resourceid.go (about)

     1  package azurerm
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"strings"
     7  )
     8  
     9  // ResourceID represents a parsed long-form Azure Resource Manager ID
    10  // with the Subscription ID, Resource Group and the Provider as top-
    11  // level fields, and other key-value pairs available via a map in the
    12  // Path field.
    13  type ResourceID struct {
    14  	SubscriptionID string
    15  	ResourceGroup  string
    16  	Provider       string
    17  	Path           map[string]string
    18  }
    19  
    20  // parseAzureResourceID converts a long-form Azure Resource Manager ID
    21  // into a ResourceID. We make assumptions about the structure of URLs,
    22  // which is obviously not good, but the best thing available given the
    23  // SDK.
    24  func parseAzureResourceID(id string) (*ResourceID, error) {
    25  	idURL, err := url.ParseRequestURI(id)
    26  	if err != nil {
    27  		return nil, fmt.Errorf("Cannot parse Azure Id: %s", err)
    28  	}
    29  
    30  	path := idURL.Path
    31  
    32  	path = strings.TrimSpace(path)
    33  	if strings.HasPrefix(path, "/") {
    34  		path = path[1:]
    35  	}
    36  
    37  	if strings.HasSuffix(path, "/") {
    38  		path = path[:len(path)-1]
    39  	}
    40  
    41  	components := strings.Split(path, "/")
    42  
    43  	// We should have an even number of key-value pairs.
    44  	if len(components)%2 != 0 {
    45  		return nil, fmt.Errorf("The number of path segments is not divisible by 2 in %q", path)
    46  	}
    47  
    48  	var subscriptionID string
    49  
    50  	// Put the constituent key-value pairs into a map
    51  	componentMap := make(map[string]string, len(components)/2)
    52  	for current := 0; current < len(components); current += 2 {
    53  		key := components[current]
    54  		value := components[current+1]
    55  
    56  		// Check key/value for empty strings.
    57  		if key == "" || value == "" {
    58  			return nil, fmt.Errorf("Key/Value cannot be empty strings. Key: '%s', Value: '%s'", key, value)
    59  		}
    60  
    61  		// Catch the subscriptionID before it can be overwritten by another "subscriptions"
    62  		// value in the ID which is the case for the Service Bus subscription resource
    63  		if key == "subscriptions" && subscriptionID == "" {
    64  			subscriptionID = value
    65  		} else {
    66  			componentMap[key] = value
    67  		}
    68  	}
    69  
    70  	// Build up a ResourceID from the map
    71  	idObj := &ResourceID{}
    72  	idObj.Path = componentMap
    73  
    74  	if subscriptionID != "" {
    75  		idObj.SubscriptionID = subscriptionID
    76  	} else {
    77  		return nil, fmt.Errorf("No subscription ID found in: %q", path)
    78  	}
    79  
    80  	if resourceGroup, ok := componentMap["resourceGroups"]; ok {
    81  		idObj.ResourceGroup = resourceGroup
    82  		delete(componentMap, "resourceGroups")
    83  	} else {
    84  		// Some Azure APIs are weird and provide things in lower case...
    85  		// However it's not clear whether the casing of other elements in the URI
    86  		// matter, so we explicitly look for that case here.
    87  		if resourceGroup, ok := componentMap["resourcegroups"]; ok {
    88  			idObj.ResourceGroup = resourceGroup
    89  			delete(componentMap, "resourcegroups")
    90  		} else {
    91  			return nil, fmt.Errorf("No resource group name found in: %q", path)
    92  		}
    93  	}
    94  
    95  	// It is OK not to have a provider in the case of a resource group
    96  	if provider, ok := componentMap["providers"]; ok {
    97  		idObj.Provider = provider
    98  		delete(componentMap, "providers")
    99  	}
   100  
   101  	return idObj, nil
   102  }
   103  
   104  func composeAzureResourceID(idObj *ResourceID) (id string, err error) {
   105  	if idObj.SubscriptionID == "" || idObj.ResourceGroup == "" {
   106  		return "", fmt.Errorf("SubscriptionID and ResourceGroup cannot be empty")
   107  	}
   108  
   109  	id = fmt.Sprintf("/subscriptions/%s/resourceGroups/%s", idObj.SubscriptionID, idObj.ResourceGroup)
   110  
   111  	if idObj.Provider != "" {
   112  		if len(idObj.Path) < 1 {
   113  			return "", fmt.Errorf("ResourceID.Path should have at least one item when ResourceID.Provider is specified")
   114  		}
   115  
   116  		id += fmt.Sprintf("/providers/%s", idObj.Provider)
   117  
   118  		for k, v := range idObj.Path {
   119  			if k == "" || v == "" {
   120  				return "", fmt.Errorf("ResourceID.Path cannot contain empty strings")
   121  			}
   122  			id += fmt.Sprintf("/%s/%s", k, v)
   123  		}
   124  	}
   125  
   126  	return
   127  }
   128  
   129  func parseNetworkSecurityGroupName(networkSecurityGroupId string) (string, error) {
   130  	id, err := parseAzureResourceID(networkSecurityGroupId)
   131  	if err != nil {
   132  		return "", fmt.Errorf("[ERROR] Unable to Parse Network Security Group ID '%s': %+v", networkSecurityGroupId, err)
   133  	}
   134  
   135  	return id.Path["networkSecurityGroups"], nil
   136  }
   137  
   138  func parseRouteTableName(routeTableId string) (string, error) {
   139  	id, err := parseAzureResourceID(routeTableId)
   140  	if err != nil {
   141  		return "", fmt.Errorf("[ERROR] Unable to parse Route Table ID '%s': %+v", routeTableId, err)
   142  	}
   143  
   144  	return id.Path["routeTables"], nil
   145  }