vendor: add github.com/Azure/azure-sdk-for-go and dependencies

This commit is contained in:
Nick Craig-Wood
2017-08-04 22:54:27 +01:00
parent 132f71d504
commit 91cfcc21ff
958 changed files with 320589 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
# Azure Service Management packages for Go
The `github.com/Azure/azure-sdk-for-go/management` packages are used to perform operations using the Azure Service Management (ASM), aka classic deployment model. Read more about [Azure Resource Manager vs. classic deployment](https://azure.microsoft.com/documentation/articles/resource-manager-deployment-model/). Packages for Azure Resource Manager are in the [arm](../arm) folder.
## First a Sidenote: Authentication and the Azure Service Manager
The client currently supports authentication to the Service Management
API with certificates or Azure `.publishSettings` file. You can
download the `.publishSettings` file for your subscriptions
[here](https://manage.windowsazure.com/publishsettings).
@@ -0,0 +1,131 @@
package affinitygroup
import (
"encoding/base64"
"encoding/xml"
"fmt"
"github.com/Azure/azure-sdk-for-go/management"
)
const (
azureCreateAffinityGroupURL = "/affinitygroups"
azureGetAffinityGroupURL = "/affinitygroups/%s"
azureListAffinityGroupsURL = "/affinitygroups"
azureUpdateAffinityGroupURL = "/affinitygroups/%s"
azureDeleteAffinityGroupURL = "/affinitygroups/%s"
errParameterNotSpecified = "Parameter %s not specified."
)
// AffinityGroupClient simply contains a management.Client and has
// methods for doing all affinity group-related API calls to Azure.
type AffinityGroupClient struct {
mgmtClient management.Client
}
// NewClient returns an AffinityGroupClient with the given management.Client.
func NewClient(mgmtClient management.Client) AffinityGroupClient {
return AffinityGroupClient{mgmtClient}
}
// CreateAffinityGroup creates a new affinity group.
//
// https://msdn.microsoft.com/en-us/library/azure/gg715317.aspx
func (c AffinityGroupClient) CreateAffinityGroup(params CreateAffinityGroupParams) error {
params.Label = encodeLabel(params.Label)
req, err := xml.Marshal(params)
if err != nil {
return err
}
_, err = c.mgmtClient.SendAzurePostRequest(azureCreateAffinityGroupURL, req)
return err
}
// GetAffinityGroup returns the system properties that are associated with the
// specified affinity group.
//
// https://msdn.microsoft.com/en-us/library/azure/ee460789.aspx
func (c AffinityGroupClient) GetAffinityGroup(name string) (AffinityGroup, error) {
var affgroup AffinityGroup
if name == "" {
return affgroup, fmt.Errorf(errParameterNotSpecified, "name")
}
url := fmt.Sprintf(azureGetAffinityGroupURL, name)
resp, err := c.mgmtClient.SendAzureGetRequest(url)
if err != nil {
return affgroup, err
}
err = xml.Unmarshal(resp, &affgroup)
affgroup.Label = decodeLabel(affgroup.Label)
return affgroup, err
}
// ListAffinityGroups lists the affinity groups off Azure.
//
// https://msdn.microsoft.com/en-us/library/azure/ee460797.aspx
func (c AffinityGroupClient) ListAffinityGroups() (ListAffinityGroupsResponse, error) {
var affinitygroups ListAffinityGroupsResponse
resp, err := c.mgmtClient.SendAzureGetRequest(azureListAffinityGroupsURL)
if err != nil {
return affinitygroups, err
}
err = xml.Unmarshal(resp, &affinitygroups)
for i, grp := range affinitygroups.AffinityGroups {
affinitygroups.AffinityGroups[i].Label = decodeLabel(grp.Label)
}
return affinitygroups, err
}
// UpdateAffinityGroup updates the label or description for an the group.
//
// https://msdn.microsoft.com/en-us/library/azure/gg715316.aspx
func (c AffinityGroupClient) UpdateAffinityGroup(name string, params UpdateAffinityGroupParams) error {
if name == "" {
return fmt.Errorf(errParameterNotSpecified, "name")
}
params.Label = encodeLabel(params.Label)
req, err := xml.Marshal(params)
if err != nil {
return err
}
url := fmt.Sprintf(azureUpdateAffinityGroupURL, name)
_, err = c.mgmtClient.SendAzurePutRequest(url, "text/xml", req)
return err
}
// DeleteAffinityGroup deletes the given affinity group.
//
// https://msdn.microsoft.com/en-us/library/azure/gg715314.aspx
func (c AffinityGroupClient) DeleteAffinityGroup(name string) error {
if name == "" {
return fmt.Errorf(errParameterNotSpecified, name)
}
url := fmt.Sprintf(azureDeleteAffinityGroupURL, name)
_, err := c.mgmtClient.SendAzureDeleteRequest(url)
return err
}
// encodeLabel is a helper function which encodes the given string
// to the base64 string which will be sent to Azure as a Label.
func encodeLabel(label string) string {
return base64.StdEncoding.EncodeToString([]byte(label))
}
// decodeLabel is a helper function which decodes the base64 encoded
// label received from Azure into standard encoding.
func decodeLabel(label string) string {
res, _ := base64.StdEncoding.DecodeString(label)
return string(res)
}
@@ -0,0 +1,80 @@
package affinitygroup
import (
"encoding/xml"
)
// CreateAffinityGroupParams respresents the set of parameters required for
// creating an affinity group creation request to Azure.
//
// https://msdn.microsoft.com/en-us/library/azure/gg715317.aspx
type CreateAffinityGroupParams struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure CreateAffinityGroup"`
Name string
Label string
Description string `xml:",omitempty"`
Location string
}
// HostedService is a struct containing details about a hosted service that is
// part of an affinity group on Azure.
type HostedService struct {
URL string `xml:"Url"`
ServiceName string
}
// StorageService is a struct containing details about a storage service that is
// part of an affinity group on Azure.
type StorageService struct {
URL string `xml:"Url"`
ServiceName string
}
// AffinityGroup respresents the properties of an affinity group on Azure.
//
// https://msdn.microsoft.com/en-us/library/azure/ee460789.aspx
type AffinityGroup struct {
Name string
Label string
Description string
Location string
HostedServices []HostedService
StorageServices []StorageService
Capabilities []string
}
// ComputeCapabilities represents the sets of capabilities of an affinity group
// obtained from an affinity group list call to Azure.
type ComputeCapabilities struct {
VirtualMachineRoleSizes []string
WebWorkerRoleSizes []string
}
// AffinityGroupListResponse represents the properties obtained for each
// affinity group listed off Azure.
//
// https://msdn.microsoft.com/en-us/library/azure/ee460797.aspx
type AffinityGroupListResponse struct {
Name string
Label string
Description string
Location string
Capabilities []string
ComputeCapabilities ComputeCapabilities
}
// ListAffinityGroupsResponse contains all the affinity groups obtained from a
// call to the Azure API to list all affinity groups.
type ListAffinityGroupsResponse struct {
AffinityGroups []AffinityGroupListResponse `xml:"AffinityGroup"`
}
// UpdateAffinityGroupParams if the set of parameters required to update an
// affinity group on Azure.
//
// https://msdn.microsoft.com/en-us/library/azure/gg715316.aspx
type UpdateAffinityGroupParams struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure UpdateAffinityGroup"`
Label string `xml:",omitempty"`
Description string `xml:",omitempty"`
}
+152
View File
@@ -0,0 +1,152 @@
// Package management provides the main API client to construct other clients
// and make requests to the Microsoft Azure Service Management REST API.
package management
import (
"errors"
"fmt"
"runtime"
"time"
)
var (
DefaultUserAgent = userAgent()
)
const (
DefaultAzureManagementURL = "https://management.core.windows.net"
DefaultOperationPollInterval = time.Second * 30
DefaultAPIVersion = "2014-10-01"
errPublishSettingsConfiguration = "PublishSettingsFilePath is set. Consequently ManagementCertificatePath and SubscriptionId must not be set."
errManagementCertificateConfiguration = "Both ManagementCertificatePath and SubscriptionId should be set, and PublishSettingsFilePath must not be set."
errParamNotSpecified = "Parameter %s is not specified."
)
type client struct {
publishSettings publishSettings
config ClientConfig
}
// Client is the base Azure Service Management API client instance that
// can be used to construct client instances for various services.
type Client interface {
// SendAzureGetRequest sends a request to the management API using the HTTP GET method
// and returns the response body or an error.
SendAzureGetRequest(url string) ([]byte, error)
// SendAzurePostRequest sends a request to the management API using the HTTP POST method
// and returns the request ID or an error.
SendAzurePostRequest(url string, data []byte) (OperationID, error)
// SendAzurePostRequestWithReturnedResponse sends a request to the management API using
// the HTTP POST method and returns the response body or an error.
SendAzurePostRequestWithReturnedResponse(url string, data []byte) ([]byte, error)
// SendAzurePutRequest sends a request to the management API using the HTTP PUT method
// and returns the request ID or an error. The content type can be specified, however
// if an empty string is passed, the default of "application/xml" will be used.
SendAzurePutRequest(url, contentType string, data []byte) (OperationID, error)
// SendAzureDeleteRequest sends a request to the management API using the HTTP DELETE method
// and returns the request ID or an error.
SendAzureDeleteRequest(url string) (OperationID, error)
// GetOperationStatus gets the status of operation with given Operation ID.
// WaitForOperation utility method can be used for polling for operation status.
GetOperationStatus(operationID OperationID) (GetOperationStatusResponse, error)
// WaitForOperation polls the Azure API for given operation ID indefinitely
// until the operation is completed with either success or failure.
// It is meant to be used for waiting for the result of the methods that
// return an OperationID value (meaning a long running operation has started).
//
// Cancellation of the polling loop (for instance, timing out) is done through
// cancel channel. If the user does not want to cancel, a nil chan can be provided.
// To cancel the method, it is recommended to close the channel provided to this
// method.
//
// If the operation was not successful or cancelling is signaled, an error
// is returned.
WaitForOperation(operationID OperationID, cancel chan struct{}) error
}
// ClientConfig provides a configuration for use by a Client.
type ClientConfig struct {
ManagementURL string
OperationPollInterval time.Duration
UserAgent string
APIVersion string
}
// NewAnonymousClient creates a new azure.Client with no credentials set.
func NewAnonymousClient() Client {
return client{}
}
// DefaultConfig returns the default client configuration used to construct
// a client. This value can be used to make modifications on the default API
// configuration.
func DefaultConfig() ClientConfig {
return ClientConfig{
ManagementURL: DefaultAzureManagementURL,
OperationPollInterval: DefaultOperationPollInterval,
APIVersion: DefaultAPIVersion,
UserAgent: DefaultUserAgent,
}
}
// NewClient creates a new Client using the given subscription ID and
// management certificate.
func NewClient(subscriptionID string, managementCert []byte) (Client, error) {
return NewClientFromConfig(subscriptionID, managementCert, DefaultConfig())
}
// NewClientFromConfig creates a new Client using a given ClientConfig.
func NewClientFromConfig(subscriptionID string, managementCert []byte, config ClientConfig) (Client, error) {
return makeClient(subscriptionID, managementCert, config)
}
func makeClient(subscriptionID string, managementCert []byte, config ClientConfig) (Client, error) {
var c client
if subscriptionID == "" {
return c, errors.New("azure: subscription ID required")
}
if len(managementCert) == 0 {
return c, errors.New("azure: management certificate required")
}
publishSettings := publishSettings{
SubscriptionID: subscriptionID,
SubscriptionCert: managementCert,
SubscriptionKey: managementCert,
}
// Validate client configuration
switch {
case config.ManagementURL == "":
return c, errors.New("azure: base URL required")
case config.OperationPollInterval <= 0:
return c, errors.New("azure: operation polling interval must be a positive duration")
case config.APIVersion == "":
return c, errors.New("azure: client configuration must specify an API version")
case config.UserAgent == "":
config.UserAgent = DefaultUserAgent
}
return client{
publishSettings: publishSettings,
config: config,
}, nil
}
func userAgent() string {
return fmt.Sprintf("Go/%s (%s-%s) Azure-SDK-For-Go/%s asm/%s",
runtime.Version(),
runtime.GOARCH,
runtime.GOOS,
sdkVersion,
DefaultAPIVersion)
}
+36
View File
@@ -0,0 +1,36 @@
package management
import (
"encoding/xml"
"fmt"
)
// AzureError represents an error returned by the management API. It has an error
// code (for example, ResourceNotFound) and a descriptive message.
type AzureError struct {
Code string
Message string
}
//Error implements the error interface for the AzureError type.
func (e AzureError) Error() string {
return fmt.Sprintf("Error response from Azure. Code: %s, Message: %s", e.Code, e.Message)
}
// IsResourceNotFoundError returns true if the provided error is an AzureError
// reporting that a given resource has not been found.
func IsResourceNotFoundError(err error) bool {
azureErr, ok := err.(AzureError)
return ok && azureErr.Code == "ResourceNotFound"
}
// getAzureError converts an error response body into an AzureError instance.
func getAzureError(responseBody []byte) error {
var azErr AzureError
err := xml.Unmarshal(responseBody, &azErr)
if err != nil {
return fmt.Errorf("Failed parsing contents to AzureError format: %v", err)
}
return azErr
}
+30
View File
@@ -0,0 +1,30 @@
package management_test
import (
"fmt"
"testing"
"github.com/Azure/azure-sdk-for-go/management"
)
// TestIsResourceNotFoundError tests IsResourceNotFoundError with the
// set of given test cases.
func TestIsResourceNotFoundError(t *testing.T) {
// isResourceNotFoundTestCases is a set of structs comprising of the error
// IsResourceNotFoundError should test and the expected result.
var isResourceNotFoundTestCases = []struct {
err error
expected bool
}{
{nil, false},
{fmt.Errorf("Some other random error."), false},
{management.AzureError{Code: "ResourceNotFound"}, true},
{management.AzureError{Code: "NotAResourceNotFound"}, false},
}
for i, testCase := range isResourceNotFoundTestCases {
if res := management.IsResourceNotFoundError(testCase.err); res != testCase.expected {
t.Fatalf("Test %d: error %s - expected %t - got %t", i+1, testCase.err, testCase.expected, res)
}
}
}
@@ -0,0 +1,125 @@
// Package hostedservice provides a client for Hosted Services.
package hostedservice
import (
"encoding/base64"
"encoding/xml"
"fmt"
"github.com/Azure/azure-sdk-for-go/management"
)
const (
azureXmlns = "http://schemas.microsoft.com/windowsazure"
azureDeploymentListURL = "services/hostedservices/%s/deployments"
azureHostedServiceListURL = "services/hostedservices"
azureHostedServiceAvailabilityURL = "services/hostedservices/operations/isavailable/%s"
azureDeploymentURL = "services/hostedservices/%s/deployments/%s"
deleteAzureDeploymentURL = "services/hostedservices/%s/deployments/%s"
getHostedServicePropertiesURL = "services/hostedservices/%s"
azureServiceCertificateURL = "services/hostedservices/%s/certificates"
errParamNotSpecified = "Parameter %s is not specified."
)
//NewClient is used to return a handle to the HostedService API
func NewClient(client management.Client) HostedServiceClient {
return HostedServiceClient{client: client}
}
func (h HostedServiceClient) CreateHostedService(params CreateHostedServiceParameters) error {
req, err := xml.Marshal(params)
if err != nil {
return err
}
_, err = h.client.SendAzurePostRequest(azureHostedServiceListURL, req) // not a long running operation
return err
}
func (h HostedServiceClient) CheckHostedServiceNameAvailability(dnsName string) (AvailabilityResponse, error) {
var r AvailabilityResponse
if dnsName == "" {
return r, fmt.Errorf(errParamNotSpecified, "dnsName")
}
requestURL := fmt.Sprintf(azureHostedServiceAvailabilityURL, dnsName)
response, err := h.client.SendAzureGetRequest(requestURL)
if err != nil {
return r, err
}
err = xml.Unmarshal(response, &r)
return r, err
}
func (h HostedServiceClient) DeleteHostedService(dnsName string, deleteDisksAndBlobs bool) (management.OperationID, error) {
if dnsName == "" {
return "", fmt.Errorf(errParamNotSpecified, "dnsName")
}
requestURL := fmt.Sprintf(getHostedServicePropertiesURL, dnsName)
if deleteDisksAndBlobs {
requestURL += "?comp=media"
}
return h.client.SendAzureDeleteRequest(requestURL)
}
func (h HostedServiceClient) GetHostedService(name string) (HostedService, error) {
hostedService := HostedService{}
if name == "" {
return hostedService, fmt.Errorf(errParamNotSpecified, "name")
}
requestURL := fmt.Sprintf(getHostedServicePropertiesURL, name)
response, err := h.client.SendAzureGetRequest(requestURL)
if err != nil {
return hostedService, err
}
err = xml.Unmarshal(response, &hostedService)
if err != nil {
return hostedService, err
}
decodedLabel, err := base64.StdEncoding.DecodeString(hostedService.LabelBase64)
if err != nil {
return hostedService, err
}
hostedService.Label = string(decodedLabel)
return hostedService, nil
}
func (h HostedServiceClient) ListHostedServices() (ListHostedServicesResponse, error) {
var response ListHostedServicesResponse
data, err := h.client.SendAzureGetRequest(azureHostedServiceListURL)
if err != nil {
return response, err
}
err = xml.Unmarshal(data, &response)
return response, err
}
func (h HostedServiceClient) AddCertificate(dnsName string, certData []byte, certificateFormat CertificateFormat, password string) (management.OperationID, error) {
if dnsName == "" {
return "", fmt.Errorf(errParamNotSpecified, "dnsName")
}
certBase64 := base64.StdEncoding.EncodeToString(certData)
addCertificate := CertificateFile{
Data: certBase64,
CertificateFormat: certificateFormat,
Password: password,
Xmlns: azureXmlns,
}
buffer, err := xml.Marshal(addCertificate)
if err != nil {
return "", err
}
requestURL := fmt.Sprintf(azureServiceCertificateURL, dnsName)
return h.client.SendAzurePostRequest(requestURL, buffer)
}
@@ -0,0 +1,58 @@
package hostedservice
import (
"encoding/xml"
"github.com/Azure/azure-sdk-for-go/management"
)
//HostedServiceClient is used to perform operations on Azure Hosted Services
type HostedServiceClient struct {
client management.Client
}
type CreateHostedServiceParameters struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure CreateHostedService"`
ServiceName string
Label string
Description string
Location string
ReverseDNSFqdn string `xml:"ReverseDnsFqdn,omitempty"`
}
type AvailabilityResponse struct {
Xmlns string `xml:"xmlns,attr"`
Result bool
Reason string
}
type HostedService struct {
URL string `xml:"Url"`
ServiceName string
Description string `xml:"HostedServiceProperties>Description"`
AffinityGroup string `xml:"HostedServiceProperties>AffinityGroup"`
Location string `xml:"HostedServiceProperties>Location"`
LabelBase64 string `xml:"HostedServiceProperties>Label"`
Label string
Status string `xml:"HostedServiceProperties>Status"`
ReverseDNSFqdn string `xml:"HostedServiceProperties>ReverseDnsFqdn"`
DefaultWinRmCertificateThumbprint string
}
type CertificateFile struct {
Xmlns string `xml:"xmlns,attr"`
Data string
CertificateFormat CertificateFormat
Password string `xml:",omitempty"`
}
type CertificateFormat string
const (
CertificateFormatPfx = CertificateFormat("pfx")
CertificateFormatCer = CertificateFormat("cer")
)
type ListHostedServicesResponse struct {
HostedServices []HostedService `xml:"HostedService"`
}
+190
View File
@@ -0,0 +1,190 @@
package management
import (
"bytes"
"crypto/tls"
"fmt"
"net/http"
)
const (
msVersionHeader = "x-ms-version"
requestIDHeader = "x-ms-request-id"
uaHeader = "User-Agent"
contentHeader = "Content-Type"
defaultContentHeaderValue = "application/xml"
)
func (client client) SendAzureGetRequest(url string) ([]byte, error) {
resp, err := client.sendAzureRequest("GET", url, "", nil)
if err != nil {
return nil, err
}
return getResponseBody(resp)
}
func (client client) SendAzurePostRequest(url string, data []byte) (OperationID, error) {
return client.doAzureOperation("POST", url, "", data)
}
func (client client) SendAzurePostRequestWithReturnedResponse(url string, data []byte) ([]byte, error) {
resp, err := client.sendAzureRequest("POST", url, "", data)
if err != nil {
return nil, err
}
return getResponseBody(resp)
}
func (client client) SendAzurePutRequest(url, contentType string, data []byte) (OperationID, error) {
return client.doAzureOperation("PUT", url, contentType, data)
}
func (client client) SendAzureDeleteRequest(url string) (OperationID, error) {
return client.doAzureOperation("DELETE", url, "", nil)
}
func (client client) doAzureOperation(method, url, contentType string, data []byte) (OperationID, error) {
response, err := client.sendAzureRequest(method, url, contentType, data)
if err != nil {
return "", err
}
return getOperationID(response)
}
func getOperationID(response *http.Response) (OperationID, error) {
requestID := response.Header.Get(requestIDHeader)
if requestID == "" {
return "", fmt.Errorf("Could not retrieve operation id from %q header", requestIDHeader)
}
return OperationID(requestID), nil
}
// sendAzureRequest constructs an HTTP client for the request, sends it to the
// management API and returns the response or an error.
func (client client) sendAzureRequest(method, url, contentType string, data []byte) (*http.Response, error) {
if method == "" {
return nil, fmt.Errorf(errParamNotSpecified, "method")
}
if url == "" {
return nil, fmt.Errorf(errParamNotSpecified, "url")
}
httpClient, err := client.createHTTPClient()
if err != nil {
return nil, err
}
response, err := client.sendRequest(httpClient, url, method, contentType, data, 5)
if err != nil {
return nil, err
}
return response, nil
}
// createHTTPClient creates an HTTP Client configured with the key pair for
// the subscription for this client.
func (client client) createHTTPClient() (*http.Client, error) {
cert, err := tls.X509KeyPair(client.publishSettings.SubscriptionCert, client.publishSettings.SubscriptionKey)
if err != nil {
return nil, err
}
return &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
Renegotiation: tls.RenegotiateOnceAsClient,
Certificates: []tls.Certificate{cert},
},
},
}, nil
}
// sendRequest sends a request to the Azure management API using the given
// HTTP client and parameters. It returns the response from the call or an
// error.
func (client client) sendRequest(httpClient *http.Client, url, requestType, contentType string, data []byte, numberOfRetries int) (*http.Response, error) {
absURI := client.createAzureRequestURI(url)
for {
request, reqErr := client.createAzureRequest(absURI, requestType, contentType, data)
if reqErr != nil {
return nil, reqErr
}
response, err := httpClient.Do(request)
if err != nil {
if numberOfRetries == 0 {
return nil, err
}
return client.sendRequest(httpClient, url, requestType, contentType, data, numberOfRetries-1)
}
if response.StatusCode == http.StatusTemporaryRedirect {
// ASM's way of moving traffic around, see https://msdn.microsoft.com/en-us/library/azure/ee460801.aspx
// Only handled automatically for GET/HEAD requests. This is for the rest of the http verbs.
u, err := response.Location()
if err != nil {
return response, fmt.Errorf("Redirect requested but location header could not be retrieved: %v", err)
}
absURI = u.String()
continue // re-issue request
}
if response.StatusCode >= http.StatusBadRequest {
body, err := getResponseBody(response)
if err != nil {
// Failed to read the response body
return nil, err
}
azureErr := getAzureError(body)
if azureErr != nil {
if numberOfRetries == 0 {
return nil, azureErr
}
return client.sendRequest(httpClient, url, requestType, contentType, data, numberOfRetries-1)
}
}
return response, nil
}
}
// createAzureRequestURI constructs the request uri using the management API endpoint and
// subscription ID associated with the client.
func (client client) createAzureRequestURI(url string) string {
return fmt.Sprintf("%s/%s/%s", client.config.ManagementURL, client.publishSettings.SubscriptionID, url)
}
// createAzureRequest packages up the request with the correct set of headers and returns
// the request object or an error.
func (client client) createAzureRequest(url string, requestType string, contentType string, data []byte) (*http.Request, error) {
var request *http.Request
var err error
if data != nil {
body := bytes.NewBuffer(data)
request, err = http.NewRequest(requestType, url, body)
} else {
request, err = http.NewRequest(requestType, url, nil)
}
if err != nil {
return nil, err
}
request.Header.Set(msVersionHeader, client.config.APIVersion)
request.Header.Set(uaHeader, client.config.UserAgent)
if contentType != "" {
request.Header.Set(contentHeader, contentType)
} else {
request.Header.Set(contentHeader, defaultContentHeaderValue)
}
return request, nil
}
+30
View File
@@ -0,0 +1,30 @@
// Package location provides a client for Locations.
package location
import (
"encoding/xml"
"github.com/Azure/azure-sdk-for-go/management"
)
const (
azureLocationListURL = "locations"
errParamNotSpecified = "Parameter %s is not specified."
)
//NewClient is used to instantiate a new LocationClient from an Azure client
func NewClient(client management.Client) LocationClient {
return LocationClient{client: client}
}
func (c LocationClient) ListLocations() (ListLocationsResponse, error) {
var l ListLocationsResponse
response, err := c.client.SendAzureGetRequest(azureLocationListURL)
if err != nil {
return l, err
}
err = xml.Unmarshal(response, &l)
return l, err
}
@@ -0,0 +1,37 @@
package location
import (
"bytes"
"encoding/xml"
"fmt"
"strings"
"github.com/Azure/azure-sdk-for-go/management"
)
//LocationClient is used to perform operations on Azure Locations
type LocationClient struct {
client management.Client
}
type ListLocationsResponse struct {
XMLName xml.Name `xml:"Locations"`
Locations []Location `xml:"Location"`
}
type Location struct {
Name string
DisplayName string
AvailableServices []string `xml:"AvailableServices>AvailableService"`
WebWorkerRoleSizes []string `xml:"ComputeCapabilities>WebWorkerRoleSizes>RoleSize"`
VirtualMachineRoleSizes []string `xml:"ComputeCapabilities>VirtualMachinesRoleSizes>RoleSize"`
}
func (ll ListLocationsResponse) String() string {
var buf bytes.Buffer
for _, l := range ll.Locations {
fmt.Fprintf(&buf, "%s, ", l.Name)
}
return strings.Trim(buf.String(), ", ")
}
@@ -0,0 +1,245 @@
// Package networksecuritygroup provides a client for Network Security Groups.
package networksecuritygroup
import (
"encoding/xml"
"fmt"
"github.com/Azure/azure-sdk-for-go/management"
)
const (
createSecurityGroupURL = "services/networking/networksecuritygroups"
deleteSecurityGroupURL = "services/networking/networksecuritygroups/%s"
getSecurityGroupURL = "services/networking/networksecuritygroups/%s?detaillevel=full"
listSecurityGroupsURL = "services/networking/networksecuritygroups"
addSecurityGroupToSubnetURL = "services/networking/virtualnetwork/%s/subnets/%s/networksecuritygroups"
getSecurityGroupForSubnetURL = "services/networking/virtualnetwork/%s/subnets/%s/networksecuritygroups"
removeSecurityGroupFromSubnetURL = "services/networking/virtualnetwork/%s/subnets/%s/networksecuritygroups/%s"
setSecurityGroupRuleURL = "services/networking/networksecuritygroups/%s/rules/%s"
deleteSecurityGroupRuleURL = "services/networking/networksecuritygroups/%s/rules/%s"
errParamNotSpecified = "Parameter %s is not specified."
)
// NewClient is used to instantiate a new SecurityGroupClient from an Azure client
func NewClient(client management.Client) SecurityGroupClient {
return SecurityGroupClient{client: client}
}
// CreateNetworkSecurityGroup creates a new network security group within
// the context of the specified subscription
//
// https://msdn.microsoft.com/en-us/library/azure/dn913818.aspx
func (sg SecurityGroupClient) CreateNetworkSecurityGroup(
name string,
label string,
location string) (management.OperationID, error) {
if name == "" {
return "", fmt.Errorf(errParamNotSpecified, "name")
}
if location == "" {
return "", fmt.Errorf(errParamNotSpecified, "location")
}
data, err := xml.Marshal(SecurityGroupRequest{
Name: name,
Label: label,
Location: location,
})
if err != nil {
return "", err
}
requestURL := fmt.Sprintf(createSecurityGroupURL)
return sg.client.SendAzurePostRequest(requestURL, data)
}
// DeleteNetworkSecurityGroup deletes the specified network security group from the subscription
//
// https://msdn.microsoft.com/en-us/library/azure/dn913825.aspx
func (sg SecurityGroupClient) DeleteNetworkSecurityGroup(
name string) (management.OperationID, error) {
if name == "" {
return "", fmt.Errorf(errParamNotSpecified, "name")
}
requestURL := fmt.Sprintf(deleteSecurityGroupURL, name)
return sg.client.SendAzureDeleteRequest(requestURL)
}
// GetNetworkSecurityGroup returns information about the specified network security group
//
// https://msdn.microsoft.com/en-us/library/azure/dn913821.aspx
func (sg SecurityGroupClient) GetNetworkSecurityGroup(name string) (SecurityGroupResponse, error) {
if name == "" {
return SecurityGroupResponse{}, fmt.Errorf(errParamNotSpecified, "name")
}
var securityGroup SecurityGroupResponse
requestURL := fmt.Sprintf(getSecurityGroupURL, name)
response, err := sg.client.SendAzureGetRequest(requestURL)
if err != nil {
return securityGroup, err
}
err = xml.Unmarshal(response, &securityGroup)
return securityGroup, err
}
// ListNetworkSecurityGroups returns a list of the network security groups
// in the specified subscription
//
// https://msdn.microsoft.com/en-us/library/azure/dn913815.aspx
func (sg SecurityGroupClient) ListNetworkSecurityGroups() (SecurityGroupList, error) {
var securityGroups SecurityGroupList
response, err := sg.client.SendAzureGetRequest(listSecurityGroupsURL)
if err != nil {
return securityGroups, err
}
err = xml.Unmarshal(response, &securityGroups)
return securityGroups, err
}
// AddNetworkSecurityToSubnet associates the network security group with
// specified subnet in a virtual network
//
// https://msdn.microsoft.com/en-us/library/azure/dn913822.aspx
func (sg SecurityGroupClient) AddNetworkSecurityToSubnet(
name string,
subnet string,
virtualNetwork string) (management.OperationID, error) {
if name == "" {
return "", fmt.Errorf(errParamNotSpecified, "name")
}
if subnet == "" {
return "", fmt.Errorf(errParamNotSpecified, "subnet")
}
if virtualNetwork == "" {
return "", fmt.Errorf(errParamNotSpecified, "virtualNetwork")
}
data, err := xml.Marshal(SecurityGroupRequest{Name: name})
if err != nil {
return "", err
}
requestURL := fmt.Sprintf(addSecurityGroupToSubnetURL, virtualNetwork, subnet)
return sg.client.SendAzurePostRequest(requestURL, data)
}
// GetNetworkSecurityGroupForSubnet returns information about the network
// security group associated with a subnet
//
// https://msdn.microsoft.com/en-us/library/azure/dn913817.aspx
func (sg SecurityGroupClient) GetNetworkSecurityGroupForSubnet(
subnet string,
virtualNetwork string) (SecurityGroupResponse, error) {
if subnet == "" {
return SecurityGroupResponse{}, fmt.Errorf(errParamNotSpecified, "subnet")
}
if virtualNetwork == "" {
return SecurityGroupResponse{}, fmt.Errorf(errParamNotSpecified, "virtualNetwork")
}
var securityGroup SecurityGroupResponse
requestURL := fmt.Sprintf(getSecurityGroupForSubnetURL, virtualNetwork, subnet)
response, err := sg.client.SendAzureGetRequest(requestURL)
if err != nil {
return securityGroup, err
}
err = xml.Unmarshal(response, &securityGroup)
return securityGroup, err
}
// RemoveNetworkSecurityGroupFromSubnet removes the association of the
// specified network security group from the specified subnet
//
// https://msdn.microsoft.com/en-us/library/azure/dn913820.aspx
func (sg SecurityGroupClient) RemoveNetworkSecurityGroupFromSubnet(
name string,
subnet string,
virtualNetwork string) (management.OperationID, error) {
if name == "" {
return "", fmt.Errorf(errParamNotSpecified, "name")
}
if subnet == "" {
return "", fmt.Errorf(errParamNotSpecified, "subnet")
}
if virtualNetwork == "" {
return "", fmt.Errorf(errParamNotSpecified, "virtualNetwork")
}
requestURL := fmt.Sprintf(removeSecurityGroupFromSubnetURL, virtualNetwork, subnet, name)
return sg.client.SendAzureDeleteRequest(requestURL)
}
// SetNetworkSecurityGroupRule adds or updates a network security rule that
// is associated with the specified network security group
//
// https://msdn.microsoft.com/en-us/library/azure/dn913819.aspx
func (sg SecurityGroupClient) SetNetworkSecurityGroupRule(
securityGroup string,
rule RuleRequest) (management.OperationID, error) {
if securityGroup == "" {
return "", fmt.Errorf(errParamNotSpecified, "securityGroup")
}
if rule.Name == "" {
return "", fmt.Errorf(errParamNotSpecified, "Name")
}
if rule.Type == "" {
return "", fmt.Errorf(errParamNotSpecified, "Type")
}
if rule.Priority == 0 {
return "", fmt.Errorf(errParamNotSpecified, "Priority")
}
if rule.Action == "" {
return "", fmt.Errorf(errParamNotSpecified, "Action")
}
if rule.SourceAddressPrefix == "" {
return "", fmt.Errorf(errParamNotSpecified, "SourceAddressPrefix")
}
if rule.SourcePortRange == "" {
return "", fmt.Errorf(errParamNotSpecified, "SourcePortRange")
}
if rule.DestinationAddressPrefix == "" {
return "", fmt.Errorf(errParamNotSpecified, "DestinationAddressPrefix")
}
if rule.DestinationPortRange == "" {
return "", fmt.Errorf(errParamNotSpecified, "DestinationPortRange")
}
if rule.Protocol == "" {
return "", fmt.Errorf(errParamNotSpecified, "Protocol")
}
data, err := xml.Marshal(rule)
if err != nil {
return "", err
}
requestURL := fmt.Sprintf(setSecurityGroupRuleURL, securityGroup, rule.Name)
return sg.client.SendAzurePutRequest(requestURL, "", data)
}
// DeleteNetworkSecurityGroupRule deletes a network security group rule from
// the specified network security group
//
// https://msdn.microsoft.com/en-us/library/azure/dn913816.aspx
func (sg SecurityGroupClient) DeleteNetworkSecurityGroupRule(
securityGroup string,
rule string) (management.OperationID, error) {
if securityGroup == "" {
return "", fmt.Errorf(errParamNotSpecified, "securityGroup")
}
if rule == "" {
return "", fmt.Errorf(errParamNotSpecified, "rule")
}
requestURL := fmt.Sprintf(deleteSecurityGroupRuleURL, securityGroup, rule)
return sg.client.SendAzureDeleteRequest(requestURL)
}
@@ -0,0 +1,115 @@
// Package networksecuritygroup implements operations for managing network security groups
// using the Service Management REST API
//
// https://msdn.microsoft.com/en-us/library/azure/dn913824.aspx
package networksecuritygroup
import (
"encoding/xml"
"github.com/Azure/azure-sdk-for-go/management"
)
// SecurityGroupClient is used to perform operations on network security groups
type SecurityGroupClient struct {
client management.Client
}
// SecurityGroupRequest represents a network security group
//
// https://msdn.microsoft.com/en-us/library/azure/dn913821.aspx
type SecurityGroupRequest struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure NetworkSecurityGroup"`
Name string
Label string `xml:",omitempty"`
Location string `xml:",omitempty"`
}
// SecurityGroupResponse represents a network security group
//
// https://msdn.microsoft.com/en-us/library/azure/dn913821.aspx
type SecurityGroupResponse struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure NetworkSecurityGroup"`
Name string
Label string `xml:",omitempty"`
Location string `xml:",omitempty"`
State SecurityGroupState `xml:",omitempty"`
Rules []RuleResponse `xml:">Rule,omitempty"`
}
// SecurityGroupList represents a list of security groups
type SecurityGroupList []SecurityGroupResponse
// SecurityGroupState represents a security group state
type SecurityGroupState string
// These constants represent the possible security group states
const (
SecurityGroupStateCreated SecurityGroupState = "Created"
SecurityGroupStateCreating SecurityGroupState = "Creating"
SecurityGroupStateUpdating SecurityGroupState = "Updating"
SecurityGroupStateDeleting SecurityGroupState = "Deleting"
SecurityGroupStateUnavailable SecurityGroupState = "Unavailable"
)
// RuleRequest represents a single rule of a network security group
//
// https://msdn.microsoft.com/en-us/library/azure/dn913821.aspx#bk_rules
type RuleRequest struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure Rule"`
Name string
Type RuleType
Priority int
Action RuleAction
SourceAddressPrefix string
SourcePortRange string
DestinationAddressPrefix string
DestinationPortRange string
Protocol RuleProtocol
}
// RuleResponse represents a single rule of a network security group
//
// https://msdn.microsoft.com/en-us/library/azure/dn913821.aspx#bk_rules
type RuleResponse struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure Rule"`
Name string
Type RuleType
Priority int
Action RuleAction
SourceAddressPrefix string
SourcePortRange string
DestinationAddressPrefix string
DestinationPortRange string
Protocol RuleProtocol
State string `xml:",omitempty"`
IsDefault bool `xml:",omitempty"`
}
// RuleType represents a rule type
type RuleType string
// These constants represent the possible rule types
const (
RuleTypeInbound RuleType = "Inbound"
RuleTypeOutbound RuleType = "Outbound"
)
// RuleAction represents a rule action
type RuleAction string
// These constants represent the possible rule actions
const (
RuleActionAllow RuleAction = "Allow"
RuleActionDeny RuleAction = "Deny"
)
// RuleProtocol represents a rule protocol
type RuleProtocol string
// These constants represent the possible rule types
const (
RuleProtocolTCP RuleProtocol = "TCP"
RuleProtocolUDP RuleProtocol = "UDP"
RuleProtocolAll RuleProtocol = "*"
)
+92
View File
@@ -0,0 +1,92 @@
package management
import (
"encoding/xml"
"errors"
"fmt"
"time"
)
var (
// ErrOperationCancelled from WaitForOperation when the polling loop is
// cancelled through signaling the channel.
ErrOperationCancelled = errors.New("Polling for operation status cancelled")
)
// GetOperationStatusResponse represents an in-flight operation. Use
// client.GetOperationStatus() to get the operation given the operation ID, or
// use WaitForOperation() to poll and wait until the operation has completed.
// See https://msdn.microsoft.com/en-us/library/azure/ee460783.aspx
type GetOperationStatusResponse struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure Operation"`
ID string
Status OperationStatus
HTTPStatusCode string
Error *AzureError
}
// OperationStatus describes the states an Microsoft Azure Service Management
// operation an be in.
type OperationStatus string
// List of states an operation can be reported as
const (
OperationStatusInProgress OperationStatus = "InProgress"
OperationStatusSucceeded OperationStatus = "Succeeded"
OperationStatusFailed OperationStatus = "Failed"
)
// OperationID is assigned by Azure API and can be used to look up the status of
// an operation
type OperationID string
func (c client) GetOperationStatus(operationID OperationID) (GetOperationStatusResponse, error) {
operation := GetOperationStatusResponse{}
if operationID == "" {
return operation, fmt.Errorf(errParamNotSpecified, "operationID")
}
url := fmt.Sprintf("operations/%s", operationID)
response, azureErr := c.SendAzureGetRequest(url)
if azureErr != nil {
return operation, azureErr
}
err := xml.Unmarshal(response, &operation)
return operation, err
}
func (c client) WaitForOperation(operationID OperationID, cancel chan struct{}) error {
for {
done, err := c.checkOperationStatus(operationID)
if err != nil || done {
return err
}
select {
case <-time.After(c.config.OperationPollInterval):
case <-cancel:
return ErrOperationCancelled
}
}
}
func (c client) checkOperationStatus(id OperationID) (done bool, err error) {
op, err := c.GetOperationStatus(id)
if err != nil {
return false, fmt.Errorf("Failed to get operation status '%s': %v", id, err)
}
switch op.Status {
case OperationStatusSucceeded:
return true, nil
case OperationStatusFailed:
if op.Error != nil {
return true, op.Error
}
return true, fmt.Errorf("Azure Operation (x-ms-request-id=%s) has failed", id)
case OperationStatusInProgress:
return false, nil
default:
return false, fmt.Errorf("Unknown operation status returned from API: %s (x-ms-request-id=%s)", op.Status, id)
}
}
+44
View File
@@ -0,0 +1,44 @@
// Package osimage provides a client for Operating System Images.
package osimage
import (
"encoding/xml"
"github.com/Azure/azure-sdk-for-go/management"
)
const (
azureImageListURL = "services/images"
errInvalidImage = "Can not find image %s in specified subscription, please specify another image name."
errParamNotSpecified = "Parameter %s is not specified."
)
// NewClient is used to instantiate a new OSImageClient from an Azure client.
func NewClient(client management.Client) OSImageClient {
return OSImageClient{client: client}
}
func (c OSImageClient) ListOSImages() (ListOSImagesResponse, error) {
var l ListOSImagesResponse
response, err := c.client.SendAzureGetRequest(azureImageListURL)
if err != nil {
return l, err
}
err = xml.Unmarshal(response, &l)
return l, err
}
// AddOSImage adds an operating system image to the image repository that is associated with the specified subscription.
//
// See https://msdn.microsoft.com/en-us/library/azure/jj157192.aspx for details.
func (c OSImageClient) AddOSImage(osi *OSImage) (management.OperationID, error) {
data, err := xml.Marshal(osi)
if err != nil {
return "", err
}
return c.client.SendAzurePostRequest(azureImageListURL, data)
}
@@ -0,0 +1,49 @@
package osimage
import (
"encoding/xml"
"github.com/Azure/azure-sdk-for-go/management"
)
// OSImageClient is used to perform operations on Azure Locations
type OSImageClient struct {
client management.Client
}
type ListOSImagesResponse struct {
XMLName xml.Name `xml:"Images"`
OSImages []OSImage `xml:"OSImage"`
}
type OSImage struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure OSImage"`
Category string // Public || Private || MSDN
Label string // Specifies an identifier for the image.
LogicalSizeInGB float64 //Specifies the size, in GB, of the image.
Name string // Specifies the name of the operating system image. This is the name that is used when creating one or more virtual machines using the image.
OS string // Linux || Windows
Eula string // Specifies the End User License Agreement that is associated with the image. The value for this element is a string, but it is recommended that the value be a URL that points to a EULA.
Description string // Specifies the description of the image.
Location string // The geo-location in which this media is located. The Location value is derived from storage account that contains the blob in which the media is located. If the storage account belongs to an affinity group the value is NULL.
AffinityGroup string // Specifies the affinity in which the media is located. The AffinityGroup value is derived from storage account that contains the blob in which the media is located. If the storage account does not belong to an affinity group the value is NULL and the element is not displayed in the response. This value is NULL for platform images.
MediaLink string // Specifies the location of the vhd file for the image. The storage account where the vhd is located must be associated with the specified subscription.
ImageFamily string // Specifies a value that can be used to group images.
PublishedDate string // Specifies the date when the image was added to the image repository.
IsPremium string // Indicates whether the image contains software or associated services that will incur charges above the core price for the virtual machine. For additional details, see the PricingDetailLink element.
PrivacyURI string `xml:"PrivacyUri"` // Specifies the URI that points to a document that contains the privacy policy related to the image.
RecommendedVMSize string // Specifies the size to use for the virtual machine that is created from the image.
PublisherName string // The name of the publisher of the image. All user images have a publisher name of User.
PricingDetailLink string // Specifies a URL for an image with IsPremium set to true, which contains the pricing details for a virtual machine that is created from the image.
IconURI string `xml:"IconUri"` // Specifies the Uri to the icon that is displayed for the image in the Management Portal.
SmallIconURI string `xml:"SmallIconUri"` // Specifies the URI to the small icon that is displayed when the image is presented in the Microsoft Azure Management Portal.
Language string // Specifies the language of the image.
IOType IOType // Provisioned || Standard
}
type IOType string
const (
IOTypeProvisioned IOType = "Provisioned"
IOTypeStandard IOType = "Standard"
)
+108
View File
@@ -0,0 +1,108 @@
package management
import (
"encoding/base64"
"encoding/pem"
"encoding/xml"
"fmt"
"io/ioutil"
"golang.org/x/crypto/pkcs12"
)
// ClientFromPublishSettingsData unmarshalls the contents of a publish settings file
// from https://manage.windowsazure.com/publishsettings.
// If subscriptionID is left empty, the first subscription in the file is used.
func ClientFromPublishSettingsData(settingsData []byte, subscriptionID string) (client Client, err error) {
return ClientFromPublishSettingsDataWithConfig(settingsData, subscriptionID, DefaultConfig())
}
// ClientFromPublishSettingsFile reads a publish settings file downloaded from https://manage.windowsazure.com/publishsettings.
// If subscriptionID is left empty, the first subscription in the file is used.
func ClientFromPublishSettingsFile(filePath, subscriptionID string) (client Client, err error) {
return ClientFromPublishSettingsFileWithConfig(filePath, subscriptionID, DefaultConfig())
}
// ClientFromPublishSettingsFileWithConfig reads a publish settings file downloaded from https://manage.windowsazure.com/publishsettings.
// If subscriptionID is left empty, the first subscription in the file is used.
func ClientFromPublishSettingsFileWithConfig(filePath, subscriptionID string, config ClientConfig) (client Client, err error) {
if filePath == "" {
return client, fmt.Errorf(errParamNotSpecified, "filePath")
}
publishSettingsContent, err := ioutil.ReadFile(filePath)
if err != nil {
return client, err
}
return ClientFromPublishSettingsDataWithConfig(publishSettingsContent, subscriptionID, config)
}
// ClientFromPublishSettingsDataWithConfig unmarshalls the contents of a publish settings file
// from https://manage.windowsazure.com/publishsettings.
// If subscriptionID is left empty, the first subscription in the string is used.
func ClientFromPublishSettingsDataWithConfig(data []byte, subscriptionID string, config ClientConfig) (client Client, err error) {
publishData := publishData{}
if err = xml.Unmarshal(data, &publishData); err != nil {
return client, err
}
for _, profile := range publishData.PublishProfiles {
for _, sub := range profile.Subscriptions {
if sub.ID == subscriptionID || subscriptionID == "" {
base64Cert := sub.ManagementCertificate
if base64Cert == "" {
base64Cert = profile.ManagementCertificate
}
pfxData, err := base64.StdEncoding.DecodeString(base64Cert)
if err != nil {
return client, err
}
pems, err := pkcs12.ToPEM(pfxData, "")
if err != nil {
return client, err
}
cert := []byte{}
for _, b := range pems {
cert = append(cert, pem.EncodeToMemory(b)...)
}
config.ManagementURL = sub.ServiceManagementURL
return makeClient(sub.ID, cert, config)
}
}
}
return client, fmt.Errorf("could not find subscription '%s' in settings provided", subscriptionID)
}
type publishSettings struct {
SubscriptionID string
SubscriptionCert []byte
SubscriptionKey []byte
}
type publishData struct {
XMLName xml.Name `xml:"PublishData"`
PublishProfiles []publishProfile `xml:"PublishProfile"`
}
type publishProfile struct {
XMLName xml.Name `xml:"PublishProfile"`
SchemaVersion string `xml:",attr"`
PublishMethod string `xml:",attr"`
URL string `xml:"Url,attr"`
ManagementCertificate string `xml:",attr"`
Subscriptions []subscription `xml:"Subscription"`
}
type subscription struct {
XMLName xml.Name `xml:"Subscription"`
ServiceManagementURL string `xml:"ServiceManagementUrl,attr"`
ID string `xml:"Id,attr"`
Name string `xml:",attr"`
ManagementCertificate string `xml:",attr"`
}
+316
View File
@@ -0,0 +1,316 @@
package sql
import (
"encoding/xml"
"fmt"
"time"
"github.com/Azure/azure-sdk-for-go/management"
)
// Definitions of numerous constants representing API endpoints.
const (
azureCreateDatabaseServerURL = "services/sqlservers/servers"
azureListDatabaseServersURL = "services/sqlservers/servers"
azureDeleteDatabaseServerURL = "services/sqlservers/servers/%s"
azureCreateFirewallRuleURL = "services/sqlservers/servers/%s/firewallrules"
azureGetFirewallRuleURL = "services/sqlservers/servers/%s/firewallrules/%s"
azureListFirewallRulesURL = "services/sqlservers/servers/%s/firewallrules"
azureUpdateFirewallRuleURL = "services/sqlservers/servers/%s/firewallrules/%s"
azureDeleteFirewallRuleURL = "services/sqlservers/servers/%s/firewallrules/%s"
azureCreateDatabaseURL = "services/sqlservers/servers/%s/databases"
azureGetDatabaseURL = "services/sqlservers/servers/%s/databases/%s"
azureListDatabasesURL = "services/sqlservers/servers/%s/databases?contentview=generic"
azureUpdateDatabaseURL = "services/sqlservers/servers/%s/databases/%s"
azureDeleteDatabaseURL = "services/sqlservers/servers/%s/databases/%s"
errParamNotSpecified = "Parameter %s was not specified."
DatabaseStateCreating = "Creating"
)
// SQLDatabaseClient defines various database CRUD operations.
// It contains a management.Client for making the actual http calls.
type SQLDatabaseClient struct {
mgmtClient management.Client
}
// NewClient returns a new SQLDatabaseClient struct with the provided
// management.Client as the underlying client.
func NewClient(mgmtClient management.Client) SQLDatabaseClient {
return SQLDatabaseClient{mgmtClient}
}
// CreateServer creates a new Azure SQL Database server and return its name.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505699.aspx
func (c SQLDatabaseClient) CreateServer(params DatabaseServerCreateParams) (string, error) {
req, err := xml.Marshal(params)
if err != nil {
return "", err
}
resp, err := c.mgmtClient.SendAzurePostRequestWithReturnedResponse(azureCreateDatabaseServerURL, req)
if err != nil {
return "", err
}
var name string
err = xml.Unmarshal(resp, &name)
return name, err
}
// ListServers retrieves the Azure SQL Database servers for this subscription.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505702.aspx
func (c SQLDatabaseClient) ListServers() (ListServersResponse, error) {
var resp ListServersResponse
data, err := c.mgmtClient.SendAzureGetRequest(azureListDatabaseServersURL)
if err != nil {
return resp, err
}
err = xml.Unmarshal(data, &resp)
return resp, err
}
// DeleteServer deletes an Azure SQL Database server (including all its databases).
//
// https://msdn.microsoft.com/en-us/library/azure/dn505695.aspx
func (c SQLDatabaseClient) DeleteServer(name string) error {
if name == "" {
return fmt.Errorf(errParamNotSpecified, "name")
}
url := fmt.Sprintf(azureDeleteDatabaseServerURL, name)
_, err := c.mgmtClient.SendAzureDeleteRequest(url)
return err
}
// CreateFirewallRule creates an Azure SQL Database server
// firewall rule.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505712.aspx
func (c SQLDatabaseClient) CreateFirewallRule(server string, params FirewallRuleCreateParams) error {
if server == "" {
return fmt.Errorf(errParamNotSpecified, "server")
}
req, err := xml.Marshal(params)
if err != nil {
return err
}
url := fmt.Sprintf(azureCreateFirewallRuleURL, server)
_, err = c.mgmtClient.SendAzurePostRequest(url, req)
return err
}
// GetFirewallRule gets the details of an Azure SQL Database Server firewall rule.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505698.aspx
func (c SQLDatabaseClient) GetFirewallRule(server, ruleName string) (FirewallRuleResponse, error) {
var rule FirewallRuleResponse
if server == "" {
return rule, fmt.Errorf(errParamNotSpecified, "server")
}
if ruleName == "" {
return rule, fmt.Errorf(errParamNotSpecified, "ruleName")
}
url := fmt.Sprintf(azureGetFirewallRuleURL, server, ruleName)
resp, err := c.mgmtClient.SendAzureGetRequest(url)
if err != nil {
return rule, err
}
err = xml.Unmarshal(resp, &rule)
return rule, err
}
// ListFirewallRules retrieves the set of firewall rules for an Azure SQL
// Database Server.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505715.aspx
func (c SQLDatabaseClient) ListFirewallRules(server string) (ListFirewallRulesResponse, error) {
var rules ListFirewallRulesResponse
if server == "" {
return rules, fmt.Errorf(errParamNotSpecified, "server")
}
url := fmt.Sprintf(azureListFirewallRulesURL, server)
resp, err := c.mgmtClient.SendAzureGetRequest(url)
if err != nil {
return rules, err
}
err = xml.Unmarshal(resp, &rules)
return rules, err
}
// UpdateFirewallRule update a firewall rule for an Azure SQL Database server.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505707.aspx
func (c SQLDatabaseClient) UpdateFirewallRule(server, ruleName string, params FirewallRuleUpdateParams) error {
if server == "" {
return fmt.Errorf(errParamNotSpecified, "server")
}
if ruleName == "" {
return fmt.Errorf(errParamNotSpecified, "ruleName")
}
req, err := xml.Marshal(params)
if err != nil {
return err
}
url := fmt.Sprintf(azureUpdateFirewallRuleURL, server, ruleName)
_, err = c.mgmtClient.SendAzurePutRequest(url, "text/xml", req)
return err
}
// DeleteFirewallRule deletes an Azure SQL Database server firewall rule.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505706.aspx
func (c SQLDatabaseClient) DeleteFirewallRule(server, ruleName string) error {
if server == "" {
return fmt.Errorf(errParamNotSpecified, "server")
}
if ruleName == "" {
return fmt.Errorf(errParamNotSpecified, "ruleName")
}
url := fmt.Sprintf(azureDeleteFirewallRuleURL, server, ruleName)
_, err := c.mgmtClient.SendAzureDeleteRequest(url)
return err
}
// CreateDatabase creates a new Microsoft Azure SQL Database on the given database server.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505701.aspx
func (c SQLDatabaseClient) CreateDatabase(server string, params DatabaseCreateParams) error {
if server == "" {
return fmt.Errorf(errParamNotSpecified, "server")
}
req, err := xml.Marshal(params)
if err != nil {
return err
}
target := fmt.Sprintf(azureCreateDatabaseURL, server)
_, err = c.mgmtClient.SendAzurePostRequest(target, req)
return err
}
// WaitForDatabaseCreation is a helper method which waits
// for the creation of the database on the given server.
func (c SQLDatabaseClient) WaitForDatabaseCreation(
server, database string,
cancel chan struct{}) error {
for {
stat, err := c.GetDatabase(server, database)
if err != nil {
return err
}
if stat.State != DatabaseStateCreating {
return nil
}
select {
case <-time.After(management.DefaultOperationPollInterval):
case <-cancel:
return management.ErrOperationCancelled
}
}
}
// GetDatabase gets the details for an Azure SQL Database.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505708.aspx
func (c SQLDatabaseClient) GetDatabase(server, database string) (ServiceResource, error) {
var db ServiceResource
if database == "" {
return db, fmt.Errorf(errParamNotSpecified, "database")
}
if server == "" {
return db, fmt.Errorf(errParamNotSpecified, "server")
}
url := fmt.Sprintf(azureGetDatabaseURL, server, database)
resp, err := c.mgmtClient.SendAzureGetRequest(url)
if err != nil {
return db, err
}
err = xml.Unmarshal(resp, &db)
return db, err
}
// ListDatabases returns a list of Azure SQL Databases on the given server.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505711.aspx
func (c SQLDatabaseClient) ListDatabases(server string) (ListDatabasesResponse, error) {
var databases ListDatabasesResponse
if server == "" {
return databases, fmt.Errorf(errParamNotSpecified, "server name")
}
url := fmt.Sprintf(azureListDatabasesURL, server)
resp, err := c.mgmtClient.SendAzureGetRequest(url)
if err != nil {
return databases, err
}
err = xml.Unmarshal(resp, &databases)
return databases, err
}
// UpdateDatabase updates the details of the given Database off the given server.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505718.aspx
func (c SQLDatabaseClient) UpdateDatabase(
server, database string,
params ServiceResourceUpdateParams) (management.OperationID, error) {
if database == "" {
return "", fmt.Errorf(errParamNotSpecified, "database")
}
if server == "" {
return "", fmt.Errorf(errParamNotSpecified, "server")
}
url := fmt.Sprintf(azureUpdateDatabaseURL, server, database)
req, err := xml.Marshal(params)
if err != nil {
return "", err
}
return c.mgmtClient.SendAzurePutRequest(url, "text/xml", req)
}
// DeleteDatabase deletes the Azure SQL Database off the given server.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505705.aspx
func (c SQLDatabaseClient) DeleteDatabase(server, database string) error {
if database == "" {
return fmt.Errorf(errParamNotSpecified, "database")
}
if server == "" {
return fmt.Errorf(errParamNotSpecified, "server")
}
url := fmt.Sprintf(azureDeleteDatabaseURL, server, database)
_, err := c.mgmtClient.SendAzureDeleteRequest(url)
return err
}
+124
View File
@@ -0,0 +1,124 @@
package sql
import (
"encoding/xml"
)
// DatabaseServerCreateParams represents the set of possible parameters
// when issuing a database server creation request to Azure.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505699.aspx
type DatabaseServerCreateParams struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/sqlazure/2010/12/ Server"`
AdministratorLogin string
AdministratorLoginPassword string
Location string
Version string
}
// DatabaseServerCreateResponse represents the response following the creation of
// a database server on Azure.
type DatabaseServerCreateResponse struct {
ServerName string
}
const (
DatabaseServerVersion11 = "2.0"
DatabaseServerVersion12 = "12.0"
)
// DatabaseServer represents the set of data received from
// a database server list operation.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505702.aspx
type DatabaseServer struct {
Name string
AdministratorLogin string
Location string
FullyQualifiedDomainName string
Version string
State string
}
type ListServersResponse struct {
DatabaseServers []DatabaseServer `xml:"Server"`
}
// FirewallRuleCreateParams represents the set of possible
// parameters when creating a firewall rule on an Azure database server.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505712.aspx
type FirewallRuleCreateParams struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure ServiceResource"`
Name string
StartIPAddress string
EndIPAddress string
}
// FirewallRuleResponse represents the set of data received from
// an Azure database server firewall rule get response.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505698.aspx
type FirewallRuleResponse struct {
Name string
StartIPAddress string
EndIPAddress string
}
type ListFirewallRulesResponse struct {
FirewallRules []FirewallRuleResponse `xml:"ServiceResource"`
}
// FirewallRuleUpdateParams represents the set of possible
// parameters when issuing an update of a database server firewall rule.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505707.aspx
type FirewallRuleUpdateParams struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure ServiceResource"`
Name string
StartIPAddress string
EndIPAddress string
}
// DatabaseCreateParams represents the set of possible parameters when issuing
// a database creation to Azure, and reading a list response from Azure.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505701.aspx
type DatabaseCreateParams struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure ServiceResource"`
Name string
Edition string `xml:",omitempty"`
CollationName string `xml:",omitempty"`
MaxSizeBytes int64 `xml:",omitempty"`
ServiceObjectiveID string `xml:"ServiceObjectiveId,omitempty"`
}
// ServiceResource represents the set of parameters obtained from a database
// get or list call.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505708.aspx
type ServiceResource struct {
Name string
State string
SelfLink string
Edition string
CollationName string
MaxSizeBytes int64
ServiceObjectiveID string `xml:"ServiceObjectiveId,omitempty"`
}
type ListDatabasesResponse struct {
ServiceResources []ServiceResource `xml:"ServiceResource"`
}
// ServiceResourceUpdateParams represents the set of parameters available
// for a database service update operation.
//
// https://msdn.microsoft.com/en-us/library/azure/dn505718.aspx
type ServiceResourceUpdateParams struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure ServiceResource"`
Name string
Edition string `xml:",omitempty"`
MaxSizeBytes int64 `xml:",omitempty"`
ServiceObjectiveID string `xml:"ServiceObjectiveId,omitempty"`
}
@@ -0,0 +1,108 @@
// Package storageservice provides a client for Storage Services.
package storageservice
import (
"encoding/xml"
"fmt"
"github.com/Azure/azure-sdk-for-go/management"
)
const (
azureStorageServiceListURL = "services/storageservices"
azureStorageServiceURL = "services/storageservices/%s"
azureStorageServiceKeysURL = "services/storageservices/%s/keys"
azureStorageAccountAvailabilityURL = "services/storageservices/operations/isavailable/%s"
azureXmlns = "http://schemas.microsoft.com/windowsazure"
errParamNotSpecified = "Parameter %s is not specified."
)
// NewClient is used to instantiate a new StorageServiceClient from an Azure
// client.
func NewClient(s management.Client) StorageServiceClient {
return StorageServiceClient{client: s}
}
func (s StorageServiceClient) ListStorageServices() (ListStorageServicesResponse, error) {
var l ListStorageServicesResponse
response, err := s.client.SendAzureGetRequest(azureStorageServiceListURL)
if err != nil {
return l, err
}
err = xml.Unmarshal(response, &l)
return l, err
}
func (s StorageServiceClient) GetStorageService(serviceName string) (StorageServiceResponse, error) {
var svc StorageServiceResponse
if serviceName == "" {
return svc, fmt.Errorf(errParamNotSpecified, "serviceName")
}
requestURL := fmt.Sprintf(azureStorageServiceURL, serviceName)
response, err := s.client.SendAzureGetRequest(requestURL)
if err != nil {
return svc, err
}
err = xml.Unmarshal(response, &svc)
return svc, err
}
func (s StorageServiceClient) GetStorageServiceKeys(serviceName string) (GetStorageServiceKeysResponse, error) {
var r GetStorageServiceKeysResponse
if serviceName == "" {
return r, fmt.Errorf(errParamNotSpecified, "serviceName")
}
requestURL := fmt.Sprintf(azureStorageServiceKeysURL, serviceName)
data, err := s.client.SendAzureGetRequest(requestURL)
if err != nil {
return r, err
}
err = xml.Unmarshal(data, &r)
return r, err
}
func (s StorageServiceClient) CreateStorageService(parameters StorageAccountCreateParameters) (management.OperationID, error) {
data, err := xml.Marshal(CreateStorageServiceInput{
StorageAccountCreateParameters: parameters})
if err != nil {
return "", err
}
return s.client.SendAzurePostRequest(azureStorageServiceListURL, data)
}
func (s StorageServiceClient) DeleteStorageService(serviceName string) (management.OperationID, error) {
if serviceName == "" {
return "", fmt.Errorf(errParamNotSpecified, "serviceName")
}
requestURL := fmt.Sprintf(azureStorageServiceURL, serviceName)
return s.client.SendAzureDeleteRequest(requestURL)
}
// CheckStorageAccountNameAvailability checks to if the specified storage account
// name is available.
//
// See https://msdn.microsoft.com/en-us/library/azure/jj154125.aspx
func (s StorageServiceClient) CheckStorageAccountNameAvailability(name string) (AvailabilityResponse, error) {
var r AvailabilityResponse
if name == "" {
return r, fmt.Errorf(errParamNotSpecified, "name")
}
requestURL := fmt.Sprintf(azureStorageAccountAvailabilityURL, name)
response, err := s.client.SendAzureGetRequest(requestURL)
if err != nil {
return r, err
}
err = xml.Unmarshal(response, &r)
return r, err
}
@@ -0,0 +1,79 @@
package storageservice
import (
"encoding/xml"
"github.com/Azure/azure-sdk-for-go/management"
)
// StorageServiceClient is used to perform operations on Azure Storage
type StorageServiceClient struct {
client management.Client
}
type ListStorageServicesResponse struct {
StorageServices []StorageServiceResponse `xml:"StorageService"`
}
type StorageServiceResponse struct {
URL string `xml:"Url"`
ServiceName string
StorageServiceProperties StorageServiceProperties
}
type StorageServiceProperties struct {
Description string
Location string
Label string
Status string
Endpoints []string `xml:"Endpoints>Endpoint"`
GeoReplicationEnabled string
GeoPrimaryRegion string
}
type GetStorageServiceKeysResponse struct {
URL string `xml:"Url"`
PrimaryKey string `xml:"StorageServiceKeys>Primary"`
SecondaryKey string `xml:"StorageServiceKeys>Secondary"`
}
type CreateStorageServiceInput struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure CreateStorageServiceInput"`
StorageAccountCreateParameters
}
type StorageAccountCreateParameters struct {
ServiceName string
Description string `xml:",omitempty"`
Label string
AffinityGroup string `xml:",omitempty"`
Location string `xml:",omitempty"`
ExtendedProperties ExtendedPropertyList
AccountType AccountType
}
type AccountType string
const (
AccountTypeStandardLRS AccountType = "Standard_LRS"
AccountTypeStandardZRS AccountType = "Standard_ZRS"
AccountTypeStandardGRS AccountType = "Standard_GRS"
AccountTypeStandardRAGRS AccountType = "Standard_RAGRS"
AccountTypePremiumLRS AccountType = "Premium_LRS"
)
type ExtendedPropertyList struct {
ExtendedProperty []ExtendedProperty
}
type ExtendedProperty struct {
Name string
Value string
}
type AvailabilityResponse struct {
XMLName xml.Name `xml:"AvailabilityResponse"`
Xmlns string `xml:"xmlns,attr"`
Result bool
Reason string
}
@@ -0,0 +1,31 @@
package storageservice
import (
"encoding/xml"
"testing"
)
func Test_StorageServiceKeysResponse_Unmarshal(t *testing.T) {
// from https://msdn.microsoft.com/en-us/library/azure/ee460785.aspx
response := []byte(`<?xml version="1.0" encoding="utf-8"?>
<StorageService xmlns="http://schemas.microsoft.com/windowsazure">
<Url>storage-service-url</Url>
<StorageServiceKeys>
<Primary>primary-key</Primary>
<Secondary>secondary-key</Secondary>
</StorageServiceKeys>
</StorageService>`)
keysResponse := GetStorageServiceKeysResponse{}
err := xml.Unmarshal(response, &keysResponse)
if err != nil {
t.Fatal(err)
}
if expected := "primary-key"; keysResponse.PrimaryKey != expected {
t.Fatalf("Expected %q but got %q", expected, keysResponse.PrimaryKey)
}
if expected := "secondary-key"; keysResponse.SecondaryKey != expected {
t.Fatalf("Expected %q but got %q", expected, keysResponse.SecondaryKey)
}
}
@@ -0,0 +1,87 @@
// Package testutils contains some test utilities for the Azure SDK
package testutils
import (
"encoding/base64"
"os"
"testing"
"github.com/Azure/azure-sdk-for-go/management"
)
// GetTestClient returns a management Client for testing. Expects
// AZSUBSCRIPTIONID and AZCERTDATA to be present in the environment. AZCERTDATA
// is the base64encoded binary representation of the PEM certificate data.
func GetTestClient(t *testing.T) management.Client {
subid := os.Getenv("AZSUBSCRIPTIONID")
certdata := os.Getenv("AZCERTDATA")
if subid == "" || certdata == "" {
t.Skip("AZSUBSCRIPTIONID or AZCERTDATA not set, skipping test")
}
cert, err := base64.StdEncoding.DecodeString(certdata)
if err != nil {
t.Fatal(err)
}
client, err := management.NewClient(subid, cert)
if err != nil {
t.Fatal(err)
}
return testClient{client, t}
}
type testClient struct {
management.Client
t *testing.T
}
func chop(d []byte) string {
const maxlen = 5000
s := string(d)
if len(s) > maxlen {
return s[:maxlen] + "..."
}
return s
}
func (l testClient) SendAzureGetRequest(url string) ([]byte, error) {
d, err := l.Client.SendAzureGetRequest(url)
logOperation(l.t, "GET", url, nil, d, "", err)
return d, err
}
func (l testClient) SendAzurePostRequest(url string, data []byte) (management.OperationID, error) {
oid, err := l.Client.SendAzurePostRequest(url, data)
logOperation(l.t, "POST", url, data, nil, oid, err)
return oid, err
}
func (l testClient) SendAzurePutRequest(url string, contentType string, data []byte) (management.OperationID, error) {
oid, err := l.Client.SendAzurePutRequest(url, contentType, data)
logOperation(l.t, "PUT", url, data, nil, oid, err)
return oid, err
}
func (l testClient) SendAzureDeleteRequest(url string) (management.OperationID, error) {
oid, err := l.Client.SendAzureDeleteRequest(url)
logOperation(l.t, "DELETE", url, nil, nil, oid, err)
return oid, err
}
func logOperation(t *testing.T, method, url string, requestData, responseData []byte, oid management.OperationID, err error) {
t.Logf("AZURE> %s %s\n", method, url)
if requestData != nil {
t.Logf(" >>> %s\n", chop(requestData))
}
if err != nil {
t.Logf(" <<< ERROR: %+v\n", err)
} else {
if responseData != nil {
t.Logf(" <<< %s\n", chop(responseData))
} else {
t.Logf(" <<< OperationID: %s\n", oid)
}
}
}
+11
View File
@@ -0,0 +1,11 @@
package management
import (
"io/ioutil"
"net/http"
)
func getResponseBody(response *http.Response) ([]byte, error) {
defer response.Body.Close()
return ioutil.ReadAll(response.Body)
}
+5
View File
@@ -0,0 +1,5 @@
package management
var (
sdkVersion = "v10.1.0-beta"
)
@@ -0,0 +1,328 @@
// Package virtualmachine provides a client for Virtual Machines.
package virtualmachine
import (
"encoding/xml"
"fmt"
"github.com/Azure/azure-sdk-for-go/management"
)
const (
azureDeploymentListURL = "services/hostedservices/%s/deployments"
azureDeploymentURL = "services/hostedservices/%s/deployments/%s"
azureListDeploymentsInSlotURL = "services/hostedservices/%s/deploymentslots/Production"
deleteAzureDeploymentURL = "services/hostedservices/%s/deployments/%s?comp=media"
azureAddRoleURL = "services/hostedservices/%s/deployments/%s/roles"
azureRoleURL = "services/hostedservices/%s/deployments/%s/roles/%s"
azureOperationsURL = "services/hostedservices/%s/deployments/%s/roleinstances/%s/Operations"
azureRoleSizeListURL = "rolesizes"
errParamNotSpecified = "Parameter %s is not specified."
)
//NewClient is used to instantiate a new VirtualMachineClient from an Azure client
func NewClient(client management.Client) VirtualMachineClient {
return VirtualMachineClient{client: client}
}
// CreateDeploymentOptions can be used to create a customized deployement request
type CreateDeploymentOptions struct {
DNSServers []DNSServer
LoadBalancers []LoadBalancer
ReservedIPName string
VirtualNetworkName string
}
// CreateDeployment creates a deployment and then creates a virtual machine
// in the deployment based on the specified configuration.
//
// https://msdn.microsoft.com/en-us/library/azure/jj157194.aspx
func (vm VirtualMachineClient) CreateDeployment(
role Role,
cloudServiceName string,
options CreateDeploymentOptions) (management.OperationID, error) {
req := DeploymentRequest{
Name: role.RoleName,
DeploymentSlot: "Production",
Label: role.RoleName,
RoleList: []Role{role},
DNSServers: options.DNSServers,
LoadBalancers: options.LoadBalancers,
ReservedIPName: options.ReservedIPName,
VirtualNetworkName: options.VirtualNetworkName,
}
data, err := xml.Marshal(req)
if err != nil {
return "", err
}
requestURL := fmt.Sprintf(azureDeploymentListURL, cloudServiceName)
return vm.client.SendAzurePostRequest(requestURL, data)
}
// GetDeploymentName queries an existing Azure cloud service for the name of the Deployment,
// if any, in its 'Production' slot (the only slot possible). If none exists, it returns empty
// string but no error
//
//https://msdn.microsoft.com/en-us/library/azure/ee460804.aspx
func (vm VirtualMachineClient) GetDeploymentName(cloudServiceName string) (string, error) {
var deployment DeploymentResponse
if cloudServiceName == "" {
return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
}
requestURL := fmt.Sprintf(azureListDeploymentsInSlotURL, cloudServiceName)
response, err := vm.client.SendAzureGetRequest(requestURL)
if err != nil {
if management.IsResourceNotFoundError(err) {
return "", nil
}
return "", err
}
err = xml.Unmarshal(response, &deployment)
if err != nil {
return "", err
}
return deployment.Name, nil
}
func (vm VirtualMachineClient) GetDeployment(cloudServiceName, deploymentName string) (DeploymentResponse, error) {
var deployment DeploymentResponse
if cloudServiceName == "" {
return deployment, fmt.Errorf(errParamNotSpecified, "cloudServiceName")
}
if deploymentName == "" {
return deployment, fmt.Errorf(errParamNotSpecified, "deploymentName")
}
requestURL := fmt.Sprintf(azureDeploymentURL, cloudServiceName, deploymentName)
response, azureErr := vm.client.SendAzureGetRequest(requestURL)
if azureErr != nil {
return deployment, azureErr
}
err := xml.Unmarshal(response, &deployment)
return deployment, err
}
func (vm VirtualMachineClient) DeleteDeployment(cloudServiceName, deploymentName string) (management.OperationID, error) {
if cloudServiceName == "" {
return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
}
if deploymentName == "" {
return "", fmt.Errorf(errParamNotSpecified, "deploymentName")
}
requestURL := fmt.Sprintf(deleteAzureDeploymentURL, cloudServiceName, deploymentName)
return vm.client.SendAzureDeleteRequest(requestURL)
}
func (vm VirtualMachineClient) GetRole(cloudServiceName, deploymentName, roleName string) (*Role, error) {
if cloudServiceName == "" {
return nil, fmt.Errorf(errParamNotSpecified, "cloudServiceName")
}
if deploymentName == "" {
return nil, fmt.Errorf(errParamNotSpecified, "deploymentName")
}
if roleName == "" {
return nil, fmt.Errorf(errParamNotSpecified, "roleName")
}
role := new(Role)
requestURL := fmt.Sprintf(azureRoleURL, cloudServiceName, deploymentName, roleName)
response, azureErr := vm.client.SendAzureGetRequest(requestURL)
if azureErr != nil {
return nil, azureErr
}
err := xml.Unmarshal(response, role)
if err != nil {
return nil, err
}
return role, nil
}
// AddRole adds a Virtual Machine to a deployment of Virtual Machines, where role name = VM name
// See https://msdn.microsoft.com/en-us/library/azure/jj157186.aspx
func (vm VirtualMachineClient) AddRole(cloudServiceName string, deploymentName string, role Role) (management.OperationID, error) {
if cloudServiceName == "" {
return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
}
if deploymentName == "" {
return "", fmt.Errorf(errParamNotSpecified, "deploymentName")
}
data, err := xml.Marshal(PersistentVMRole{Role: role})
if err != nil {
return "", err
}
requestURL := fmt.Sprintf(azureAddRoleURL, cloudServiceName, deploymentName)
return vm.client.SendAzurePostRequest(requestURL, data)
}
// UpdateRole updates the configuration of the specified virtual machine
// See https://msdn.microsoft.com/en-us/library/azure/jj157187.aspx
func (vm VirtualMachineClient) UpdateRole(cloudServiceName, deploymentName, roleName string, role Role) (management.OperationID, error) {
if cloudServiceName == "" {
return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
}
if deploymentName == "" {
return "", fmt.Errorf(errParamNotSpecified, "deploymentName")
}
if roleName == "" {
return "", fmt.Errorf(errParamNotSpecified, "roleName")
}
data, err := xml.Marshal(PersistentVMRole{Role: role})
if err != nil {
return "", err
}
requestURL := fmt.Sprintf(azureRoleURL, cloudServiceName, deploymentName, roleName)
return vm.client.SendAzurePutRequest(requestURL, "text/xml", data)
}
func (vm VirtualMachineClient) StartRole(cloudServiceName, deploymentName, roleName string) (management.OperationID, error) {
if cloudServiceName == "" {
return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
}
if deploymentName == "" {
return "", fmt.Errorf(errParamNotSpecified, "deploymentName")
}
if roleName == "" {
return "", fmt.Errorf(errParamNotSpecified, "roleName")
}
startRoleOperationBytes, err := xml.Marshal(StartRoleOperation{
OperationType: "StartRoleOperation",
})
if err != nil {
return "", err
}
requestURL := fmt.Sprintf(azureOperationsURL, cloudServiceName, deploymentName, roleName)
return vm.client.SendAzurePostRequest(requestURL, startRoleOperationBytes)
}
func (vm VirtualMachineClient) ShutdownRole(cloudServiceName, deploymentName, roleName string, postaction PostShutdownAction) (management.OperationID, error) {
if cloudServiceName == "" {
return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
}
if deploymentName == "" {
return "", fmt.Errorf(errParamNotSpecified, "deploymentName")
}
if roleName == "" {
return "", fmt.Errorf(errParamNotSpecified, "roleName")
}
shutdownRoleOperationBytes, err := xml.Marshal(ShutdownRoleOperation{
OperationType: "ShutdownRoleOperation",
PostShutdownAction: postaction,
})
if err != nil {
return "", err
}
requestURL := fmt.Sprintf(azureOperationsURL, cloudServiceName, deploymentName, roleName)
return vm.client.SendAzurePostRequest(requestURL, shutdownRoleOperationBytes)
}
func (vm VirtualMachineClient) RestartRole(cloudServiceName, deploymentName, roleName string) (management.OperationID, error) {
if cloudServiceName == "" {
return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
}
if deploymentName == "" {
return "", fmt.Errorf(errParamNotSpecified, "deploymentName")
}
if roleName == "" {
return "", fmt.Errorf(errParamNotSpecified, "roleName")
}
restartRoleOperationBytes, err := xml.Marshal(RestartRoleOperation{
OperationType: "RestartRoleOperation",
})
if err != nil {
return "", err
}
requestURL := fmt.Sprintf(azureOperationsURL, cloudServiceName, deploymentName, roleName)
return vm.client.SendAzurePostRequest(requestURL, restartRoleOperationBytes)
}
func (vm VirtualMachineClient) DeleteRole(cloudServiceName, deploymentName, roleName string, deleteVHD bool) (management.OperationID, error) {
if cloudServiceName == "" {
return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
}
if deploymentName == "" {
return "", fmt.Errorf(errParamNotSpecified, "deploymentName")
}
if roleName == "" {
return "", fmt.Errorf(errParamNotSpecified, "roleName")
}
requestURL := fmt.Sprintf(azureRoleURL, cloudServiceName, deploymentName, roleName)
if deleteVHD {
requestURL += "?comp=media"
}
return vm.client.SendAzureDeleteRequest(requestURL)
}
func (vm VirtualMachineClient) GetRoleSizeList() (RoleSizeList, error) {
roleSizeList := RoleSizeList{}
response, err := vm.client.SendAzureGetRequest(azureRoleSizeListURL)
if err != nil {
return roleSizeList, err
}
err = xml.Unmarshal(response, &roleSizeList)
return roleSizeList, err
}
// CaptureRole captures a VM role. If reprovisioningConfigurationSet is non-nil,
// the VM role is redeployed after capturing the image, otherwise, the original
// VM role is deleted.
//
// NOTE: an image resulting from this operation shows up in
// osimage.GetImageList() as images with Category "User".
func (vm VirtualMachineClient) CaptureRole(cloudServiceName, deploymentName, roleName, imageName, imageLabel string,
reprovisioningConfigurationSet *ConfigurationSet) (management.OperationID, error) {
if cloudServiceName == "" {
return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
}
if deploymentName == "" {
return "", fmt.Errorf(errParamNotSpecified, "deploymentName")
}
if roleName == "" {
return "", fmt.Errorf(errParamNotSpecified, "roleName")
}
if reprovisioningConfigurationSet != nil &&
!(reprovisioningConfigurationSet.ConfigurationSetType == ConfigurationSetTypeLinuxProvisioning ||
reprovisioningConfigurationSet.ConfigurationSetType == ConfigurationSetTypeWindowsProvisioning) {
return "", fmt.Errorf("ConfigurationSet type can only be WindowsProvisioningConfiguration or LinuxProvisioningConfiguration")
}
operation := CaptureRoleOperation{
OperationType: "CaptureRoleOperation",
PostCaptureAction: PostCaptureActionReprovision,
ProvisioningConfiguration: reprovisioningConfigurationSet,
TargetImageLabel: imageLabel,
TargetImageName: imageName,
}
if reprovisioningConfigurationSet == nil {
operation.PostCaptureAction = PostCaptureActionDelete
}
data, err := xml.Marshal(operation)
if err != nil {
return "", err
}
return vm.client.SendAzurePostRequest(fmt.Sprintf(azureOperationsURL, cloudServiceName, deploymentName, roleName), data)
}
@@ -0,0 +1,579 @@
package virtualmachine
import (
"encoding/xml"
"github.com/Azure/azure-sdk-for-go/management"
vmdisk "github.com/Azure/azure-sdk-for-go/management/virtualmachinedisk"
)
// VirtualMachineClient is used to perform operations on Azure Virtual Machines
type VirtualMachineClient struct {
client management.Client
}
// DeploymentRequest is the type for creating a deployment and Virtual Machine
// in the deployment based on the specified configuration. See
// https://msdn.microsoft.com/en-us/library/azure/jj157194.aspx
type DeploymentRequest struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure Deployment"`
// Required parameters:
Name string `` // Specifies a name for the deployment. The deployment name must be unique among other deployments for the cloud service.
DeploymentSlot string `` // Specifies the environment in which the Virtual Machine is to be deployed. The only allowable value is Production.
Label string `` // Specifies an identifier for the deployment. The label can be up to 100 characters long. The label can be used for tracking purposes.
RoleList []Role `xml:">Role"` // Contains information about the Virtual Machines that are to be deployed.
// Optional parameters:
VirtualNetworkName string `xml:",omitempty"` // Specifies the name of an existing virtual network to which the deployment will belong.
DNSServers []DNSServer `xml:"Dns>DnsServers>DnsServer,omitempty"` // Contains a list of DNS servers to associate with the Virtual Machine.
LoadBalancers []LoadBalancer `xml:">LoadBalancer,omitempty"` // Contains a list of internal load balancers that can be assigned to input endpoints.
ReservedIPName string `xml:",omitempty"` // Specifies the name of a reserved IP address that is to be assigned to the deployment.
}
// DeploymentResponse is the type for receiving deployment information
// See https://msdn.microsoft.com/en-us/library/azure/ee460804.aspx
type DeploymentResponse struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure Deployment"`
Name string
DeploymentSlot string
Status DeploymentStatus
Label string
URL string `xml:"Url"`
Configuration string
RoleInstanceList []RoleInstance `xml:">RoleInstance"`
UpgradeStatus UpgradeStatus
UpgradeDomainCount int
RoleList []Role `xml:">Role"`
SdkVersion string
Locked bool
RollbackAllowed bool
CreatedTime string
LastModifiedTime string
VirtualNetworkName string
DNSServers []DNSServer `xml:"Dns>DnsServers>DnsServer"`
LoadBalancers []LoadBalancer `xml:">LoadBalancer"`
ExtendedProperties []ExtendedProperty `xml:">ExtendedProperty"`
PersistentVMDowntime PersistentVMDowntime
VirtualIPs []VirtualIP `xml:">VirtualIP"`
ExtensionConfiguration string // cloud service extensions not fully implemented
ReservedIPName string
InternalDNSSuffix string `xml:"InternalDnsSuffix"`
}
type DeploymentStatus string
const (
DeploymentStatusRunning DeploymentStatus = "Running"
DeploymentStatusSuspended DeploymentStatus = "Suspended"
DeploymentStatusRunningTransitioning DeploymentStatus = "RunningTransitioning"
DeploymentStatusSuspendedTransitioning DeploymentStatus = "SuspendedTransitioning"
DeploymentStatusStarting DeploymentStatus = "Starting"
DeploymentStatusSuspending DeploymentStatus = "Suspending"
DeploymentStatusDeploying DeploymentStatus = "Deploying"
DeploymentStatusDeleting DeploymentStatus = "Deleting"
)
type RoleInstance struct {
RoleName string
InstanceName string
InstanceStatus InstanceStatus
ExtendedInstanceStatus string
InstanceUpgradeDomain int
InstanceFaultDomain int
InstanceSize string
InstanceStateDetails string
InstanceErrorCode string
IPAddress string `xml:"IpAddress"`
InstanceEndpoints []InstanceEndpoint `xml:">InstanceEndpoint"`
PowerState PowerState
HostName string
RemoteAccessCertificateThumbprint string
GuestAgentStatus string // todo: implement
ResourceExtensionStatusList []ResourceExtensionStatus `xml:">ResourceExtensionStatus"`
PublicIPs []PublicIP `xml:">PublicIP"`
}
type InstanceStatus string
const (
InstanceStatusUnknown = "Unknown"
InstanceStatusCreatingVM = "CreatingVM"
InstanceStatusStartingVM = "StartingVM"
InstanceStatusCreatingRole = "CreatingRole"
InstanceStatusStartingRole = "StartingRole"
InstanceStatusReadyRole = "ReadyRole"
InstanceStatusBusyRole = "BusyRole"
InstanceStatusStoppingRole = "StoppingRole"
InstanceStatusStoppingVM = "StoppingVM"
InstanceStatusDeletingVM = "DeletingVM"
InstanceStatusStoppedVM = "StoppedVM"
InstanceStatusRestartingRole = "RestartingRole"
InstanceStatusCyclingRole = "CyclingRole"
InstanceStatusFailedStartingRole = "FailedStartingRole"
InstanceStatusFailedStartingVM = "FailedStartingVM"
InstanceStatusUnresponsiveRole = "UnresponsiveRole"
InstanceStatusStoppedDeallocated = "StoppedDeallocated"
InstanceStatusPreparing = "Preparing"
)
type InstanceEndpoint struct {
Name string
Vip string
PublicPort int
LocalPort int
Protocol InputEndpointProtocol
}
type PowerState string
const (
PowerStateStarting PowerState = "Starting"
PowerStateStarted PowerState = "Started"
PowerStateStopping PowerState = "Stopping"
PowerStateStopped PowerState = "Stopped"
PowerStateUnknown PowerState = "Unknown"
)
type ResourceExtensionStatus struct {
HandlerName string
Version string
Status ResourceExtensionState
Code string
FormattedMessage FormattedMessage
ExtensionSettingStatus ExtensionSettingStatus
}
type ResourceExtensionState string
const (
ResourceExtensionStateInstalling ResourceExtensionState = "Installing"
ResourceExtensionStateReady ResourceExtensionState = "Ready"
ResourceExtensionStateNotReady ResourceExtensionState = "NotReady"
ResourceExtensionStateUnresponsive ResourceExtensionState = "Unresponsive"
)
type FormattedMessage struct {
Language string
Message string
}
type ExtensionSettingStatus struct {
Timestamp string
Name string
Operation string
Status ExtensionSettingState
Code string
FormattedMessage FormattedMessage
SubStatusList []SubStatus `xml:">SubStatus"`
}
type ExtensionSettingState string
const (
ExtensionSettingStateTransitioning ExtensionSettingState = "transitioning"
ExtensionSettingStateError ExtensionSettingState = "error"
ExtensionSettingStateSuccess ExtensionSettingState = "success"
ExtensionSettingStateWarning ExtensionSettingState = "warning"
)
type SubStatus struct {
Name string
Status ExtensionSettingState
FormattedMessage FormattedMessage
}
type UpgradeStatus struct {
UpgradeType UpgradeType
CurrentUpgradeDomainState CurrentUpgradeDomainState
CurrentUpgradeDomain int
}
type UpgradeType string
const (
UpgradeTypeAuto UpgradeType = "Auto"
UpgradeTypeManual UpgradeType = "Manual"
UpgradeTypeSimultaneous UpgradeType = "Simultaneous"
)
type CurrentUpgradeDomainState string
const (
CurrentUpgradeDomainStateBefore CurrentUpgradeDomainState = "Before"
CurrentUpgradeDomainStateDuring CurrentUpgradeDomainState = "During"
)
type ExtendedProperty struct {
Name string
Value string
}
type PersistentVMDowntime struct {
StartTime string
EndTime string
Status string
}
type VirtualIP struct {
Address string
IsReserved bool
ReservedIPName string
Type IPAddressType
}
// Role contains the configuration sets that are used to create virtual
// machines.
type Role struct {
RoleName string `xml:",omitempty"` // Specifies the name for the Virtual Machine.
RoleType string `xml:",omitempty"` // Specifies the type of role to use. For Virtual Machines, this must be PersistentVMRole.
ConfigurationSets []ConfigurationSet `xml:"ConfigurationSets>ConfigurationSet,omitempty"`
ResourceExtensionReferences *[]ResourceExtensionReference `xml:"ResourceExtensionReferences>ResourceExtensionReference,omitempty"`
VMImageName string `xml:",omitempty"` // Specifies the name of the VM Image that is to be used to create the Virtual Machine. If this element is used, the ConfigurationSets element is not used.
MediaLocation string `xml:",omitempty"` // Required if the Virtual Machine is being created from a published VM Image. Specifies the location of the VHD file that is created when VMImageName specifies a published VM Image.
AvailabilitySetName string `xml:",omitempty"` // Specifies the name of a collection of Virtual Machines. Virtual Machines specified in the same availability set are allocated to different nodes to maximize availability.
DataVirtualHardDisks []DataVirtualHardDisk `xml:"DataVirtualHardDisks>DataVirtualHardDisk,omitempty"` // Contains the parameters that are used to add a data disk to a Virtual Machine. If you are creating a Virtual Machine by using a VM Image, this element is not used.
OSVirtualHardDisk *OSVirtualHardDisk `xml:",omitempty"` // Contains the parameters that are used to create the operating system disk for a Virtual Machine. If you are creating a Virtual Machine by using a VM Image, this element is not used.
RoleSize string `xml:",omitempty"` // Specifies the size of the Virtual Machine. The default size is Small.
ProvisionGuestAgent bool `xml:",omitempty"` // Indicates whether the VM Agent is installed on the Virtual Machine. To run a resource extension in a Virtual Machine, this service must be installed.
VMImageInput *VMImageInput `xml:",omitempty"` // When a VM Image is used to create a new PersistentVMRole, the DiskConfigurations in the VM Image are used to create new Disks for the new VM. This parameter can be used to resize the newly created Disks to a larger size than the underlying DiskConfigurations in the VM Image.
UseCertAuth bool `xml:"-"`
CertPath string `xml:"-"`
}
// VMImageInput is for when a VM Image is used to create a new PersistantVMRole,
// the DiskConfigurations in the VM Image are used to create new Disks for the
// new VM. This parameter can be used to resize the newly created Disks to a
// larger size than the underlying DiskConfigurations in the VM Image.
type VMImageInput struct {
OSDiskConfiguration *OSDiskConfiguration `xml:",omitempty"` // This corresponds to the OSDiskConfiguration of the VM Image used to create a new role. The OSDiskConfiguration element is only available using version 2014-10-01 or higher.
DataDiskConfigurations []DataDiskConfiguration `xml:">DataDiskConfiguration,omitempty"` // This corresponds to the DataDiskConfigurations of the VM Image used to create a new role. The DataDiskConfigurations element is only available using version 2014-10-01 or higher.
}
// OSDiskConfiguration is used to resize the OS disk of a new VM created from a
// previously saved VM image.
type OSDiskConfiguration struct {
ResizedSizeInGB int
}
// DataDiskConfiguration is used to resize the data disks of a new VM created
// from a previously saved VM image.
type DataDiskConfiguration struct {
OSDiskConfiguration
Name string // The Name of the DataDiskConfiguration being referenced to.
}
// ResourceExtensionReference contains a collection of resource extensions that
// are to be installed on the Virtual Machine. The VM Agent must be installed on
// the Virtual Machine to install resource extensions. For more information, see
// Manage Extensions:
//
// https://msdn.microsoft.com/en-us/library/dn606311.aspx.
type ResourceExtensionReference struct {
ReferenceName string
Publisher string
Name string
Version string
ParameterValues []ResourceExtensionParameter `xml:"ResourceExtensionParameterValues>ResourceExtensionParameterValue,omitempty"`
State string
}
// ResourceExtensionParameter specifies the key, value, and type of a parameter that is passed to the
// resource extension when it is installed.
type ResourceExtensionParameter struct {
Key string
Value string
Type ResourceExtensionParameterType // If this value is set to Private, the parameter will not be returned by Get Deployment ().
}
type ResourceExtensionParameterType string
// Enum values for ResourceExtensionParameterType
const (
ResourceExtensionParameterTypePublic ResourceExtensionParameterType = "Public"
ResourceExtensionParameterTypePrivate ResourceExtensionParameterType = "Private"
)
// DataVirtualHardDisk specifies the properties that are used to create a data
// disk.
type DataVirtualHardDisk struct {
HostCaching vmdisk.HostCachingType `xml:",omitempty"` // Specifies the caching mode of the data disk. The default value is None.
DiskLabel string `xml:",omitempty"` // If the disk that is being added is already registered in the subscription, this element is ignored. If a new disk is being created, this element is used to provide a description of the disk. The value of this element is only obtained programmatically and does not appear in the Management Portal.
DiskName string `xml:",omitempty"` // If the disk that is being added is already registered in the subscription, this element is used to identify the disk to add. If a new disk and the associated VHD are being created by Azure, this element is not used and Azure assigns a unique name that is a combination of the deployment name, role name, and identifying number. The name of the disk must contain only alphanumeric characters, underscores, periods, or dashes. The name must not be longer than 256 characters. The name must not end with period or dash.
Lun int `xml:",omitempty"` // Specifies the Logical Unit Number (LUN) for the data disk. If the disk is the first disk that is added, this element is optional and the default value of 0 is used. If more than one disk is being added, this element is required. Valid LUN values are 0 through 31.
LogicalDiskSizeInGB int `xml:",omitempty"` // Specifies the size, in GB, of an empty disk to be attached to the Virtual Machine. If the disk that is being added is already registered in the subscription, this element is ignored. If the disk and VHD is being created by Azure as it is added, this element defines the size of the new disk.
MediaLink string `xml:",omitempty"` // If the disk that is being added is already registered in the subscription or the VHD for the disk already exists in blob storage, this element is ignored. If a VHD file does not exist in blob storage, this element defines the location of the new VHD that is created when the new disk is added.
SourceMediaLink string `xml:",omitempty"` // If the disk that is being added is already registered in the subscription or the VHD for the disk does not exist in blob storage, this element is ignored. If the VHD file exists in blob storage, this element defines the path to the VHD and a disk is registered from it and attached to the virtual machine.
}
// OSVirtualHardDisk specifies the properties that are used to create an OS
// disk.
type OSVirtualHardDisk struct {
HostCaching vmdisk.HostCachingType `xml:",omitempty"` // Specifies the caching mode of the data disk. The default value is None.
DiskLabel string `xml:",omitempty"` // If the disk that is being added is already registered in the subscription, this element is ignored. If a new disk is being created, this element is used to provide a description of the disk. The value of this element is only obtained programmatically and does not appear in the Management Portal.
DiskName string `xml:",omitempty"` // If the disk that is being added is already registered in the subscription, this element is used to identify the disk to add. If a new disk and the associated VHD are being created by Azure, this element is not used and Azure assigns a unique name that is a combination of the deployment name, role name, and identifying number. The name of the disk must contain only alphanumeric characters, underscores, periods, or dashes. The name must not be longer than 256 characters. The name must not end with period or dash.
MediaLink string `xml:",omitempty"` // If the disk that is being added is already registered in the subscription or the VHD for the disk already exists in blob storage, this element is ignored. If a VHD file does not exist in blob storage, this element defines the location of the new VHD that is created when the new disk is added.
SourceImageName string `xml:",omitempty"`
OS string `xml:",omitempty"`
RemoteSourceImageLink string `xml:",omitempty"` // Specifies a publicly accessible URI or a SAS URI to the location where an OS image is stored that is used to create the Virtual Machine. This location can be a different location than the user or platform image repositories in Azure. An image is always associated with a VHD, which is a .vhd file stored as a page blob in a storage account in Azure. If you specify the path to an image with this element, an associated VHD is created and you must use the MediaLink element to specify the location in storage where the VHD will be located. If this element is used, SourceImageName is not used.
ResizedSizeInGB int `xml:",omitempty"`
}
// ConfigurationSet specifies the configuration elements of the Virtual Machine.
// The type attribute is required to prevent the administrator password from
// being written to the operation history file.
type ConfigurationSet struct {
ConfigurationSetType ConfigurationSetType
// Windows provisioning:
ComputerName string `xml:",omitempty"` // Optional. Specifies the computer name for the Virtual Machine. If you do not specify a computer name, one is assigned that is a combination of the deployment name, role name, and identifying number. Computer names must be 1 to 15 characters long.
AdminPassword string `xml:",omitempty"` // Optional. Specifies the password to use for an administrator account on the Virtual Machine that is being created. If you are creating a Virtual Machine using an image, you must specify a name of an administrator account to be created on the machine using the AdminUsername element. You must use the AdminPassword element to specify the password of the administrator account that is being created. If you are creating a Virtual Machine using an existing specialized disk, this element is not used because the account should already exist on the disk.
EnableAutomaticUpdates bool `xml:",omitempty"` // Optional. Specifies whether automatic updates are enabled for the Virtual Machine. The default value is true.
TimeZone string `xml:",omitempty"` // Optional. Specifies the time zone for the Virtual Machine.
DomainJoin *DomainJoin `xml:",omitempty"` // Optional. Contains properties that define a domain to which the Virtual Machine will be joined.
StoredCertificateSettings []CertificateSetting `xml:">StoredCertificateSetting,omitempty"` // Optional. Contains a list of service certificates with which to provision to the new Virtual Machine.
WinRMListeners *[]WinRMListener `xml:"WinRM>Listeners>Listener,omitempty"` // Optional. Contains configuration settings for the Windows Remote Management service on the Virtual Machine. This enables remote Windows PowerShell.
AdminUsername string `xml:",omitempty"` // Optional. Specifies the name of the administrator account that is created to access the Virtual Machine. If you are creating a Virtual Machine using an image, you must specify a name of an administrator account to be created by using this element. You must use the AdminPassword element to specify the password of the administrator account that is being created. If you are creating a Virtual Machine using an existing specialized disk, this element is not used because the account should already exist on the disk.
AdditionalUnattendContent string `xml:",omitempty"` // Specifies additional base-64 encoded XML formatted information that can be included in the Unattend.xml file, which is used by Windows Setup.
// Linux provisioning:
HostName string `xml:",omitempty"` // Required. Specifies the host name for the Virtual Machine. Host names must be 1 to 64 characters long.
UserName string `xml:",omitempty"` // Required. Specifies the name of a user account to be created in the sudoer group of the Virtual Machine. User account names must be 1 to 32 characters long.
UserPassword string `xml:",omitempty"` // Required. Specifies the password for the user account. Passwords must be 6 to 72 characters long.
DisableSSHPasswordAuthentication string `xml:"DisableSshPasswordAuthentication,omitempty"` // Optional. Specifies whether SSH password authentication is disabled. By default this value is set to true.
SSH *SSH `xml:",omitempty"` // Optional. Specifies the SSH public keys and key pairs to use with the Virtual Machine.
// In WindowsProvisioningConfiguration: The base-64 encoded string is decoded to a binary array that is saved as a file on the Virtual Machine. The maximum length of the binary array is 65535 bytes. The file is saved to %SYSTEMDRIVE%\AzureData\CustomData.bin. If the file exists, it is overwritten. The security on directory is set to System:Full Control and Administrators:Full Control.
// In LinuxProvisioningConfiguration: The base-64 encoded string is located in the ovf-env.xml file on the ISO of the Virtual Machine. The file is copied to /var/lib/waagent/ovf-env.xml by the Azure Linux Agent. The Azure Linux Agent will also place the base-64 encoded data in /var/lib/waagent/CustomData during provisioning. The maximum length of the binary array is 65535 bytes.
CustomData string `xml:",omitempty"` // Specifies a base-64 encoded string of custom data.
// Network configuration:
InputEndpoints []InputEndpoint `xml:">InputEndpoint,omitempty"` // Optional in NetworkConfiguration. Contains a collection of external endpoints for the Virtual Machine.
SubnetNames []string `xml:">SubnetName,omitempty"` // Required if StaticVirtualNetworkIPAddress is specified; otherwise, optional in NetworkConfiguration. Contains a list of subnets to which the Virtual Machine will belong.
StaticVirtualNetworkIPAddress string `xml:",omitempty"` // Specifies the internal IP address for the Virtual Machine in a Virtual Network. If you specify this element, you must also specify the SubnetNames element with only one subnet defined. The IP address specified in this element must belong to the subnet that is defined in SubnetNames and it should not be the one of the first four IP addresses or the last IP address in the subnet. Deploying web roles or worker roles into a subnet that has Virtual Machines with StaticVirtualNetworkIPAddress defined is not supported.
NetworkSecurityGroup string `xml:",omitempty"` // Optional in NetworkConfiguration. Represents the name of the Network Security Group that will be associated with the Virtual Machine. Network Security Group must exist in the context of subscription and be created in same region to which the virtual machine will be deployed.
PublicIPs []PublicIP `xml:">PublicIP,omitempty"` // Contains a public IP address that can be used in addition to the default virtual IP address for the Virtual Machine.
}
type ConfigurationSetType string
// Enum values for ConfigurationSetType
const (
ConfigurationSetTypeWindowsProvisioning ConfigurationSetType = "WindowsProvisioningConfiguration"
ConfigurationSetTypeLinuxProvisioning ConfigurationSetType = "LinuxProvisioningConfiguration"
ConfigurationSetTypeNetwork ConfigurationSetType = "NetworkConfiguration"
)
// DomainJoin contains properties that define a domain to which the Virtual
// Machine will be joined.
type DomainJoin struct {
Credentials Credentials `xml:",omitempty"` // Specifies the credentials to use to join the Virtual Machine to the domain.
JoinDomain string `xml:",omitempty"` // Specifies the domain to join.
MachineObjectOU string `xml:",omitempty"` // Specifies the Lightweight Directory Access Protocol (LDAP) X 500-distinguished name of the organizational unit (OU) in which the computer account is created. This account is in Active Directory on a domain controller in the domain to which the computer is being joined.
}
// Credentials specifies the credentials to use to join the Virtual Machine to
// the domain. If Domain is not specified, Username must specify the user
// principal name (UPN) format (user@fully-qualified-DNS-domain) or the fully-
// qualified-DNS-domain\username format.
type Credentials struct {
Domain string // Specifies the name of the domain used to authenticate an account. The value is a fully qualified DNS domain.
Username string // Specifies a user name in the domain that can be used to join the domain.
Password string // Specifies the password to use to join the domain.
}
// CertificateSetting specifies the parameters for the certificate which to
// provision to the new Virtual Machine.
type CertificateSetting struct {
StoreLocation string // Required. Specifies the certificate store location on the Virtual Machine. The only supported value is "LocalMachine".
StoreName string // Required. Specifies the name of the certificate store from which the certificate is retrieved. For example, "My".
Thumbprint string // Required. Specifies the thumbprint of the certificate. The thumbprint must specify an existing service certificate.
}
// WinRMListener specifies the protocol and certificate information for a WinRM
// listener.
type WinRMListener struct {
Protocol WinRMProtocol // Specifies the protocol of listener.
CertificateThumbprint string `xml:",omitempty"` // Specifies the certificate thumbprint for the secure connection. If this value is not specified, a self-signed certificate is generated and used for the Virtual Machine.
}
type WinRMProtocol string
// Enum values for WinRMProtocol
const (
WinRMProtocolHTTP WinRMProtocol = "Http"
WinRMProtocolHTTPS WinRMProtocol = "Https"
)
// SSH specifies the SSH public keys and key pairs to use with the Virtual Machine.
type SSH struct {
PublicKeys []PublicKey `xml:">PublicKey"`
KeyPairs []KeyPair `xml:">KeyPair"`
}
// PublicKey specifies a public SSH key.
type PublicKey struct {
Fingerprint string // Specifies the SHA1 fingerprint of an X509 certificate associated with the cloud service and includes the SSH public key.
// Specifies the full path of a file, on the Virtual Machine, where the SSH public key is stored. If
// the file already exists, the specified key is appended to the file.
Path string // Usually /home/username/.ssh/authorized_keys
}
// KeyPair specifies an SSH keypair.
type KeyPair struct {
Fingerprint string // Specifies the SHA1 fingerprint of an X509 certificate that is associated with the cloud service and includes the SSH keypair.
// Specifies the full path of a file, on the virtual machine, which stores the SSH private key. The
// file is overwritten when multiple keys are written to it. The SSH public key is stored in the same
// directory and has the same name as the private key file with .pub suffix.
Path string // Usually /home/username/.ssh/id_rsa
}
// InputEndpoint specifies the properties that define an external endpoint for
// the Virtual Machine.
type InputEndpoint struct {
LocalPort int // Specifies the internal port on which the Virtual Machine is listening.
Name string // Specifies the name of the external endpoint.
Port int // Specifies the external port to use for the endpoint.
Protocol InputEndpointProtocol //Specifies the transport protocol for the endpoint.
Vip string `xml:",omitempty"`
}
type InputEndpointProtocol string
// Enum values for InputEndpointProtocol
const (
InputEndpointProtocolTCP InputEndpointProtocol = "TCP"
InputEndpointProtocolUDP InputEndpointProtocol = "UDP"
)
// PublicIP contains a public IP address that can be used in addition to default
// virtual IP address for the Virtual Machine.
type PublicIP struct {
Name string // Specifies the name of the public IP address.
Address string // Specifies the IP address.
IdleTimeoutInMinutes int `xml:",omitempty"` // Specifies the timeout for the TCP idle connection. The value can be set between 4 and 30 minutes. The default value is 4 minutes. This element is only used when the protocol is set to TCP.
}
// ServiceCertificate contains a certificate for adding it to a hosted service
type ServiceCertificate struct {
XMLName xml.Name `xml:"CertificateFile"`
Data string
CertificateFormat string
Password string `xml:",omitempty"`
}
// StartRoleOperation contains the information for starting a Role.
type StartRoleOperation struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure StartRoleOperation"`
OperationType string
}
type PostShutdownAction string
// Enum values for PostShutdownAction
const (
PostShutdownActionStopped PostShutdownAction = "Stopped"
PostShutdownActionStoppedDeallocated PostShutdownAction = "StoppedDeallocated"
)
// ShutdownRoleOperation contains the information for shutting down a Role.
type ShutdownRoleOperation struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure ShutdownRoleOperation"`
OperationType string
PostShutdownAction PostShutdownAction
}
// RestartRoleOperation contains the information for restarting a Role.
type RestartRoleOperation struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure RestartRoleOperation"`
OperationType string
}
// CaptureRoleOperation contains the information for capturing a Role
type CaptureRoleOperation struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure CaptureRoleOperation"`
OperationType string
PostCaptureAction PostCaptureAction
ProvisioningConfiguration *ConfigurationSet `xml:",omitempty"`
TargetImageLabel string
TargetImageName string
}
type PostCaptureAction string
// Enum values for PostCaptureAction
const (
PostCaptureActionDelete PostCaptureAction = "Delete"
PostCaptureActionReprovision PostCaptureAction = "Reprovision"
)
// RoleSizeList contains a list of the available role sizes
type RoleSizeList struct {
XMLName xml.Name `xml:"RoleSizes"`
RoleSizes []RoleSize `xml:"RoleSize"`
}
// RoleSize contains a detailed explanation of a role size
type RoleSize struct {
Name string
Label string
Cores int
MemoryInMb int
SupportedByWebWorkerRoles bool
SupportedByVirtualMachines bool
MaxDataDiskCount int
WebWorkerResourceDiskSizeInMb int
VirtualMachineResourceDiskSizeInMb int
}
// DNSServer contains the definition of a DNS server for virtual machine deployment
type DNSServer struct {
Name string
Address string
}
// LoadBalancer contains the definition of a load balancer for virtual machine deployment
type LoadBalancer struct {
Name string // Specifies the name of the internal load balancer.
Type IPAddressType `xml:"FrontendIpConfiguration>Type"` // Specifies the type of virtual IP address that is provided by the load balancer. The only allowable value is Private.
SubnetName string `xml:"FrontendIpConfiguration>SubnetName,omitempty"` // Required if the deployment exists in a virtual network and a StaticVirtualNetworkIPAddress is assigned. Specifies the subnet of the virtual network that the load balancer uses. The virtual IP address that is managed by the load balancer is contained in this subnet.
StaticVirtualNetworkIPAddress string `xml:"FrontendIpConfiguration>StaticVirtualNetworkIPAddress,omitempty"` // Specifies a specific virtual IP address that the load balancer uses from the subnet in the virtual network.
}
type IPAddressType string
// Enum values for IPAddressType
const (
IPAddressTypePrivate IPAddressType = "Private" // Only allowed value (currently) for IPAddressType
)
type ResourceExtensions struct {
List []ResourceExtension `xml:"ResourceExtension"`
}
type ResourceExtension struct {
Publisher string
Name string
Version string
Label string
Description string
PublicConfigurationSchema string
PrivateConfigurationSchema string
SampleConfig string
ReplicationCompleted string
Eula string
PrivacyURI string `xml:"PrivacyUri"`
HomepageURI string `xml:"HomepageUri"`
IsJSONExtension bool `xml:"IsJsonExtension"`
IsInternalExtension bool
DisallowMajorVersionUpgrade bool
CompanyName string
SupportedOS string
PublishedDate string
}
type PersistentVMRole struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure PersistentVMRole"`
Role
}
@@ -0,0 +1,299 @@
package virtualmachine
import (
"encoding/xml"
"testing"
)
func TestDocumentedDeploymentRequest(t *testing.T) {
// xml based on https://msdn.microsoft.com/en-us/library/azure/jj157194.aspx
// fixed typos, replaced strongly typed fields with values of correct type
xmlString := `<Deployment xmlns="http://schemas.microsoft.com/windowsazure" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Name>name-of-deployment</Name>
<DeploymentSlot>deployment-environment</DeploymentSlot>
<Label>identifier-of-deployment</Label>
<RoleList>
<Role>
<RoleName>name-of-the-virtual-machine</RoleName>
<RoleType>PersistentVMRole</RoleType>
<ConfigurationSets>
<ConfigurationSet i:type="WindowsProvisioningConfigurationSet">
<ConfigurationSetType>WindowsProvisioningConfiguration</ConfigurationSetType>
<ComputerName>name-of-computer</ComputerName>
<AdminPassword>administrator-password</AdminPassword>
<EnableAutomaticUpdates>true</EnableAutomaticUpdates>
<TimeZone>time-zone</TimeZone>
<DomainJoin>
<Credentials>
<Domain>domain-to-join</Domain>
<Username>user-name-in-the-domain</Username>
<Password>password-for-the-user-name</Password>
</Credentials>
<JoinDomain>domain-to-join</JoinDomain>
<MachineObjectOU>distinguished-name-of-the-ou</MachineObjectOU>
</DomainJoin>
<StoredCertificateSettings>
<CertificateSetting>
<StoreLocation>LocalMachine</StoreLocation>
<StoreName>name-of-store-on-the-machine</StoreName>
<Thumbprint>certificate-thumbprint</Thumbprint>
</CertificateSetting>
</StoredCertificateSettings>
<WinRM>
<Listeners>
<Listener>
<Protocol>listener-protocol</Protocol>
</Listener>
<Listener>
<CertificateThumbprint>certificate-thumbprint</CertificateThumbprint>
<Protocol>listener-protocol</Protocol>
</Listener>
</Listeners>
</WinRM>
<AdminUsername>name-of-administrator-account</AdminUsername>
<CustomData>base-64-encoded-data</CustomData>
<AdditionalUnattendContent>
<Passes>
<UnattendPass>
<PassName>name-of-pass</PassName>
<Components>
<UnattendComponent>
<ComponentName>name-of-component</ComponentName>
<ComponentSettings>
<ComponentSetting>
<SettingName>name-of-setting</SettingName>
<Content>base-64-encoded-XML-content</Content>
</ComponentSetting>
</ComponentSettings>
</UnattendComponent>
</Components>
</UnattendPass>
</Passes>
</AdditionalUnattendContent>
</ConfigurationSet>
<ConfigurationSet i:type="LinuxProvisioningConfigurationSet">
<ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType>
<HostName>host-name-for-the-virtual-machine</HostName>
<UserName>new-user-name</UserName>
<UserPassword>password-for-the-new-user</UserPassword>
<DisableSshPasswordAuthentication>true</DisableSshPasswordAuthentication>
<SSH>
<PublicKeys>
<PublicKey>
<FingerPrint>certificate-fingerprint</FingerPrint>
<Path>SSH-public-key-storage-location</Path>
</PublicKey>
</PublicKeys>
<KeyPairs>
<KeyPair>
<FingerPrint>certificate-fingerprint</FingerPrint>
<Path>SSH-public-key-storage-location</Path>
</KeyPair>
</KeyPairs>
</SSH>
<CustomData>base-64-encoded-data</CustomData>
</ConfigurationSet>
<ConfigurationSet>
<ConfigurationSetType>NetworkConfiguration</ConfigurationSetType>
<InputEndpoints>
<InputEndpoint>
<LoadBalancedEndpointSetName>name-of-load-balanced-set</LoadBalancedEndpointSetName>
<LocalPort>22</LocalPort>
<Name>ZZH</Name>
<Port>33</Port>
<LoadBalancerProbe>
<Path>/probe/me</Path>
<Port>80</Port>
<Protocol>http</Protocol>
<IntervalInSeconds>30</IntervalInSeconds>
<TimeoutInSeconds>5</TimeoutInSeconds>
</LoadBalancerProbe>
<Protocol>endpoint-protocol</Protocol>
<EnableDirectServerReturn>enable-direct-server-return</EnableDirectServerReturn>
<EndpointACL>
<Rules>
<Rule>
<Order>priority-of-the-rule</Order>
<Action>permit-rule</Action>
<RemoteSubnet>subnet-of-the-rule</RemoteSubnet>
<Description>description-of-the-rule</Description>
</Rule>
</Rules>
</EndpointACL>
<LoadBalancerName>name-of-internal-loadbalancer</LoadBalancerName>
<IdleTimeoutInMinutes>9</IdleTimeoutInMinutes>
</InputEndpoint>
</InputEndpoints>
<SubnetNames>
<SubnetName>name-of-subnet</SubnetName>
</SubnetNames>
<StaticVirtualNetworkIPAddress>ip-address</StaticVirtualNetworkIPAddress>
<PublicIPs>
<PublicIP>
<Name>name-of-public-ip</Name>
<IdleTimeoutInMinutes>11</IdleTimeoutInMinutes>
</PublicIP>
</PublicIPs>
</ConfigurationSet>
</ConfigurationSets>
<ResourceExtensionReferences>
<ResourceExtensionReference>
<ReferenceName>name-of-reference</ReferenceName>
<Publisher>name-of-publisher</Publisher>
<Name>name-of-extension</Name>
<Version>version-of-extension</Version>
<ResourceExtensionParameterValues>
<ResourceExtensionParameterValue>
<Key>name-of-parameter-key</Key>
<Value>parameter-value</Value>
<Type>type-of-parameter</Type>
</ResourceExtensionParameterValue>
</ResourceExtensionParameterValues>
<State>state-of-resource</State>
<Certificates>
<Certificate>
<Thumbprint>certificate-thumbprint</Thumbprint>
<ThumbprintAlgorithm>certificate-algorithm</ThumbprintAlgorithm>
</Certificate>
</Certificates>
</ResourceExtensionReference>
</ResourceExtensionReferences>
<VMImageName>name-of-vm-image</VMImageName>
<MediaLocation>path-to-vhd</MediaLocation>
<AvailabilitySetName>name-of-availability-set</AvailabilitySetName>
<DataVirtualHardDisks>
<DataVirtualHardDisk>
<HostCaching>caching-mode</HostCaching>
<DiskLabel>label-of-data-disk</DiskLabel>
<DiskName>name-of-disk</DiskName>
<Lun>0</Lun>
<LogicalDiskSizeInGB>50</LogicalDiskSizeInGB>
<MediaLink>path-to-vhd</MediaLink>
</DataVirtualHardDisk>
</DataVirtualHardDisks>
<OSVirtualHardDisk>
<HostCaching>caching-mode</HostCaching>
<DiskLabel>label-of-operating-system-disk</DiskLabel>
<DiskName>name-of-disk</DiskName>
<MediaLink>path-to-vhd</MediaLink>
<SourceImageName>name-of-source-image</SourceImageName>
<OS>operating-system-of-image</OS>
<RemoteSourceImageLink>path-to-source-image</RemoteSourceImageLink>
<ResizedSizeInGB>125</ResizedSizeInGB>
</OSVirtualHardDisk>
<RoleSize>size-of-virtual-machine</RoleSize>
<ProvisionGuestAgent>true</ProvisionGuestAgent>
<VMImageInput>
<OSDiskConfiguration>
<ResizedSizeInGB>126</ResizedSizeInGB>
</OSDiskConfiguration>
<DataDiskConfigurations>
<DataDiskConfiguration>
<Name>disk-name</Name>
<ResizedSizeInGB>127</ResizedSizeInGB>
</DataDiskConfiguration>
</DataDiskConfigurations>
</VMImageInput>
</Role>
</RoleList>
<VirtualNetworkName>name-of-virtual-network</VirtualNetworkName>
<Dns>
<DnsServers>
<DnsServer>
<Name>dns-name</Name>
<Address>dns-ip-address</Address>
</DnsServer>
</DnsServers>
</Dns>
<ReservedIPName>name-of-reserved-ip</ReservedIPName>
<LoadBalancers>
<LoadBalancer>
<Name>name-of-internal-load-balancer</Name>
<FrontendIpConfiguration>
<Type>Private</Type>
<SubnetName>name-of-subnet</SubnetName>
<StaticVirtualNetworkIPAddress>static-ip-address</StaticVirtualNetworkIPAddress>
</FrontendIpConfiguration>
</LoadBalancer>
</LoadBalancers>
</Deployment>`
deployment := DeploymentRequest{}
if err := xml.Unmarshal([]byte(xmlString), &deployment); err != nil {
t.Fatal(err)
}
if deployment.Name != "name-of-deployment" {
t.Fatalf("Expected deployment.Name=\"name-of-deployment\", but got \"%s\"",
deployment.Name)
}
// ======
t.Logf("deployment.RoleList[0]: %+v", deployment.RoleList[0])
if expected := "name-of-the-virtual-machine"; deployment.RoleList[0].RoleName != expected {
t.Fatalf("Expected deployment.RoleList[0].RoleName=%v, but got %v", expected, deployment.RoleList[0].RoleName)
}
// ======
t.Logf("deployment.DNSServers[0]: %+v", deployment.DNSServers[0])
if deployment.DNSServers[0].Name != "dns-name" {
t.Fatalf("Expected deployment.DNSServers[0].Name=\"dns-name\", but got \"%s\"",
deployment.DNSServers[0].Name)
}
// ======
t.Logf("deployment.LoadBalancers[0]: %+v", deployment.LoadBalancers[0])
if deployment.LoadBalancers[0].Name != "name-of-internal-load-balancer" {
t.Fatalf("Expected deployment.LoadBalancers[0].Name=\"name-of-internal-load-balancer\", but got \"%s\"",
deployment.LoadBalancers[0].Name)
}
if deployment.LoadBalancers[0].Type != IPAddressTypePrivate {
t.Fatalf("Expected deployment.LoadBalancers[0].Type=IPAddressTypePrivate, but got \"%s\"",
deployment.LoadBalancers[0].Type)
}
if deployment.LoadBalancers[0].StaticVirtualNetworkIPAddress != "static-ip-address" {
t.Fatalf("Expected deployment.LoadBalancers[0].StaticVirtualNetworkIPAddress=\"static-ip-address\", but got \"%s\"",
deployment.LoadBalancers[0].StaticVirtualNetworkIPAddress)
}
// ======
extensionReferences := (*deployment.RoleList[0].ResourceExtensionReferences)
t.Logf("(*deployment.RoleList[0].ResourceExtensionReferences)[0]: %+v", extensionReferences[0])
if extensionReferences[0].Name != "name-of-extension" {
t.Fatalf("Expected (*deployment.RoleList[0].ResourceExtensionReferences)[0].Name=\"name-of-extension\", but got \"%s\"",
extensionReferences[0].Name)
}
if extensionReferences[0].ParameterValues[0].Key != "name-of-parameter-key" {
t.Fatalf("Expected (*deployment.RoleList[0].ResourceExtensionReferences)[0].ParameterValues[0].Key=\"name-of-parameter-key\", but got %v",
extensionReferences[0].ParameterValues[0].Key)
}
// ======
if deployment.RoleList[0].VMImageInput.DataDiskConfigurations[0].ResizedSizeInGB != 127 {
t.Fatalf("Expected deployment.RoleList[0].VMImageInput.DataDiskConfigurations[0].ResizedSizeInGB=127, but got %v",
deployment.RoleList[0].VMImageInput.DataDiskConfigurations[0].ResizedSizeInGB)
}
// ======
winRMlisteners := *deployment.RoleList[0].ConfigurationSets[0].WinRMListeners
if string(winRMlisteners[0].Protocol) != "listener-protocol" {
t.Fatalf("Expected winRMlisteners[0].Protocol to be listener-protocol, but got %s",
string(winRMlisteners[0].Protocol))
}
winRMlisteners2 := *deployment.RoleList[0].ConfigurationSets[0].WinRMListeners
if winRMlisteners2[1].CertificateThumbprint != "certificate-thumbprint" {
t.Fatalf("Expected winRMlisteners2[1].CertificateThumbprint to be certificate-thumbprint, but got %s",
winRMlisteners2[1].CertificateThumbprint)
}
}
@@ -0,0 +1,25 @@
package virtualmachine
import (
"encoding/xml"
)
const (
azureResourceExtensionsURL = "services/resourceextensions"
)
// GetResourceExtensions lists the resource extensions that are available to add
// to a virtual machine.
//
// See https://msdn.microsoft.com/en-us/library/azure/dn495441.aspx
func (c VirtualMachineClient) GetResourceExtensions() (extensions []ResourceExtension, err error) {
data, err := c.client.SendAzureGetRequest(azureResourceExtensionsURL)
if err != nil {
return extensions, err
}
var response ResourceExtensions
err = xml.Unmarshal(data, &response)
extensions = response.List
return
}
@@ -0,0 +1,27 @@
package virtualmachine
import (
"testing"
"github.com/Azure/azure-sdk-for-go/management/testutils"
)
func TestAzureGetResourceExtensions(t *testing.T) {
client := testutils.GetTestClient(t)
list, err := NewClient(client).GetResourceExtensions()
if err != nil {
t.Fatal(err)
}
t.Logf("Found %d extensions", len(list))
if len(list) == 0 {
t.Fatal("Huh, no resource extensions at all? Something must be wrong.")
}
for _, extension := range list {
if extension.Name == "" {
t.Fatalf("Resource with empty name? Something must have gone wrong with serialization: %+v", extension)
}
}
}
@@ -0,0 +1,230 @@
// Package virtualmachinedisk provides a client for Virtual Machine Disks.
package virtualmachinedisk
import (
"encoding/xml"
"fmt"
"github.com/Azure/azure-sdk-for-go/management"
)
const (
addDataDiskURL = "services/hostedservices/%s/deployments/%s/roles/%s/DataDisks"
addDiskURL = "services/disks"
deleteDataDiskURL = "services/hostedservices/%s/deployments/%s/roles/%s/DataDisks/%d"
deleteDiskURL = "services/disks/%s"
getDataDiskURL = "services/hostedservices/%s/deployments/%s/roles/%s/DataDisks/%d"
getDiskURL = "services/disks/%s"
listDisksURL = "services/disks"
updateDataDiskURL = "services/hostedservices/%s/deployments/%s/roles/%s/DataDisks/%d"
updateDiskURL = "services/disks/%s"
errParamNotSpecified = "Parameter %s is not specified."
)
//NewClient is used to instantiate a new DiskClient from an Azure client
func NewClient(client management.Client) DiskClient {
return DiskClient{client: client}
}
// AddDataDisk adds a data disk to a Virtual Machine
//
// https://msdn.microsoft.com/en-us/library/azure/jj157199.aspx
func (c DiskClient) AddDataDisk(
service string,
deployment string,
role string,
params CreateDataDiskParameters) (management.OperationID, error) {
if service == "" {
return "", fmt.Errorf(errParamNotSpecified, "service")
}
if deployment == "" {
return "", fmt.Errorf(errParamNotSpecified, "deployment")
}
if role == "" {
return "", fmt.Errorf(errParamNotSpecified, "role")
}
requestURL := fmt.Sprintf(addDataDiskURL, service, deployment, role)
req, err := xml.Marshal(params)
if err != nil {
return "", err
}
return c.client.SendAzurePostRequest(requestURL, req)
}
// AddDisk adds an operating system disk or data disk to the user image repository
//
// https://msdn.microsoft.com/en-us/library/azure/jj157178.aspx
func (c DiskClient) AddDisk(params CreateDiskParameters) (management.OperationID, error) {
req, err := xml.Marshal(params)
if err != nil {
return "", err
}
return c.client.SendAzurePostRequest(addDiskURL, req)
}
// DeleteDataDisk removes the specified data disk from a Virtual Machine
//
// https://msdn.microsoft.com/en-us/library/azure/jj157179.aspx
func (c DiskClient) DeleteDataDisk(
service string,
deployment string,
role string,
lun int,
deleteVHD bool) (management.OperationID, error) {
if service == "" {
return "", fmt.Errorf(errParamNotSpecified, "service")
}
if deployment == "" {
return "", fmt.Errorf(errParamNotSpecified, "deployment")
}
if role == "" {
return "", fmt.Errorf(errParamNotSpecified, "role")
}
requestURL := fmt.Sprintf(deleteDataDiskURL, service, deployment, role, lun)
if deleteVHD {
requestURL += "?comp=media"
}
return c.client.SendAzureDeleteRequest(requestURL)
}
// DeleteDisk deletes the specified data or operating system disk from the image
// repository that is associated with the specified subscription
//
// https://msdn.microsoft.com/en-us/library/azure/jj157200.aspx
func (c DiskClient) DeleteDisk(name string, deleteVHD bool) error {
if name == "" {
return fmt.Errorf(errParamNotSpecified, "name")
}
requestURL := fmt.Sprintf(deleteDiskURL, name)
if deleteVHD {
requestURL += "?comp=media"
}
_, err := c.client.SendAzureDeleteRequest(requestURL) // request is handled synchronously
return err
}
// GetDataDisk retrieves the specified data disk from a Virtual Machine
//
// https://msdn.microsoft.com/en-us/library/azure/jj157180.aspx
func (c DiskClient) GetDataDisk(
service string,
deployment string,
role string,
lun int) (DataDiskResponse, error) {
var response DataDiskResponse
if service == "" {
return response, fmt.Errorf(errParamNotSpecified, "service")
}
if deployment == "" {
return response, fmt.Errorf(errParamNotSpecified, "deployment")
}
if role == "" {
return response, fmt.Errorf(errParamNotSpecified, "role")
}
requestURL := fmt.Sprintf(getDataDiskURL, service, deployment, role, lun)
data, err := c.client.SendAzureGetRequest(requestURL)
if err != nil {
return response, err
}
err = xml.Unmarshal(data, &response)
return response, err
}
// GetDisk retrieves information about the specified disk
//
// https://msdn.microsoft.com/en-us/library/azure/dn775053.aspx
func (c DiskClient) GetDisk(name string) (DiskResponse, error) {
var response DiskResponse
if name == "" {
return response, fmt.Errorf(errParamNotSpecified, "name")
}
requestURL := fmt.Sprintf(getDiskURL, name)
data, err := c.client.SendAzureGetRequest(requestURL)
if err != nil {
return response, err
}
err = xml.Unmarshal(data, &response)
return response, err
}
// ListDisks retrieves a list of the disks in the image repository that is associated
// with the specified subscription
//
// https://msdn.microsoft.com/en-us/library/azure/jj157176.aspx
func (c DiskClient) ListDisks() (ListDiskResponse, error) {
var response ListDiskResponse
data, err := c.client.SendAzureGetRequest(listDisksURL)
if err != nil {
return response, err
}
err = xml.Unmarshal(data, &response)
return response, err
}
// UpdateDataDisk updates the configuration of the specified data disk that is
// attached to the specified Virtual Machine
//
// https://msdn.microsoft.com/en-us/library/azure/jj157190.aspx
func (c DiskClient) UpdateDataDisk(
service string,
deployment string,
role string,
lun int,
params UpdateDataDiskParameters) (management.OperationID, error) {
if service == "" {
return "", fmt.Errorf(errParamNotSpecified, "service")
}
if deployment == "" {
return "", fmt.Errorf(errParamNotSpecified, "deployment")
}
if role == "" {
return "", fmt.Errorf(errParamNotSpecified, "role")
}
requestURL := fmt.Sprintf(updateDataDiskURL, service, deployment, role, lun)
req, err := xml.Marshal(params)
if err != nil {
return "", err
}
return c.client.SendAzurePutRequest(requestURL, "", req)
}
// UpdateDisk updates the label of an existing disk in the image repository that is
// associated with the specified subscription
//
// https://msdn.microsoft.com/en-us/library/azure/jj157205.aspx
func (c DiskClient) UpdateDisk(
name string,
params UpdateDiskParameters) (management.OperationID, error) {
if name == "" {
return "", fmt.Errorf(errParamNotSpecified, "name")
}
requestURL := fmt.Sprintf(updateDiskURL, name)
req, err := xml.Marshal(params)
if err != nil {
return "", err
}
return c.client.SendAzurePutRequest(requestURL, "", req)
}
@@ -0,0 +1,134 @@
package virtualmachinedisk
import (
"encoding/xml"
"github.com/Azure/azure-sdk-for-go/management"
)
// DiskClient is used to perform operations on Azure Disks
type DiskClient struct {
client management.Client
}
// CreateDiskParameters represents a disk
//
// https://msdn.microsoft.com/en-us/library/azure/jj157188.aspx
type CreateDiskParameters struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure Disk"`
OS OperatingSystemType `xml:",omitempty"`
Label string
MediaLink string `xml:",omitempty"`
Name string
}
// UpdateDiskParameters represents a disk
//
// https://msdn.microsoft.com/en-us/library/azure/jj157188.aspx
type UpdateDiskParameters struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure Disk"`
Label string `xml:",omitempty"`
Name string
ResizedSizeInGB int `xml:",omitempty"`
}
// ListDiskResponse represents a disk
//
// https://msdn.microsoft.com/en-us/library/azure/jj157188.aspx
type ListDiskResponse struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure Disks"`
Disk []DiskResponse
}
// DiskResponse represents a disk
//
// https://msdn.microsoft.com/en-us/library/azure/jj157188.aspx
type DiskResponse struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure Disk"`
AffinityGroup string
AttachedTo Resource
IsCorrupted bool
OS OperatingSystemType
Location string
LogicalDiskSizeInGB int
MediaLink string
Name string
SourceImageName string
CreatedTime string
IOType IOType
}
// Resource describes the resource details a disk is currently attached to
type Resource struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure AttachedTo"`
DeploymentName string
HostedServiceName string
RoleName string
}
// IOType represents an IO type
type IOType string
// These constants represent the possible IO types
const (
IOTypeProvisioned IOType = "Provisioned"
IOTypeStandard IOType = "Standard"
)
// OperatingSystemType represents an operating system type
type OperatingSystemType string
// These constants represent the valid operating system types
const (
OperatingSystemTypeNull OperatingSystemType = "NULL"
OperatingSystemTypeLinux OperatingSystemType = "Linux"
OperatingSystemTypeWindows OperatingSystemType = "Windows"
)
// CreateDataDiskParameters represents a data disk
//
// https://msdn.microsoft.com/en-us/library/azure/jj157188.aspx
type CreateDataDiskParameters struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure DataVirtualHardDisk"`
HostCaching HostCachingType `xml:",omitempty"`
DiskLabel string `xml:",omitempty"`
DiskName string `xml:",omitempty"`
Lun int `xml:",omitempty"`
LogicalDiskSizeInGB int `xml:",omitempty"`
MediaLink string
SourceMediaLink string `xml:",omitempty"`
}
// UpdateDataDiskParameters represents a data disk
//
// https://msdn.microsoft.com/en-us/library/azure/jj157188.aspx
type UpdateDataDiskParameters struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure DataVirtualHardDisk"`
HostCaching HostCachingType `xml:",omitempty"`
DiskName string
Lun int
MediaLink string
}
// DataDiskResponse represents a data disk
//
// https://msdn.microsoft.com/en-us/library/azure/jj157188.aspx
type DataDiskResponse struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure DataVirtualHardDisk"`
HostCaching HostCachingType
DiskLabel string
DiskName string
Lun int
LogicalDiskSizeInGB int
MediaLink string
}
// HostCachingType represents a host caching type
type HostCachingType string
// These constants represent the valid host caching types
const (
HostCachingTypeNone HostCachingType = "None"
HostCachingTypeReadOnly HostCachingType = "ReadOnly"
HostCachingTypeReadWrite HostCachingType = "ReadWrite"
)
@@ -0,0 +1,110 @@
// Package virtualmachineimage provides a client for Virtual Machine Images.
package virtualmachineimage
import (
"encoding/xml"
"fmt"
"net/url"
"github.com/Azure/azure-sdk-for-go/management"
)
const (
azureImageListURL = "services/vmimages"
azureImageDeleteURLformat = "services/vmimages/%s"
azureRoleOperationsURL = "services/hostedservices/%s/deployments/%s/roleinstances/%s/operations"
errParamNotSpecified = "Parameter %s is not specified."
)
//NewClient is used to instantiate a new Client from an Azure client
func NewClient(client management.Client) Client {
return Client{client}
}
//ListVirtualMachineImages lists the available VM images, filtered by the optional parameters.
//See https://msdn.microsoft.com/en-us/library/azure/dn499770.aspx
func (c Client) ListVirtualMachineImages(parameters ListParameters) (ListVirtualMachineImagesResponse, error) {
var imageList ListVirtualMachineImagesResponse
listURL := azureImageListURL
v := url.Values{}
if parameters.Location != "" {
v.Add("location", parameters.Location)
}
if parameters.Publisher != "" {
v.Add("publisher", parameters.Publisher)
}
if parameters.Category != "" {
v.Add("category", parameters.Category)
}
query := v.Encode()
if query != "" {
listURL = listURL + "?" + query
}
response, err := c.SendAzureGetRequest(listURL)
if err != nil {
return imageList, err
}
err = xml.Unmarshal(response, &imageList)
return imageList, err
}
//DeleteVirtualMachineImage deletes the named VM image. If deleteVHDs is specified,
//the referenced OS and data disks are also deleted.
//See https://msdn.microsoft.com/en-us/library/azure/dn499769.aspx
func (c Client) DeleteVirtualMachineImage(name string, deleteVHDs bool) error {
if name == "" {
return fmt.Errorf(errParamNotSpecified, "name")
}
uri := fmt.Sprintf(azureImageDeleteURLformat, name)
if deleteVHDs {
uri = uri + "?comp=media"
}
_, err := c.SendAzureDeleteRequest(uri) // delete is synchronous for this operation
return err
}
type ListParameters struct {
Location string
Publisher string
Category string
}
const CategoryUser = "User"
//Capture captures a VM into a VM image. The VM has to be shut down previously.
//See https://msdn.microsoft.com/en-us/library/azure/dn499768.aspx
func (c Client) Capture(cloudServiceName, deploymentName, roleName string,
name, label string, osState OSState, parameters CaptureParameters) (management.OperationID, error) {
if cloudServiceName == "" {
return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
}
if deploymentName == "" {
return "", fmt.Errorf(errParamNotSpecified, "deploymentName")
}
if roleName == "" {
return "", fmt.Errorf(errParamNotSpecified, "roleName")
}
request := CaptureRoleAsVMImageOperation{
VMImageName: name,
VMImageLabel: label,
OSState: osState,
CaptureParameters: parameters,
}
data, err := xml.Marshal(request)
if err != nil {
return "", err
}
return c.SendAzurePostRequest(fmt.Sprintf(azureRoleOperationsURL,
cloudServiceName, deploymentName, roleName), data)
}
@@ -0,0 +1,95 @@
package virtualmachineimage
import (
"encoding/xml"
"github.com/Azure/azure-sdk-for-go/management"
vmdisk "github.com/Azure/azure-sdk-for-go/management/virtualmachinedisk"
)
// Client is used to perform operations on Azure VM Images.
type Client struct {
management.Client
}
type ListVirtualMachineImagesResponse struct {
VMImages []VMImage `xml:"VMImage"`
}
type VMImage struct {
Name string // Specifies the name of the image.
Label string // Specifies an identifier for the image.
Category string // Specifies the repository classification of the image. All user images have the category User.
Description string // Specifies the description of the image.
OSDiskConfiguration OSDiskConfiguration // Specifies configuration information for the operating system disk that is associated with the image.
DataDiskConfigurations []DataDiskConfiguration `xml:">DataDiskConfiguration"` // Specifies configuration information for the data disks that are associated with the image. A VM Image might not have data disks associated with it.
ServiceName string // Specifies the name of the cloud service that contained the Virtual Machine from which the image was created.
DeploymentName string // Specifies the name of the deployment that contained the Virtual Machine from which the image was created.
RoleName string // Specifies the name of the Virtual Machine from which the image was created.
Location string // Specifies the geo-location in which the media is located. The Location value is derived from the storage account that contains the blob in which the media is located. If the storage account belongs to an affinity group the value is NULL and the element is not displayed in the response.
AffinityGroup string // Specifies the affinity group in which the media is located. The AffinityGroup value is derived from the storage account that contains the blob in which the media is located. If the storage account does not belong to an affinity group the value is NULL and the element is not displayed in the response.
CreatedTime string // Specifies the time that the image was created.
ModifiedTime string // Specifies the time that the image was last updated.
Language string // Specifies the language of the image.
ImageFamily string // Specifies a value that can be used to group VM Images.
RecommendedVMSize string // Optional. Specifies the size to use for the Virtual Machine that is created from the VM Image.
IsPremium string // Indicates whether the image contains software or associated services that will incur charges above the core price for the virtual machine. For additional details, see the PricingDetailLink element.
Eula string // Specifies the End User License Agreement that is associated with the image. The value for this element is a string, but it is recommended that the value be a URL that points to a EULA.
IconURI string `xml:"IconUri"` // Specifies the URI to the icon that is displayed for the image in the Management Portal.
SmallIconURI string `xml:"SmallIconUri"` // Specifies the URI to the small icon that is displayed for the image in the Management Portal.
PrivacyURI string `xml:"PrivacyUri"` // Specifies the URI that points to a document that contains the privacy policy related to the image.
PublishedDate string // Specifies the date when the image was added to the image repository.
}
type OSState string
const (
OSStateGeneralized OSState = "Generalized"
OSStateSpecialized OSState = "Specialized"
)
type IOType string
const (
IOTypeProvisioned IOType = "Provisioned"
IOTypeStandard IOType = "Standard"
)
// OSDiskConfiguration specifies configuration information for the operating
// system disk that is associated with the image.
type OSDiskConfiguration struct {
Name string // Specifies the name of the operating system disk.
HostCaching vmdisk.HostCachingType // Specifies the caching behavior of the operating system disk.
OSState OSState // Specifies the state of the operating system in the image.
OS string // Specifies the operating system type of the image.
MediaLink string // Specifies the location of the blob in Azure storage. The blob location belongs to a storage account in the subscription specified by the <subscription-id> value in the operation call.
LogicalSizeInGB float64 // Specifies the size, in GB, of the operating system disk.
IOType IOType // Identifies the type of the storage account for the backing VHD. If the backing VHD is in an Provisioned Storage account, “Provisioned” is returned otherwise “Standard” is returned.
}
// DataDiskConfiguration specifies configuration information for the data disks
// that are associated with the image.
type DataDiskConfiguration struct {
Name string // Specifies the name of the data disk.
HostCaching vmdisk.HostCachingType // Specifies the caching behavior of the data disk.
Lun string // Specifies the Logical Unit Number (LUN) for the data disk.
MediaLink string // Specifies the location of the blob in Azure storage. The blob location belongs to a storage account in the subscription specified by the <subscription-id> value in the operation call.
LogicalSizeInGB float64 // Specifies the size, in GB, of the data disk.
IOType IOType // Identifies the type of the storage account for the backing VHD. If the backing VHD is in an Provisioned Storage account, “Provisioned” is returned otherwise “Standard” is returned.
}
type CaptureRoleAsVMImageOperation struct {
XMLName xml.Name `xml:"http://schemas.microsoft.com/windowsazure CaptureRoleAsVMImageOperation"`
OperationType string //CaptureRoleAsVMImageOperation
OSState OSState
VMImageName string
VMImageLabel string
CaptureParameters
}
type CaptureParameters struct {
Description string `xml:",omitempty"`
Language string `xml:",omitempty"`
ImageFamily string `xml:",omitempty"`
RecommendedVMSize string `xml:",omitempty"`
}
@@ -0,0 +1,110 @@
package virtualmachineimage
import (
"encoding/xml"
"testing"
)
const xml1 = `
<VMImage>
<Name>imgName</Name>
<Label>PackerMade_Ubuntu_Serv14</Label>
<Category>User</Category>
<Description>packer made image</Description>
<OSDiskConfiguration>
<Name>OSDisk</Name>
<HostCaching>ReadWrite</HostCaching>
<OSState>Generalized</OSState>
<OS>Linux</OS>
<MediaLink>https://sa.blob.core.windows.net/images/PackerMade_Ubuntu_Serv14_2015-12-12.vhd</MediaLink>
<LogicalDiskSizeInGB>30</LogicalDiskSizeInGB>
<IOType>Standard</IOType>
</OSDiskConfiguration>
<DataDiskConfigurations/>
<ServiceName>PkrSrvf3mz03u4mi</ServiceName>
<DeploymentName>PkrVMf3mz03u4mi</DeploymentName>
<RoleName>PkrVMf3mz03u4mi</RoleName>
<Location>Central US</Location>
<CreatedTime>2015-12-12T08:59:29.1936858Z</CreatedTime>
<ModifiedTime>2015-12-12T08:59:29.1936858Z</ModifiedTime>
<ImageFamily>PackerMade</ImageFamily>
<RecommendedVMSize>Small</RecommendedVMSize>
<IsPremium>false</IsPremium>
<VMImageState>VMImageReadyForUse</VMImageState>
<RoleStateOnCapture>StoppedVM</RoleStateOnCapture>
<RoleSizeOnCapture>Small</RoleSizeOnCapture>
</VMImage>`
const xml2 = `
<VMImage>
<Name>imgName</Name>
<Label>PackerMade_Ubuntu_Serv14</Label>
<Category>User</Category>
<Description>packer made image</Description>
<OSDiskConfiguration>
<Name>OSDisk</Name>
<HostCaching>ReadWrite</HostCaching>
<OSState>Generalized</OSState>
<OS>Linux</OS>
<MediaLink>https://sa.blob.core.windows.net/images/PackerMade_Ubuntu_Serv14_2015-12-12.vhd</MediaLink>
<LogicalDiskSizeInGB>30</LogicalDiskSizeInGB>
<IOType>Standard</IOType>
</OSDiskConfiguration>
<DataDiskConfigurations>
<DataDiskConfiguration>
<Name>DataDisk1</Name>
<HostCaching>ReadWrite</HostCaching>
<MediaLink>https://sa.blob.core.windows.net/images/PackerMade_Ubuntu_Serv14_2015-12-12-dd1.vhd</MediaLink>
<LogicalDiskSizeInGB>31</LogicalDiskSizeInGB>
<IOType>Standard</IOType>
</DataDiskConfiguration>
<DataDiskConfiguration>
<Name>DataDisk2</Name>
<HostCaching>ReadWrite</HostCaching>
<MediaLink>https://sa.blob.core.windows.net/images/PackerMade_Ubuntu_Serv14_2015-12-12-dd2.vhd</MediaLink>
<LogicalDiskSizeInGB>32</LogicalDiskSizeInGB>
<IOType>Standard</IOType>
</DataDiskConfiguration>
</DataDiskConfigurations>
<ServiceName>PkrSrvf3mz03u4mi</ServiceName>
<DeploymentName>PkrVMf3mz03u4mi</DeploymentName>
<RoleName>PkrVMf3mz03u4mi</RoleName>
<Location>Central US</Location>
<CreatedTime>2015-12-12T08:59:29.1936858Z</CreatedTime>
<ModifiedTime>2015-12-12T08:59:29.1936858Z</ModifiedTime>
<ImageFamily>PackerMade</ImageFamily>
<RecommendedVMSize>Small</RecommendedVMSize>
<IsPremium>false</IsPremium>
<VMImageState>VMImageReadyForUse</VMImageState>
<RoleStateOnCapture>StoppedVM</RoleStateOnCapture>
<RoleSizeOnCapture>Small</RoleSizeOnCapture>
</VMImage>`
func Test_NoDataDisksUnmarshal(t *testing.T) {
var image VMImage
if err := xml.Unmarshal([]byte(xml1), &image); err != nil {
t.Fatal(err)
}
check := checker{t}
check.Equal(0, len(image.DataDiskConfigurations))
}
func Test_DataDiskCountUnmarshal(t *testing.T) {
var image VMImage
if err := xml.Unmarshal([]byte(xml2), &image); err != nil {
t.Fatal(err)
}
check := checker{t}
check.Equal(2, len(image.DataDiskConfigurations))
check.Equal("DataDisk1", image.DataDiskConfigurations[0].Name)
check.Equal("DataDisk2", image.DataDiskConfigurations[1].Name)
}
type checker struct{ *testing.T }
func (a *checker) Equal(expected, actual interface{}) {
if expected != actual {
a.T.Fatalf("Expected %q, but got %q", expected, actual)
}
}
@@ -0,0 +1,47 @@
// Package virtualnetwork provides a client for Virtual Networks.
package virtualnetwork
import (
"encoding/xml"
"github.com/Azure/azure-sdk-for-go/management"
)
const (
azureNetworkConfigurationURL = "services/networking/media"
)
// NewClient is used to return new VirtualNetworkClient instance
func NewClient(client management.Client) VirtualNetworkClient {
return VirtualNetworkClient{client: client}
}
// GetVirtualNetworkConfiguration retreives the current virtual network
// configuration for the currently active subscription. Note that the
// underlying Azure API means that network related operations are not safe
// for running concurrently.
func (c VirtualNetworkClient) GetVirtualNetworkConfiguration() (NetworkConfiguration, error) {
networkConfiguration := c.NewNetworkConfiguration()
response, err := c.client.SendAzureGetRequest(azureNetworkConfigurationURL)
if err != nil {
return networkConfiguration, err
}
err = xml.Unmarshal(response, &networkConfiguration)
return networkConfiguration, err
}
// SetVirtualNetworkConfiguration configures the virtual networks for the
// currently active subscription according to the NetworkConfiguration given.
// Note that the underlying Azure API means that network related operations
// are not safe for running concurrently.
func (c VirtualNetworkClient) SetVirtualNetworkConfiguration(networkConfiguration NetworkConfiguration) (management.OperationID, error) {
networkConfiguration.setXMLNamespaces()
networkConfigurationBytes, err := xml.Marshal(networkConfiguration)
if err != nil {
return "", err
}
return c.client.SendAzurePutRequest(azureNetworkConfigurationURL, "text/plain", networkConfigurationBytes)
}
@@ -0,0 +1,90 @@
package virtualnetwork
import (
"encoding/xml"
"github.com/Azure/azure-sdk-for-go/management"
)
const xmlNamespace = "http://schemas.microsoft.com/ServiceHosting/2011/07/NetworkConfiguration"
const xmlNamespaceXsd = "http://www.w3.org/2001/XMLSchema"
const xmlNamespaceXsi = "http://www.w3.org/2001/XMLSchema-instance"
// VirtualNetworkClient is used to perform operations on Virtual Networks.
type VirtualNetworkClient struct {
client management.Client
}
// NetworkConfiguration represents the network configuration for an entire Azure
// subscription.
type NetworkConfiguration struct {
XMLName xml.Name `xml:"NetworkConfiguration"`
XMLNamespaceXsd string `xml:"xmlns:xsd,attr"`
XMLNamespaceXsi string `xml:"xmlns:xsi,attr"`
XMLNs string `xml:"xmlns,attr"`
Configuration VirtualNetworkConfiguration `xml:"VirtualNetworkConfiguration"`
// TODO: Nicer builder methods for these that abstract away the
// underlying structure.
}
// NewNetworkConfiguration creates a new empty NetworkConfiguration structure
// for further configuration. The XML namespaces are already set correctly.
func (client *VirtualNetworkClient) NewNetworkConfiguration() NetworkConfiguration {
networkConfiguration := NetworkConfiguration{}
networkConfiguration.setXMLNamespaces()
return networkConfiguration
}
// setXMLNamespaces ensure that all of the required namespaces are set. It
// should be called prior to marshalling the structure to XML for use with the
// Azure REST endpoint. It is used internally prior to submitting requests, but
// since it is idempotent there is no harm in repeat calls.
func (n *NetworkConfiguration) setXMLNamespaces() {
n.XMLNamespaceXsd = xmlNamespaceXsd
n.XMLNamespaceXsi = xmlNamespaceXsi
n.XMLNs = xmlNamespace
}
type VirtualNetworkConfiguration struct {
DNS DNS `xml:"Dns,omitempty"`
LocalNetworkSites []LocalNetworkSite `xml:"LocalNetworkSites>LocalNetworkSite"`
VirtualNetworkSites []VirtualNetworkSite `xml:"VirtualNetworkSites>VirtualNetworkSite"`
}
type DNS struct {
DNSServers []DNSServer `xml:"DnsServers>DnsServer,omitempty"`
}
type DNSServer struct {
XMLName xml.Name `xml:"DnsServer"`
Name string `xml:"name,attr"`
IPAddress string `xml:"IPAddress,attr"`
}
type DNSServerRef struct {
Name string `xml:"name,attr"`
}
type VirtualNetworkSite struct {
Name string `xml:"name,attr"`
Location string `xml:"Location,attr"`
AddressSpace AddressSpace `xml:"AddressSpace"`
Subnets []Subnet `xml:"Subnets>Subnet"`
DNSServersRef []DNSServerRef `xml:"DnsServersRef>DnsServerRef,omitempty"`
}
type LocalNetworkSite struct {
Name string `xml:"name,attr"`
VPNGatewayAddress string
AddressSpace AddressSpace
}
type AddressSpace struct {
AddressPrefix []string
}
type Subnet struct {
Name string `xml:"name,attr"`
AddressPrefix string
}
@@ -0,0 +1,28 @@
package vmutils
import (
vm "github.com/Azure/azure-sdk-for-go/management/virtualmachine"
)
func updateOrAddConfig(configs []vm.ConfigurationSet, configType vm.ConfigurationSetType, update func(*vm.ConfigurationSet)) []vm.ConfigurationSet {
config := findConfig(configs, configType)
if config == nil {
configs = append(configs, vm.ConfigurationSet{ConfigurationSetType: configType})
config = findConfig(configs, configType)
}
update(config)
return configs
}
func findConfig(configs []vm.ConfigurationSet, configType vm.ConfigurationSetType) *vm.ConfigurationSet {
for i, config := range configs {
if config.ConfigurationSetType == configType {
// need to return a pointer to the original set in configs,
// not the copy made by the range iterator
return &configs[i]
}
}
return nil
}
@@ -0,0 +1,58 @@
package vmutils
import (
"fmt"
vm "github.com/Azure/azure-sdk-for-go/management/virtualmachine"
vmdisk "github.com/Azure/azure-sdk-for-go/management/virtualmachinedisk"
)
// ConfigureWithNewDataDisk adds configuration for a new (empty) data disk
func ConfigureWithNewDataDisk(role *vm.Role, label, destinationVhdStorageURL string, sizeInGB int, cachingType vmdisk.HostCachingType) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
appendDataDisk(role, vm.DataVirtualHardDisk{
DiskLabel: label,
HostCaching: cachingType,
LogicalDiskSizeInGB: sizeInGB,
MediaLink: destinationVhdStorageURL,
})
return nil
}
// ConfigureWithExistingDataDisk adds configuration for an existing data disk
func ConfigureWithExistingDataDisk(role *vm.Role, diskName string, cachingType vmdisk.HostCachingType) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
appendDataDisk(role, vm.DataVirtualHardDisk{
DiskName: diskName,
HostCaching: cachingType,
})
return nil
}
// ConfigureWithVhdDataDisk adds configuration for adding a vhd in a storage
// account as a data disk
func ConfigureWithVhdDataDisk(role *vm.Role, sourceVhdStorageURL string, cachingType vmdisk.HostCachingType) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
appendDataDisk(role, vm.DataVirtualHardDisk{
SourceMediaLink: sourceVhdStorageURL,
HostCaching: cachingType,
})
return nil
}
func appendDataDisk(role *vm.Role, disk vm.DataVirtualHardDisk) {
disk.Lun = len(role.DataVirtualHardDisks)
role.DataVirtualHardDisks = append(role.DataVirtualHardDisks, disk)
}
@@ -0,0 +1,91 @@
package vmutils
import (
"fmt"
vm "github.com/Azure/azure-sdk-for-go/management/virtualmachine"
)
// ConfigureDeploymentFromRemoteImage configures VM Role to deploy from a remote
// image source. "remoteImageSourceURL" can be any publically accessible URL to
// a VHD file, including but not limited to a SAS Azure Storage blob url. "os"
// needs to be either "Linux" or "Windows". "label" is optional.
func ConfigureDeploymentFromRemoteImage(
role *vm.Role,
remoteImageSourceURL string,
os string,
newDiskName string,
destinationVhdStorageURL string,
label string) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
role.OSVirtualHardDisk = &vm.OSVirtualHardDisk{
RemoteSourceImageLink: remoteImageSourceURL,
MediaLink: destinationVhdStorageURL,
DiskName: newDiskName,
OS: os,
DiskLabel: label,
}
return nil
}
// ConfigureDeploymentFromPlatformImage configures VM Role to deploy from a
// platform image. See osimage package for methods to retrieve a list of the
// available platform images. "label" is optional.
func ConfigureDeploymentFromPlatformImage(
role *vm.Role,
imageName string,
mediaLink string,
label string) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
role.OSVirtualHardDisk = &vm.OSVirtualHardDisk{
SourceImageName: imageName,
MediaLink: mediaLink,
}
return nil
}
// ConfigureDeploymentFromPublishedVMImage configures VM Role to deploy from
// a published (public) VM image.
func ConfigureDeploymentFromPublishedVMImage(
role *vm.Role,
vmImageName string,
mediaLocation string,
provisionGuestAgent bool) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
role.VMImageName = vmImageName
role.MediaLocation = mediaLocation
role.ProvisionGuestAgent = provisionGuestAgent
return nil
}
// ConfigureDeploymentFromUserVMImage configures VM Role to deploy from a previously
// captured (user generated) VM image.
func ConfigureDeploymentFromUserVMImage(
role *vm.Role,
vmImageName string) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
role.VMImageName = vmImageName
return nil
}
// ConfigureDeploymentFromExistingOSDisk configures VM Role to deploy from an
// existing disk. 'label' is optional.
func ConfigureDeploymentFromExistingOSDisk(role *vm.Role, osDiskName, label string) error {
role.OSVirtualHardDisk = &vm.OSVirtualHardDisk{
DiskName: osDiskName,
DiskLabel: label,
}
return nil
}
@@ -0,0 +1,90 @@
package vmutils
import (
"encoding/base64"
"encoding/json"
"fmt"
vm "github.com/Azure/azure-sdk-for-go/management/virtualmachine"
)
const (
dockerPublicConfigVersion = 2
)
func AddAzureVMExtensionConfiguration(role *vm.Role, name, publisher, version, referenceName, state string,
publicConfigurationValue, privateConfigurationValue []byte) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
extension := vm.ResourceExtensionReference{
Name: name,
Publisher: publisher,
Version: version,
ReferenceName: referenceName,
State: state,
}
if len(privateConfigurationValue) != 0 {
extension.ParameterValues = append(extension.ParameterValues, vm.ResourceExtensionParameter{
Key: "ignored",
Value: base64.StdEncoding.EncodeToString(privateConfigurationValue),
Type: "Private",
})
}
if len(publicConfigurationValue) != 0 {
extension.ParameterValues = append(extension.ParameterValues, vm.ResourceExtensionParameter{
Key: "ignored",
Value: base64.StdEncoding.EncodeToString(publicConfigurationValue),
Type: "Public",
})
}
if role.ResourceExtensionReferences == nil {
role.ResourceExtensionReferences = &[]vm.ResourceExtensionReference{}
}
extensionList := append(*role.ResourceExtensionReferences, extension)
role.ResourceExtensionReferences = &extensionList
return nil
}
// AddAzureDockerVMExtensionConfiguration adds the DockerExtension to the role
// configuratioon and opens a port "dockerPort"
// TODO(ahmetalpbalkan) Deprecate this and move to 'docker-machine' codebase.
func AddAzureDockerVMExtensionConfiguration(role *vm.Role, dockerPort int, version string) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
if err := ConfigureWithExternalPort(role, "docker", dockerPort, dockerPort, vm.InputEndpointProtocolTCP); err != nil {
return err
}
publicConfiguration, err := createDockerPublicConfig(dockerPort)
if err != nil {
return err
}
privateConfiguration, err := json.Marshal(dockerPrivateConfig{})
if err != nil {
return err
}
return AddAzureVMExtensionConfiguration(role,
"DockerExtension", "MSOpenTech.Extensions",
version, "DockerExtension", "enable",
publicConfiguration, privateConfiguration)
}
func createDockerPublicConfig(dockerPort int) ([]byte, error) {
return json.Marshal(dockerPublicConfig{DockerPort: dockerPort, Version: dockerPublicConfigVersion})
}
type dockerPublicConfig struct {
DockerPort int `json:"dockerport"`
Version int `json:"version"`
}
type dockerPrivateConfig struct{}
@@ -0,0 +1,42 @@
package vmutils
import (
"encoding/xml"
"testing"
vm "github.com/Azure/azure-sdk-for-go/management/virtualmachine"
)
func Test_AddAzureVMExtensionConfiguration(t *testing.T) {
role := vm.Role{}
AddAzureVMExtensionConfiguration(&role,
"nameOfExtension", "nameOfPublisher", "versionOfExtension", "nameOfReference", "state", []byte{1, 2, 3}, []byte{})
data, err := xml.MarshalIndent(role, "", " ")
if err != nil {
t.Fatal(err)
}
if expected := `<Role>
<ConfigurationSets></ConfigurationSets>
<ResourceExtensionReferences>
<ResourceExtensionReference>
<ReferenceName>nameOfReference</ReferenceName>
<Publisher>nameOfPublisher</Publisher>
<Name>nameOfExtension</Name>
<Version>versionOfExtension</Version>
<ResourceExtensionParameterValues>
<ResourceExtensionParameterValue>
<Key>ignored</Key>
<Value>AQID</Value>
<Type>Public</Type>
</ResourceExtensionParameterValue>
</ResourceExtensionParameterValues>
<State>state</State>
</ResourceExtensionReference>
</ResourceExtensionReferences>
<DataVirtualHardDisks></DataVirtualHardDisks>
</Role>`; string(data) != expected {
t.Fatalf("Expected %q, but got %q", expected, string(data))
}
}
@@ -0,0 +1,458 @@
package vmutils
import (
"encoding/base64"
"fmt"
"math/rand"
"testing"
"time"
"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/location"
"github.com/Azure/azure-sdk-for-go/management/osimage"
storage "github.com/Azure/azure-sdk-for-go/management/storageservice"
"github.com/Azure/azure-sdk-for-go/management/testutils"
vm "github.com/Azure/azure-sdk-for-go/management/virtualmachine"
vmimage "github.com/Azure/azure-sdk-for-go/management/virtualmachineimage"
)
func TestDeployPlatformImage(t *testing.T) {
client := testutils.GetTestClient(t)
vmname := GenerateName()
sa := GetTestStorageAccount(t, client)
location := sa.StorageServiceProperties.Location
role := NewVMConfiguration(vmname, "Standard_D3")
ConfigureDeploymentFromPlatformImage(&role,
GetLinuxTestImage(t, client).Name,
fmt.Sprintf("http://%s.blob.core.windows.net/sdktest/%s.vhd", sa.ServiceName, vmname),
GenerateName())
ConfigureForLinux(&role, "myvm", "azureuser", GeneratePassword())
ConfigureWithPublicSSH(&role)
testRoleConfiguration(t, client, role, location)
}
func TestDeployPlatformWindowsImage(t *testing.T) {
client := testutils.GetTestClient(t)
vmname := GenerateName()
sa := GetTestStorageAccount(t, client)
location := sa.StorageServiceProperties.Location
role := NewVMConfiguration(vmname, "Standard_D3")
ConfigureDeploymentFromPlatformImage(&role,
GetWindowsTestImage(t, client).Name,
fmt.Sprintf("http://%s.blob.core.windows.net/sdktest/%s.vhd", sa.ServiceName, vmname),
GenerateName())
ConfigureForWindows(&role, vmname, "azureuser", GeneratePassword(), true, "")
ConfigureWinRMOverHTTP(&role)
ConfigureWinRMOverHTTPS(&role, "")
testRoleConfiguration(t, client, role, location)
}
func TestVMImageList(t *testing.T) {
client := testutils.GetTestClient(t)
vmic := vmimage.NewClient(client)
il, _ := vmic.ListVirtualMachineImages(vmimage.ListParameters{})
for _, im := range il.VMImages {
t.Logf("%s -%s", im.Name, im.Description)
}
}
func TestDeployPlatformOSImageCaptureRedeploy(t *testing.T) {
client := testutils.GetTestClient(t)
vmname := GenerateName()
sa := GetTestStorageAccount(t, client)
location := sa.StorageServiceProperties.Location
role := NewVMConfiguration(vmname, "Standard_D3")
ConfigureDeploymentFromPlatformImage(&role,
GetLinuxTestImage(t, client).Name,
fmt.Sprintf("http://%s.blob.core.windows.net/sdktest/%s.vhd", sa.ServiceName, vmname),
GenerateName())
ConfigureForLinux(&role, "myvm", "azureuser", GeneratePassword())
ConfigureWithPublicSSH(&role)
t.Logf("Deploying VM: %s", vmname)
createRoleConfiguration(t, client, role, location)
t.Logf("Wait for deployment to enter running state")
vmc := vm.NewClient(client)
status := vm.DeploymentStatusDeploying
for status != vm.DeploymentStatusRunning {
deployment, err := vmc.GetDeployment(vmname, vmname)
if err != nil {
t.Error(err)
break
}
status = deployment.Status
}
t.Logf("Shutting down VM: %s", vmname)
if err := Await(client, func() (management.OperationID, error) {
return vmc.ShutdownRole(vmname, vmname, vmname, vm.PostShutdownActionStopped)
}); err != nil {
t.Error(err)
}
if err := WaitForDeploymentPowerState(client, vmname, vmname, vm.PowerStateStopped); err != nil {
t.Fatal(err)
}
imagename := GenerateName()
t.Logf("Capturing OSImage: %s", imagename)
if err := Await(client, func() (management.OperationID, error) {
return vmc.CaptureRole(vmname, vmname, vmname, imagename, imagename, nil)
}); err != nil {
t.Error(err)
}
im := GetUserOSImage(t, client, imagename)
t.Logf("Found image: %+v", im)
newvmname := GenerateName()
role = NewVMConfiguration(newvmname, "Standard_D3")
ConfigureDeploymentFromPlatformImage(&role,
im.Name,
fmt.Sprintf("http://%s.blob.core.windows.net/sdktest/%s.vhd", sa.ServiceName, newvmname),
GenerateName())
ConfigureForLinux(&role, newvmname, "azureuser", GeneratePassword())
ConfigureWithPublicSSH(&role)
t.Logf("Deploying new VM from freshly captured OS image: %s", newvmname)
if err := Await(client, func() (management.OperationID, error) {
return vmc.CreateDeployment(role, vmname, vm.CreateDeploymentOptions{})
}); err != nil {
t.Error(err)
}
deleteHostedService(t, client, vmname)
}
func TestDeployPlatformVMImageCaptureRedeploy(t *testing.T) {
client := testutils.GetTestClient(t)
vmname := GenerateName()
sa := GetTestStorageAccount(t, client)
location := sa.StorageServiceProperties.Location
role := NewVMConfiguration(vmname, "Standard_D3")
ConfigureDeploymentFromPlatformImage(&role,
GetLinuxTestImage(t, client).Name,
fmt.Sprintf("http://%s.blob.core.windows.net/sdktest/%s.vhd", sa.ServiceName, vmname),
GenerateName())
ConfigureForLinux(&role, "myvm", "azureuser", GeneratePassword())
ConfigureWithPublicSSH(&role)
t.Logf("Deploying VM: %s", vmname)
createRoleConfiguration(t, client, role, location)
t.Logf("Wait for deployment to enter running state")
vmc := vm.NewClient(client)
status := vm.DeploymentStatusDeploying
for status != vm.DeploymentStatusRunning {
deployment, err := vmc.GetDeployment(vmname, vmname)
if err != nil {
t.Error(err)
break
}
status = deployment.Status
}
t.Logf("Shutting down VM: %s", vmname)
if err := Await(client, func() (management.OperationID, error) {
return vmc.ShutdownRole(vmname, vmname, vmname, vm.PostShutdownActionStopped)
}); err != nil {
t.Error(err)
}
if err := WaitForDeploymentInstanceStatus(client, vmname, vmname, vm.InstanceStatusStoppedVM); err != nil {
t.Fatal(err)
}
imagename := GenerateName()
t.Logf("Capturing VMImage: %s", imagename)
if err := Await(client, func() (management.OperationID, error) {
return vmimage.NewClient(client).Capture(vmname, vmname, vmname, imagename, imagename, vmimage.OSStateGeneralized, vmimage.CaptureParameters{})
}); err != nil {
t.Error(err)
}
im := GetUserVMImage(t, client, imagename)
t.Logf("Found image: %+v", im)
newvmname := GenerateName()
role = NewVMConfiguration(newvmname, "Standard_D3")
ConfigureDeploymentFromUserVMImage(&role, im.Name)
ConfigureForLinux(&role, newvmname, "azureuser", GeneratePassword())
ConfigureWithPublicSSH(&role)
t.Logf("Deploying new VM from freshly captured VM image: %s", newvmname)
if err := Await(client, func() (management.OperationID, error) {
return vmc.CreateDeployment(role, vmname, vm.CreateDeploymentOptions{})
}); err != nil {
t.Error(err)
}
deleteHostedService(t, client, vmname)
}
func TestDeployFromPublishedVmImage(t *testing.T) {
client := testutils.GetTestClient(t)
vmname := GenerateName()
sa := GetTestStorageAccount(t, client)
location := sa.StorageServiceProperties.Location
im := GetVMImage(t, client, func(im vmimage.VMImage) bool {
return im.Name ==
"fb83b3509582419d99629ce476bcb5c8__SQL-Server-2014-RTM-12.0.2430.0-OLTP-ENU-Win2012R2-cy14su11"
})
role := NewVMConfiguration(vmname, "Standard_D4")
ConfigureDeploymentFromPublishedVMImage(&role, im.Name,
fmt.Sprintf("http://%s.blob.core.windows.net/%s", sa.ServiceName, vmname), false)
ConfigureForWindows(&role, vmname, "azureuser", GeneratePassword(), true, "")
ConfigureWithPublicSSH(&role)
testRoleConfiguration(t, client, role, location)
}
func TestRoleStateOperations(t *testing.T) {
client := testutils.GetTestClient(t)
vmname := GenerateName()
sa := GetTestStorageAccount(t, client)
location := sa.StorageServiceProperties.Location
role := NewVMConfiguration(vmname, "Standard_D3")
ConfigureDeploymentFromPlatformImage(&role,
GetLinuxTestImage(t, client).Name,
fmt.Sprintf("http://%s.blob.core.windows.net/sdktest/%s.vhd", sa.ServiceName, vmname),
GenerateName())
ConfigureForLinux(&role, "myvm", "azureuser", GeneratePassword())
createRoleConfiguration(t, client, role, location)
vmc := vm.NewClient(client)
if err := Await(client, func() (management.OperationID, error) {
return vmc.ShutdownRole(vmname, vmname, vmname, vm.PostShutdownActionStopped)
}); err != nil {
t.Error(err)
}
if err := Await(client, func() (management.OperationID, error) {
return vmc.StartRole(vmname, vmname, vmname)
}); err != nil {
t.Error(err)
}
if err := Await(client, func() (management.OperationID, error) {
return vmc.RestartRole(vmname, vmname, vmname)
}); err != nil {
t.Error(err)
}
deleteHostedService(t, client, vmname)
}
func testRoleConfiguration(t *testing.T, client management.Client, role vm.Role, location string) {
createRoleConfiguration(t, client, role, location)
deleteHostedService(t, client, role.RoleName)
}
func createRoleConfiguration(t *testing.T, client management.Client, role vm.Role, location string) {
vmc := vm.NewClient(client)
hsc := hostedservice.NewClient(client)
vmname := role.RoleName
if err := hsc.CreateHostedService(hostedservice.CreateHostedServiceParameters{
ServiceName: vmname, Location: location,
Label: base64.StdEncoding.EncodeToString([]byte(vmname))}); err != nil {
t.Error(err)
}
if err := Await(client, func() (management.OperationID, error) {
return vmc.CreateDeployment(role, vmname, vm.CreateDeploymentOptions{})
}); err != nil {
t.Error(err)
}
}
func deleteHostedService(t *testing.T, client management.Client, vmname string) {
t.Logf("Deleting hosted service: %s", vmname)
if err := Await(client, func() (management.OperationID, error) {
return hostedservice.NewClient(client).DeleteHostedService(vmname, true)
}); err != nil {
t.Error(err)
}
}
// === utility funcs ===
func GetTestStorageAccount(t *testing.T, client management.Client) storage.StorageServiceResponse {
t.Log("Retrieving storage account")
sc := storage.NewClient(client)
var sa storage.StorageServiceResponse
ssl, err := sc.ListStorageServices()
if err != nil {
t.Fatal(err)
}
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
if len(ssl.StorageServices) == 0 {
t.Log("No storage accounts found, creating a new one")
lc := location.NewClient(client)
ll, err := lc.ListLocations()
if err != nil {
t.Fatal(err)
}
loc := ll.Locations[rnd.Intn(len(ll.Locations))].Name
t.Logf("Location for new storage account: %s", loc)
name := GenerateName()
op, err := sc.CreateStorageService(storage.StorageAccountCreateParameters{
ServiceName: name,
Label: base64.StdEncoding.EncodeToString([]byte(name)),
Location: loc,
AccountType: storage.AccountTypeStandardLRS})
if err != nil {
t.Fatal(err)
}
if err := client.WaitForOperation(op, nil); err != nil {
t.Fatal(err)
}
sa, err = sc.GetStorageService(name)
if err != nil {
t.Fatal(err)
}
} else {
sa = ssl.StorageServices[rnd.Intn(len(ssl.StorageServices))]
}
t.Logf("Selected storage account '%s' in location '%s'",
sa.ServiceName, sa.StorageServiceProperties.Location)
return sa
}
func GetLinuxTestImage(t *testing.T, client management.Client) osimage.OSImage {
return GetOSImage(t, client, func(im osimage.OSImage) bool {
return im.Category == "Public" && im.ImageFamily == "Ubuntu Server 14.04 LTS"
})
}
func GetWindowsTestImage(t *testing.T, client management.Client) osimage.OSImage {
return GetOSImage(t, client, func(im osimage.OSImage) bool {
return im.Category == "Public" && im.ImageFamily == "Windows Server 2012 R2 Datacenter"
})
}
func GetUserOSImage(t *testing.T, client management.Client, name string) osimage.OSImage {
return GetOSImage(t, client, func(im osimage.OSImage) bool {
return im.Category == "User" && im.Name == name
})
}
func GetOSImage(
t *testing.T,
client management.Client,
filter func(osimage.OSImage) bool) osimage.OSImage {
t.Log("Selecting OS image")
osc := osimage.NewClient(client)
allimages, err := osc.ListOSImages()
if err != nil {
t.Fatal(err)
}
filtered := []osimage.OSImage{}
for _, im := range allimages.OSImages {
if filter(im) {
filtered = append(filtered, im)
}
}
if len(filtered) == 0 {
t.Fatal("Filter too restrictive, no images left?")
}
image := filtered[0]
for _, im := range filtered {
if im.PublishedDate > image.PublishedDate {
image = im
}
}
t.Logf("Selecting image '%s'", image.Name)
return image
}
func GetUserVMImage(t *testing.T, client management.Client, name string) vmimage.VMImage {
return GetVMImage(t, client, func(im vmimage.VMImage) bool {
return im.Category == "User" && im.Name == name
})
}
func GetVMImage(
t *testing.T,
client management.Client,
filter func(vmimage.VMImage) bool) vmimage.VMImage {
t.Log("Selecting VM image")
allimages, err := vmimage.NewClient(client).ListVirtualMachineImages(vmimage.ListParameters{})
if err != nil {
t.Fatal(err)
}
filtered := []vmimage.VMImage{}
for _, im := range allimages.VMImages {
if filter(im) {
filtered = append(filtered, im)
}
}
if len(filtered) == 0 {
t.Fatal("Filter too restrictive, no images left?")
}
image := filtered[0]
for _, im := range filtered {
if im.PublishedDate > image.PublishedDate {
image = im
}
}
t.Logf("Selecting image '%s'", image.Name)
return image
}
func GenerateName() string {
from := "1234567890abcdefghijklmnopqrstuvwxyz"
return "sdk" + GenerateString(12, from)
}
func GeneratePassword() string {
pw := GenerateString(20, "1234567890") +
GenerateString(20, "abcdefghijklmnopqrstuvwxyz") +
GenerateString(20, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
i := rnd.Intn(len(pw)-2) + 1
pw = string(append([]uint8(pw[i:]), pw[:i-1]...))
return pw
}
func GenerateString(length int, from string) string {
str := ""
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
for len(str) < length {
str += string(from[rnd.Intn(len(from))])
}
return str
}
type asyncFunc func() (operationId management.OperationID, err error)
func Await(client management.Client, async asyncFunc) error {
requestID, err := async()
if err != nil {
return err
}
return client.WaitForOperation(requestID, nil)
}
+83
View File
@@ -0,0 +1,83 @@
package vmutils
import (
"fmt"
vm "github.com/Azure/azure-sdk-for-go/management/virtualmachine"
)
// ConfigureWithPublicSSH adds configuration exposing port 22 externally
func ConfigureWithPublicSSH(role *vm.Role) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
return ConfigureWithExternalPort(role, "SSH", 22, 22, vm.InputEndpointProtocolTCP)
}
// ConfigureWithPublicRDP adds configuration exposing port 3389 externally
func ConfigureWithPublicRDP(role *vm.Role) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
return ConfigureWithExternalPort(role, "RDP", 3389, 3389, vm.InputEndpointProtocolTCP)
}
// ConfigureWithPublicPowerShell adds configuration exposing port 5986
// externally
func ConfigureWithPublicPowerShell(role *vm.Role) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
return ConfigureWithExternalPort(role, "PowerShell", 5986, 5986, vm.InputEndpointProtocolTCP)
}
// ConfigureWithExternalPort adds a new InputEndpoint to the Role, exposing a
// port externally
func ConfigureWithExternalPort(role *vm.Role, name string, localport, externalport int, protocol vm.InputEndpointProtocol) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
role.ConfigurationSets = updateOrAddConfig(role.ConfigurationSets, vm.ConfigurationSetTypeNetwork,
func(config *vm.ConfigurationSet) {
config.InputEndpoints = append(config.InputEndpoints, vm.InputEndpoint{
LocalPort: localport,
Name: name,
Port: externalport,
Protocol: protocol,
})
})
return nil
}
// ConfigureWithSecurityGroup associates the Role with a specific network security group
func ConfigureWithSecurityGroup(role *vm.Role, networkSecurityGroup string) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
role.ConfigurationSets = updateOrAddConfig(role.ConfigurationSets, vm.ConfigurationSetTypeNetwork,
func(config *vm.ConfigurationSet) {
config.NetworkSecurityGroup = networkSecurityGroup
})
return nil
}
// ConfigureWithSubnet associates the Role with a specific subnet
func ConfigureWithSubnet(role *vm.Role, subnet string) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
role.ConfigurationSets = updateOrAddConfig(role.ConfigurationSets, vm.ConfigurationSetTypeNetwork,
func(config *vm.ConfigurationSet) {
config.SubnetNames = append(config.SubnetNames, subnet)
})
return nil
}
@@ -0,0 +1,76 @@
package vmutils
import (
"fmt"
"github.com/Azure/azure-sdk-for-go/management"
lc "github.com/Azure/azure-sdk-for-go/management/location"
vm "github.com/Azure/azure-sdk-for-go/management/virtualmachine"
)
// IsRoleSizeValid retrieves the available rolesizes using
// vmclient.GetRoleSizeList() and returns whether that the provided roleSizeName
// is part of that list
func IsRoleSizeValid(vmclient vm.VirtualMachineClient, roleSizeName string) (bool, error) {
if roleSizeName == "" {
return false, fmt.Errorf(errParamNotSpecified, "roleSizeName")
}
roleSizeList, err := vmclient.GetRoleSizeList()
if err != nil {
return false, err
}
for _, roleSize := range roleSizeList.RoleSizes {
if roleSize.Name == roleSizeName {
return true, nil
}
}
return false, nil
}
// IsRoleSizeAvailableInLocation retrieves all available sizes in the specified
// location and returns whether that the provided roleSizeName is part of that list.
func IsRoleSizeAvailableInLocation(managementclient management.Client, location, roleSizeName string) (bool, error) {
if location == "" {
return false, fmt.Errorf(errParamNotSpecified, "location")
}
if roleSizeName == "" {
return false, fmt.Errorf(errParamNotSpecified, "roleSizeName")
}
locationClient := lc.NewClient(managementclient)
locationInfo, err := getLocation(locationClient, location)
if err != nil {
return false, err
}
for _, availableRoleSize := range locationInfo.VirtualMachineRoleSizes {
if availableRoleSize == roleSizeName {
return true, nil
}
}
return false, nil
}
func getLocation(c lc.LocationClient, location string) (*lc.Location, error) {
if location == "" {
return nil, fmt.Errorf(errParamNotSpecified, "location")
}
locations, err := c.ListLocations()
if err != nil {
return nil, err
}
for _, existingLocation := range locations.Locations {
if existingLocation.Name != location {
continue
}
return &existingLocation, nil
}
return nil, fmt.Errorf("Invalid location: %s. Available locations: %s", location, locations)
}
@@ -0,0 +1,58 @@
package vmutils
import (
"time"
"github.com/Azure/azure-sdk-for-go/management"
vm "github.com/Azure/azure-sdk-for-go/management/virtualmachine"
)
// WaitForDeploymentPowerState blocks until all role instances in deployment
// reach desired power state.
func WaitForDeploymentPowerState(client management.Client, cloudServiceName, deploymentName string, desiredPowerstate vm.PowerState) error {
for {
deployment, err := vm.NewClient(client).GetDeployment(cloudServiceName, deploymentName)
if err != nil {
return err
}
if allInstancesInPowerState(deployment.RoleInstanceList, desiredPowerstate) {
return nil
}
time.Sleep(2 * time.Second)
}
}
func allInstancesInPowerState(instances []vm.RoleInstance, desiredPowerstate vm.PowerState) bool {
for _, r := range instances {
if r.PowerState != desiredPowerstate {
return false
}
}
return true
}
// WaitForDeploymentInstanceStatus blocks until all role instances in deployment
// reach desired InstanceStatus.
func WaitForDeploymentInstanceStatus(client management.Client, cloudServiceName, deploymentName string, desiredInstanceStatus vm.InstanceStatus) error {
for {
deployment, err := vm.NewClient(client).GetDeployment(cloudServiceName, deploymentName)
if err != nil {
return err
}
if allInstancesInInstanceStatus(deployment.RoleInstanceList, desiredInstanceStatus) {
return nil
}
time.Sleep(2 * time.Second)
}
}
func allInstancesInInstanceStatus(instances []vm.RoleInstance, desiredInstancestatus vm.InstanceStatus) bool {
for _, r := range instances {
if r.InstanceStatus != desiredInstancestatus {
return false
}
}
return true
}
+177
View File
@@ -0,0 +1,177 @@
// Package vmutils provides convenience methods for creating Virtual
// Machine Role configurations.
package vmutils
import (
"fmt"
vm "github.com/Azure/azure-sdk-for-go/management/virtualmachine"
)
const (
errParamNotSpecified = "Parameter %s is not specified."
)
// NewVMConfiguration creates configuration for a new virtual machine Role.
func NewVMConfiguration(name string, roleSize string) vm.Role {
return vm.Role{
RoleName: name,
RoleType: "PersistentVMRole",
RoleSize: roleSize,
ProvisionGuestAgent: true,
}
}
// ConfigureForLinux adds configuration when deploying a generalized Linux
// image. If "password" is left empty, SSH password security will be disabled by
// default. Certificates with SSH public keys should already be uploaded to the
// cloud service where the VM will be deployed and referenced here only by their
// thumbprint.
func ConfigureForLinux(role *vm.Role, hostname, user, password string, sshPubkeyCertificateThumbprint ...string) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
role.ConfigurationSets = updateOrAddConfig(role.ConfigurationSets, vm.ConfigurationSetTypeLinuxProvisioning,
func(config *vm.ConfigurationSet) {
config.HostName = hostname
config.UserName = user
config.UserPassword = password
if password != "" {
config.DisableSSHPasswordAuthentication = "false"
}
if len(sshPubkeyCertificateThumbprint) != 0 {
config.SSH = &vm.SSH{}
for _, k := range sshPubkeyCertificateThumbprint {
config.SSH.PublicKeys = append(config.SSH.PublicKeys,
vm.PublicKey{
Fingerprint: k,
Path: "/home/" + user + "/.ssh/authorized_keys",
},
)
}
}
},
)
return nil
}
// ConfigureForWindows adds configuration when deploying a generalized
// Windows image. timeZone can be left empty. For a complete list of supported
// time zone entries, you can either refer to the values listed in the registry
// entry "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time
// Zones" or you can use the tzutil command-line tool to list the valid time.
func ConfigureForWindows(role *vm.Role, hostname, user, password string, enableAutomaticUpdates bool, timeZone string) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
role.ConfigurationSets = updateOrAddConfig(role.ConfigurationSets, vm.ConfigurationSetTypeWindowsProvisioning,
func(config *vm.ConfigurationSet) {
config.ComputerName = hostname
config.AdminUsername = user
config.AdminPassword = password
config.EnableAutomaticUpdates = enableAutomaticUpdates
config.TimeZone = timeZone
},
)
return nil
}
// ConfigureWithCustomDataForLinux configures custom data for Linux-based images.
// The customData contains either cloud-init or shell script to be executed upon start.
//
// The function expects the customData to be base64-encoded.
func ConfigureWithCustomDataForLinux(role *vm.Role, customData string) error {
return configureWithCustomData(role, customData, vm.ConfigurationSetTypeLinuxProvisioning)
}
// ConfigureWithCustomDataForWindows configures custom data for Windows-based images.
// The customData contains either cloud-init or shell script to be executed upon start.
//
// The function expects the customData to be base64-encoded.
func ConfigureWithCustomDataForWindows(role *vm.Role, customData string) error {
return configureWithCustomData(role, customData, vm.ConfigurationSetTypeWindowsProvisioning)
}
func configureWithCustomData(role *vm.Role, customData string, typ vm.ConfigurationSetType) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
role.ConfigurationSets = updateOrAddConfig(role.ConfigurationSets, typ,
func(config *vm.ConfigurationSet) {
config.CustomData = customData
})
return nil
}
// ConfigureWindowsToJoinDomain adds configuration to join a new Windows vm to a
// domain. "username" must be in UPN form (user@domain.com), "machineOU" can be
// left empty
func ConfigureWindowsToJoinDomain(role *vm.Role, username, password, domainToJoin, machineOU string) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
winconfig := findConfig(role.ConfigurationSets, vm.ConfigurationSetTypeWindowsProvisioning)
if winconfig != nil {
winconfig.DomainJoin = &vm.DomainJoin{
Credentials: vm.Credentials{Username: username, Password: password},
JoinDomain: domainToJoin,
MachineObjectOU: machineOU,
}
}
return nil
}
func ConfigureWinRMListener(role *vm.Role, protocol vm.WinRMProtocol, certificateThumbprint string) error {
if role == nil {
return fmt.Errorf(errParamNotSpecified, "role")
}
winconfig := findConfig(role.ConfigurationSets, vm.ConfigurationSetTypeWindowsProvisioning)
if winconfig != nil {
listener := vm.WinRMListener{
Protocol: protocol,
CertificateThumbprint: certificateThumbprint,
}
if winconfig.WinRMListeners == nil {
winconfig.WinRMListeners = &[]vm.WinRMListener{}
}
currentListeners := *winconfig.WinRMListeners
// replace existing listener if it's already configured
for i, existingListener := range currentListeners {
if existingListener.Protocol == protocol {
currentListeners[i] = listener
return nil
}
}
// otherwise append to list of listeners
newListeners := append(currentListeners, listener)
winconfig.WinRMListeners = &newListeners
return nil
}
return fmt.Errorf("WindowsProvisioningConfigurationSet not found in 'role'")
}
func ConfigureWinRMOverHTTP(role *vm.Role) error {
return ConfigureWinRMListener(role, vm.WinRMProtocolHTTP, "")
}
func ConfigureWinRMOverHTTPS(role *vm.Role, certificateThumbprint string) error {
return ConfigureWinRMListener(role, vm.WinRMProtocolHTTPS, certificateThumbprint)
}
@@ -0,0 +1,440 @@
package vmutils
import (
"encoding/xml"
"testing"
vmdisk "github.com/Azure/azure-sdk-for-go/management/virtualmachinedisk"
)
func TestNewLinuxVmRemoteImage(t *testing.T) {
role := NewVMConfiguration("myvm", "Standard_D3")
ConfigureDeploymentFromRemoteImage(&role,
"http://remote.host/some.vhd?sv=12&sig=ukhfiuwef78687", "Linux",
"myvm-os-disk", "http://mystorageacct.blob.core.windows.net/vhds/mybrandnewvm.vhd",
"OSDisk")
ConfigureForLinux(&role, "myvm", "azureuser", "P@ssword", "2398yyKJGd78e2389ydfncuirowebhf89yh3IUOBY")
ConfigureWithPublicSSH(&role)
bytes, err := xml.MarshalIndent(role, "", " ")
if err != nil {
t.Fatal(err)
}
expected := `<Role>
<RoleName>myvm</RoleName>
<RoleType>PersistentVMRole</RoleType>
<ConfigurationSets>
<ConfigurationSet>
<ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType>
<StoredCertificateSettings></StoredCertificateSettings>
<HostName>myvm</HostName>
<UserName>azureuser</UserName>
<UserPassword>P@ssword</UserPassword>
<DisableSshPasswordAuthentication>false</DisableSshPasswordAuthentication>
<SSH>
<PublicKeys>
<PublicKey>
<Fingerprint>2398yyKJGd78e2389ydfncuirowebhf89yh3IUOBY</Fingerprint>
<Path>/home/azureuser/.ssh/authorized_keys</Path>
</PublicKey>
</PublicKeys>
<KeyPairs></KeyPairs>
</SSH>
<InputEndpoints></InputEndpoints>
<SubnetNames></SubnetNames>
<PublicIPs></PublicIPs>
</ConfigurationSet>
<ConfigurationSet>
<ConfigurationSetType>NetworkConfiguration</ConfigurationSetType>
<StoredCertificateSettings></StoredCertificateSettings>
<InputEndpoints>
<InputEndpoint>
<LocalPort>22</LocalPort>
<Name>SSH</Name>
<Port>22</Port>
<Protocol>TCP</Protocol>
</InputEndpoint>
</InputEndpoints>
<SubnetNames></SubnetNames>
<PublicIPs></PublicIPs>
</ConfigurationSet>
</ConfigurationSets>
<DataVirtualHardDisks></DataVirtualHardDisks>
<OSVirtualHardDisk>
<DiskLabel>OSDisk</DiskLabel>
<DiskName>myvm-os-disk</DiskName>
<MediaLink>http://mystorageacct.blob.core.windows.net/vhds/mybrandnewvm.vhd</MediaLink>
<OS>Linux</OS>
<RemoteSourceImageLink>http://remote.host/some.vhd?sv=12&amp;sig=ukhfiuwef78687</RemoteSourceImageLink>
</OSVirtualHardDisk>
<RoleSize>Standard_D3</RoleSize>
<ProvisionGuestAgent>true</ProvisionGuestAgent>
</Role>`
if string(bytes) != expected {
t.Fatalf("Expected marshalled xml to be %q, but got %q", expected, string(bytes))
}
}
func TestNewLinuxVmPlatformImage(t *testing.T) {
role := NewVMConfiguration("myplatformvm", "Standard_D3")
ConfigureDeploymentFromPlatformImage(&role,
"b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-14_04_2_LTS-amd64-server-20150309-en-us-30GB",
"http://mystorageacct.blob.core.windows.net/vhds/mybrandnewvm.vhd", "mydisklabel")
ConfigureForLinux(&role, "myvm", "azureuser", "", "2398yyKJGd78e2389ydfncuirdebhf89yh3IUOBY")
bytes, err := xml.MarshalIndent(role, "", " ")
if err != nil {
t.Fatal(err)
}
expected := `<Role>
<RoleName>myplatformvm</RoleName>
<RoleType>PersistentVMRole</RoleType>
<ConfigurationSets>
<ConfigurationSet>
<ConfigurationSetType>LinuxProvisioningConfiguration</ConfigurationSetType>
<StoredCertificateSettings></StoredCertificateSettings>
<HostName>myvm</HostName>
<UserName>azureuser</UserName>
<SSH>
<PublicKeys>
<PublicKey>
<Fingerprint>2398yyKJGd78e2389ydfncuirdebhf89yh3IUOBY</Fingerprint>
<Path>/home/azureuser/.ssh/authorized_keys</Path>
</PublicKey>
</PublicKeys>
<KeyPairs></KeyPairs>
</SSH>
<InputEndpoints></InputEndpoints>
<SubnetNames></SubnetNames>
<PublicIPs></PublicIPs>
</ConfigurationSet>
</ConfigurationSets>
<DataVirtualHardDisks></DataVirtualHardDisks>
<OSVirtualHardDisk>
<MediaLink>http://mystorageacct.blob.core.windows.net/vhds/mybrandnewvm.vhd</MediaLink>
<SourceImageName>b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-14_04_2_LTS-amd64-server-20150309-en-us-30GB</SourceImageName>
</OSVirtualHardDisk>
<RoleSize>Standard_D3</RoleSize>
<ProvisionGuestAgent>true</ProvisionGuestAgent>
</Role>`
if string(bytes) != expected {
t.Fatalf("Expected marshalled xml to be %q, but got %q", expected, string(bytes))
}
}
func TestNewVmFromVMImage(t *testing.T) {
role := NewVMConfiguration("restoredbackup", "Standard_D1")
ConfigureDeploymentFromPublishedVMImage(&role, "myvm-backup-20150209",
"http://mystorageacct.blob.core.windows.net/vhds/myoldnewvm.vhd", false)
bytes, err := xml.MarshalIndent(role, "", " ")
if err != nil {
t.Fatal(err)
}
expected := `<Role>
<RoleName>restoredbackup</RoleName>
<RoleType>PersistentVMRole</RoleType>
<ConfigurationSets></ConfigurationSets>
<VMImageName>myvm-backup-20150209</VMImageName>
<MediaLocation>http://mystorageacct.blob.core.windows.net/vhds/myoldnewvm.vhd</MediaLocation>
<DataVirtualHardDisks></DataVirtualHardDisks>
<RoleSize>Standard_D1</RoleSize>
</Role>`
if string(bytes) != expected {
t.Fatalf("Expected marshalled xml to be %q, but got %q", expected, string(bytes))
}
}
func TestNewVmFromExistingDisk(t *testing.T) {
role := NewVMConfiguration("blobvm", "Standard_D14")
ConfigureDeploymentFromExistingOSDisk(&role, "myvm-backup-20150209", "OSDisk")
ConfigureForWindows(&role, "WINVM", "azuser", "P2ssw@rd", true, "")
ConfigureWindowsToJoinDomain(&role, "user@domain.com", "youReN3verG0nnaGu3ss", "redmond.corp.contoso.com", "")
ConfigureWithNewDataDisk(&role, "my-brand-new-disk", "http://account.blob.core.windows.net/vhds/newdatadisk.vhd",
30, vmdisk.HostCachingTypeReadWrite)
ConfigureWithExistingDataDisk(&role, "data-disk", vmdisk.HostCachingTypeReadOnly)
bytes, err := xml.MarshalIndent(role, "", " ")
if err != nil {
t.Fatal(err)
}
expected := `<Role>
<RoleName>blobvm</RoleName>
<RoleType>PersistentVMRole</RoleType>
<ConfigurationSets>
<ConfigurationSet>
<ConfigurationSetType>WindowsProvisioningConfiguration</ConfigurationSetType>
<ComputerName>WINVM</ComputerName>
<AdminPassword>P2ssw@rd</AdminPassword>
<EnableAutomaticUpdates>true</EnableAutomaticUpdates>
<DomainJoin>
<Credentials>
<Domain></Domain>
<Username>user@domain.com</Username>
<Password>youReN3verG0nnaGu3ss</Password>
</Credentials>
<JoinDomain>redmond.corp.contoso.com</JoinDomain>
</DomainJoin>
<StoredCertificateSettings></StoredCertificateSettings>
<AdminUsername>azuser</AdminUsername>
<InputEndpoints></InputEndpoints>
<SubnetNames></SubnetNames>
<PublicIPs></PublicIPs>
</ConfigurationSet>
</ConfigurationSets>
<DataVirtualHardDisks>
<DataVirtualHardDisk>
<HostCaching>ReadWrite</HostCaching>
<DiskLabel>my-brand-new-disk</DiskLabel>
<LogicalDiskSizeInGB>30</LogicalDiskSizeInGB>
<MediaLink>http://account.blob.core.windows.net/vhds/newdatadisk.vhd</MediaLink>
</DataVirtualHardDisk>
<DataVirtualHardDisk>
<HostCaching>ReadOnly</HostCaching>
<DiskName>data-disk</DiskName>
<Lun>1</Lun>
</DataVirtualHardDisk>
</DataVirtualHardDisks>
<OSVirtualHardDisk>
<DiskLabel>OSDisk</DiskLabel>
<DiskName>myvm-backup-20150209</DiskName>
</OSVirtualHardDisk>
<RoleSize>Standard_D14</RoleSize>
<ProvisionGuestAgent>true</ProvisionGuestAgent>
</Role>`
if string(bytes) != expected {
t.Fatalf("Expected marshalled xml to be %q, but got %q", expected, string(bytes))
}
}
func TestWinRMOverHttps(t *testing.T) {
role := NewVMConfiguration("winrmoverhttp", "Standard_D1")
ConfigureForWindows(&role, "WINVM", "azuser", "P2ssw@rd", true, "")
ConfigureWinRMOverHTTPS(&role, "abcdef")
bytes, err := xml.MarshalIndent(role, "", " ")
if err != nil {
t.Fatal(err)
}
expected := `<Role>
<RoleName>winrmoverhttp</RoleName>
<RoleType>PersistentVMRole</RoleType>
<ConfigurationSets>
<ConfigurationSet>
<ConfigurationSetType>WindowsProvisioningConfiguration</ConfigurationSetType>
<ComputerName>WINVM</ComputerName>
<AdminPassword>P2ssw@rd</AdminPassword>
<EnableAutomaticUpdates>true</EnableAutomaticUpdates>
<StoredCertificateSettings></StoredCertificateSettings>
<WinRM>
<Listeners>
<Listener>
<Protocol>Https</Protocol>
<CertificateThumbprint>abcdef</CertificateThumbprint>
</Listener>
</Listeners>
</WinRM>
<AdminUsername>azuser</AdminUsername>
<InputEndpoints></InputEndpoints>
<SubnetNames></SubnetNames>
<PublicIPs></PublicIPs>
</ConfigurationSet>
</ConfigurationSets>
<DataVirtualHardDisks></DataVirtualHardDisks>
<RoleSize>Standard_D1</RoleSize>
<ProvisionGuestAgent>true</ProvisionGuestAgent>
</Role>`
if string(bytes) != expected {
t.Fatalf("Expected marshalled xml to be %q, but got %q", expected, string(bytes))
}
}
func TestWinRMOverHttpsWithNoThumbprint(t *testing.T) {
role := NewVMConfiguration("winrmoverhttp", "Standard_D1")
ConfigureForWindows(&role, "WINVM", "azuser", "P2ssw@rd", true, "")
ConfigureWinRMOverHTTPS(&role, "")
bytes, err := xml.MarshalIndent(role, "", " ")
if err != nil {
t.Fatal(err)
}
expected := `<Role>
<RoleName>winrmoverhttp</RoleName>
<RoleType>PersistentVMRole</RoleType>
<ConfigurationSets>
<ConfigurationSet>
<ConfigurationSetType>WindowsProvisioningConfiguration</ConfigurationSetType>
<ComputerName>WINVM</ComputerName>
<AdminPassword>P2ssw@rd</AdminPassword>
<EnableAutomaticUpdates>true</EnableAutomaticUpdates>
<StoredCertificateSettings></StoredCertificateSettings>
<WinRM>
<Listeners>
<Listener>
<Protocol>Https</Protocol>
</Listener>
</Listeners>
</WinRM>
<AdminUsername>azuser</AdminUsername>
<InputEndpoints></InputEndpoints>
<SubnetNames></SubnetNames>
<PublicIPs></PublicIPs>
</ConfigurationSet>
</ConfigurationSets>
<DataVirtualHardDisks></DataVirtualHardDisks>
<RoleSize>Standard_D1</RoleSize>
<ProvisionGuestAgent>true</ProvisionGuestAgent>
</Role>`
if string(bytes) != expected {
t.Fatalf("Expected marshalled xml to be %q, but got %q", expected, string(bytes))
}
}
func TestWinRMOverHttp(t *testing.T) {
role := NewVMConfiguration("winrmoverhttp", "Standard_D1")
ConfigureForWindows(&role, "WINVM", "azuser", "P2ssw@rd", true, "")
ConfigureWinRMOverHTTP(&role)
bytes, err := xml.MarshalIndent(role, "", " ")
if err != nil {
t.Fatal(err)
}
expected := `<Role>
<RoleName>winrmoverhttp</RoleName>
<RoleType>PersistentVMRole</RoleType>
<ConfigurationSets>
<ConfigurationSet>
<ConfigurationSetType>WindowsProvisioningConfiguration</ConfigurationSetType>
<ComputerName>WINVM</ComputerName>
<AdminPassword>P2ssw@rd</AdminPassword>
<EnableAutomaticUpdates>true</EnableAutomaticUpdates>
<StoredCertificateSettings></StoredCertificateSettings>
<WinRM>
<Listeners>
<Listener>
<Protocol>Http</Protocol>
</Listener>
</Listeners>
</WinRM>
<AdminUsername>azuser</AdminUsername>
<InputEndpoints></InputEndpoints>
<SubnetNames></SubnetNames>
<PublicIPs></PublicIPs>
</ConfigurationSet>
</ConfigurationSets>
<DataVirtualHardDisks></DataVirtualHardDisks>
<RoleSize>Standard_D1</RoleSize>
<ProvisionGuestAgent>true</ProvisionGuestAgent>
</Role>`
if string(bytes) != expected {
t.Fatalf("Expected marshalled xml to be %q, but got %q", expected, string(bytes))
}
}
func TestSettingWinRMOverHttpTwice(t *testing.T) {
role := NewVMConfiguration("winrmoverhttp", "Standard_D1")
ConfigureForWindows(&role, "WINVM", "azuser", "P2ssw@rd", true, "")
ConfigureWinRMOverHTTP(&role)
ConfigureWinRMOverHTTP(&role)
bytes, err := xml.MarshalIndent(role, "", " ")
if err != nil {
t.Fatal(err)
}
expected := `<Role>
<RoleName>winrmoverhttp</RoleName>
<RoleType>PersistentVMRole</RoleType>
<ConfigurationSets>
<ConfigurationSet>
<ConfigurationSetType>WindowsProvisioningConfiguration</ConfigurationSetType>
<ComputerName>WINVM</ComputerName>
<AdminPassword>P2ssw@rd</AdminPassword>
<EnableAutomaticUpdates>true</EnableAutomaticUpdates>
<StoredCertificateSettings></StoredCertificateSettings>
<WinRM>
<Listeners>
<Listener>
<Protocol>Http</Protocol>
</Listener>
</Listeners>
</WinRM>
<AdminUsername>azuser</AdminUsername>
<InputEndpoints></InputEndpoints>
<SubnetNames></SubnetNames>
<PublicIPs></PublicIPs>
</ConfigurationSet>
</ConfigurationSets>
<DataVirtualHardDisks></DataVirtualHardDisks>
<RoleSize>Standard_D1</RoleSize>
<ProvisionGuestAgent>true</ProvisionGuestAgent>
</Role>`
if string(bytes) != expected {
t.Fatalf("Expected marshalled xml to be %q, but got %q", expected, string(bytes))
}
}
func TestSettingWinRMOverHttpAndHttpsTwice(t *testing.T) {
role := NewVMConfiguration("winrmoverhttp", "Standard_D1")
ConfigureForWindows(&role, "WINVM", "azuser", "P2ssw@rd", true, "")
ConfigureWinRMOverHTTP(&role)
ConfigureWinRMOverHTTPS(&role, "")
ConfigureWinRMOverHTTP(&role)
ConfigureWinRMOverHTTPS(&role, "abcdef")
bytes, err := xml.MarshalIndent(role, "", " ")
if err != nil {
t.Fatal(err)
}
expected := `<Role>
<RoleName>winrmoverhttp</RoleName>
<RoleType>PersistentVMRole</RoleType>
<ConfigurationSets>
<ConfigurationSet>
<ConfigurationSetType>WindowsProvisioningConfiguration</ConfigurationSetType>
<ComputerName>WINVM</ComputerName>
<AdminPassword>P2ssw@rd</AdminPassword>
<EnableAutomaticUpdates>true</EnableAutomaticUpdates>
<StoredCertificateSettings></StoredCertificateSettings>
<WinRM>
<Listeners>
<Listener>
<Protocol>Http</Protocol>
</Listener>
<Listener>
<Protocol>Https</Protocol>
<CertificateThumbprint>abcdef</CertificateThumbprint>
</Listener>
</Listeners>
</WinRM>
<AdminUsername>azuser</AdminUsername>
<InputEndpoints></InputEndpoints>
<SubnetNames></SubnetNames>
<PublicIPs></PublicIPs>
</ConfigurationSet>
</ConfigurationSets>
<DataVirtualHardDisks></DataVirtualHardDisks>
<RoleSize>Standard_D1</RoleSize>
<ProvisionGuestAgent>true</ProvisionGuestAgent>
</Role>`
if string(bytes) != expected {
t.Fatalf("Expected marshalled xml to be %q, but got %q", expected, string(bytes))
}
}