1// +build go1.7
2
3// Package virtualmachine provides a client for Virtual Machines.
4package virtualmachine
5
6// Copyright 2017 Microsoft Corporation
7//
8//    Licensed under the Apache License, Version 2.0 (the "License");
9//    you may not use this file except in compliance with the License.
10//    You may obtain a copy of the License at
11//
12//        http://www.apache.org/licenses/LICENSE-2.0
13//
14//    Unless required by applicable law or agreed to in writing, software
15//    distributed under the License is distributed on an "AS IS" BASIS,
16//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17//    See the License for the specific language governing permissions and
18//    limitations under the License.
19
20import (
21	"encoding/xml"
22	"fmt"
23
24	"github.com/Azure/azure-sdk-for-go/services/classic/management"
25)
26
27const (
28	azureDeploymentListURL        = "services/hostedservices/%s/deployments"
29	azureDeploymentURL            = "services/hostedservices/%s/deployments/%s"
30	azureListDeploymentsInSlotURL = "services/hostedservices/%s/deploymentslots/Production"
31	deleteAzureDeploymentURL      = "services/hostedservices/%s/deployments/%s?comp=media"
32	azureAddRoleURL               = "services/hostedservices/%s/deployments/%s/roles"
33	azureRoleURL                  = "services/hostedservices/%s/deployments/%s/roles/%s"
34	azureOperationsURL            = "services/hostedservices/%s/deployments/%s/roleinstances/%s/Operations"
35	azureRoleSizeListURL          = "rolesizes"
36
37	errParamNotSpecified = "Parameter %s is not specified."
38)
39
40//NewClient is used to instantiate a new VirtualMachineClient from an Azure client
41func NewClient(client management.Client) VirtualMachineClient {
42	return VirtualMachineClient{client: client}
43}
44
45// CreateDeploymentOptions can be used to create a customized deployement request
46type CreateDeploymentOptions struct {
47	DNSServers         []DNSServer
48	LoadBalancers      []LoadBalancer
49	ReservedIPName     string
50	VirtualNetworkName string
51}
52
53// CreateDeployment creates a deployment and then creates a virtual machine
54// in the deployment based on the specified configuration.
55//
56// https://msdn.microsoft.com/en-us/library/azure/jj157194.aspx
57func (vm VirtualMachineClient) CreateDeployment(
58	role Role,
59	cloudServiceName string,
60	options CreateDeploymentOptions) (management.OperationID, error) {
61
62	req := DeploymentRequest{
63		Name:               role.RoleName,
64		DeploymentSlot:     "Production",
65		Label:              role.RoleName,
66		RoleList:           []Role{role},
67		DNSServers:         options.DNSServers,
68		LoadBalancers:      options.LoadBalancers,
69		ReservedIPName:     options.ReservedIPName,
70		VirtualNetworkName: options.VirtualNetworkName,
71	}
72
73	data, err := xml.Marshal(req)
74	if err != nil {
75		return "", err
76	}
77
78	requestURL := fmt.Sprintf(azureDeploymentListURL, cloudServiceName)
79	return vm.client.SendAzurePostRequest(requestURL, data)
80}
81
82// GetDeploymentName queries an existing Azure cloud service for the name of the Deployment,
83// if any, in its 'Production' slot (the only slot possible). If none exists, it returns empty
84// string but no error
85//
86//https://msdn.microsoft.com/en-us/library/azure/ee460804.aspx
87func (vm VirtualMachineClient) GetDeploymentName(cloudServiceName string) (string, error) {
88	var deployment DeploymentResponse
89	if cloudServiceName == "" {
90		return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
91	}
92	requestURL := fmt.Sprintf(azureListDeploymentsInSlotURL, cloudServiceName)
93	response, err := vm.client.SendAzureGetRequest(requestURL)
94	if err != nil {
95		if management.IsResourceNotFoundError(err) {
96			return "", nil
97		}
98		return "", err
99	}
100	err = xml.Unmarshal(response, &deployment)
101	if err != nil {
102		return "", err
103	}
104
105	return deployment.Name, nil
106}
107
108func (vm VirtualMachineClient) GetDeployment(cloudServiceName, deploymentName string) (DeploymentResponse, error) {
109	var deployment DeploymentResponse
110	if cloudServiceName == "" {
111		return deployment, fmt.Errorf(errParamNotSpecified, "cloudServiceName")
112	}
113	if deploymentName == "" {
114		return deployment, fmt.Errorf(errParamNotSpecified, "deploymentName")
115	}
116	requestURL := fmt.Sprintf(azureDeploymentURL, cloudServiceName, deploymentName)
117	response, azureErr := vm.client.SendAzureGetRequest(requestURL)
118	if azureErr != nil {
119		return deployment, azureErr
120	}
121
122	err := xml.Unmarshal(response, &deployment)
123	return deployment, err
124}
125
126func (vm VirtualMachineClient) DeleteDeployment(cloudServiceName, deploymentName string) (management.OperationID, error) {
127	if cloudServiceName == "" {
128		return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
129	}
130	if deploymentName == "" {
131		return "", fmt.Errorf(errParamNotSpecified, "deploymentName")
132	}
133
134	requestURL := fmt.Sprintf(deleteAzureDeploymentURL, cloudServiceName, deploymentName)
135	return vm.client.SendAzureDeleteRequest(requestURL)
136}
137
138func (vm VirtualMachineClient) GetRole(cloudServiceName, deploymentName, roleName string) (*Role, error) {
139	if cloudServiceName == "" {
140		return nil, fmt.Errorf(errParamNotSpecified, "cloudServiceName")
141	}
142	if deploymentName == "" {
143		return nil, fmt.Errorf(errParamNotSpecified, "deploymentName")
144	}
145	if roleName == "" {
146		return nil, fmt.Errorf(errParamNotSpecified, "roleName")
147	}
148
149	role := new(Role)
150
151	requestURL := fmt.Sprintf(azureRoleURL, cloudServiceName, deploymentName, roleName)
152	response, azureErr := vm.client.SendAzureGetRequest(requestURL)
153	if azureErr != nil {
154		return nil, azureErr
155	}
156
157	err := xml.Unmarshal(response, role)
158	if err != nil {
159		return nil, err
160	}
161
162	return role, nil
163}
164
165// AddRole adds a Virtual Machine to a deployment of Virtual Machines, where role name = VM name
166// See https://msdn.microsoft.com/en-us/library/azure/jj157186.aspx
167func (vm VirtualMachineClient) AddRole(cloudServiceName string, deploymentName string, role Role) (management.OperationID, error) {
168	if cloudServiceName == "" {
169		return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
170	}
171	if deploymentName == "" {
172		return "", fmt.Errorf(errParamNotSpecified, "deploymentName")
173	}
174
175	data, err := xml.Marshal(PersistentVMRole{Role: role})
176	if err != nil {
177		return "", err
178	}
179
180	requestURL := fmt.Sprintf(azureAddRoleURL, cloudServiceName, deploymentName)
181	return vm.client.SendAzurePostRequest(requestURL, data)
182}
183
184// UpdateRole updates the configuration of the specified virtual machine
185// See https://msdn.microsoft.com/en-us/library/azure/jj157187.aspx
186func (vm VirtualMachineClient) UpdateRole(cloudServiceName, deploymentName, roleName string, role Role) (management.OperationID, error) {
187	if cloudServiceName == "" {
188		return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
189	}
190	if deploymentName == "" {
191		return "", fmt.Errorf(errParamNotSpecified, "deploymentName")
192	}
193	if roleName == "" {
194		return "", fmt.Errorf(errParamNotSpecified, "roleName")
195	}
196
197	data, err := xml.Marshal(PersistentVMRole{Role: role})
198	if err != nil {
199		return "", err
200	}
201
202	requestURL := fmt.Sprintf(azureRoleURL, cloudServiceName, deploymentName, roleName)
203	return vm.client.SendAzurePutRequest(requestURL, "text/xml", data)
204}
205
206func (vm VirtualMachineClient) StartRole(cloudServiceName, deploymentName, roleName string) (management.OperationID, error) {
207	if cloudServiceName == "" {
208		return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
209	}
210	if deploymentName == "" {
211		return "", fmt.Errorf(errParamNotSpecified, "deploymentName")
212	}
213	if roleName == "" {
214		return "", fmt.Errorf(errParamNotSpecified, "roleName")
215	}
216
217	startRoleOperationBytes, err := xml.Marshal(StartRoleOperation{
218		OperationType: "StartRoleOperation",
219	})
220	if err != nil {
221		return "", err
222	}
223
224	requestURL := fmt.Sprintf(azureOperationsURL, cloudServiceName, deploymentName, roleName)
225	return vm.client.SendAzurePostRequest(requestURL, startRoleOperationBytes)
226}
227
228func (vm VirtualMachineClient) ShutdownRole(cloudServiceName, deploymentName, roleName string, postaction PostShutdownAction) (management.OperationID, error) {
229	if cloudServiceName == "" {
230		return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
231	}
232	if deploymentName == "" {
233		return "", fmt.Errorf(errParamNotSpecified, "deploymentName")
234	}
235	if roleName == "" {
236		return "", fmt.Errorf(errParamNotSpecified, "roleName")
237	}
238
239	shutdownRoleOperationBytes, err := xml.Marshal(ShutdownRoleOperation{
240		OperationType:      "ShutdownRoleOperation",
241		PostShutdownAction: postaction,
242	})
243	if err != nil {
244		return "", err
245	}
246
247	requestURL := fmt.Sprintf(azureOperationsURL, cloudServiceName, deploymentName, roleName)
248	return vm.client.SendAzurePostRequest(requestURL, shutdownRoleOperationBytes)
249}
250
251func (vm VirtualMachineClient) RestartRole(cloudServiceName, deploymentName, roleName string) (management.OperationID, error) {
252	if cloudServiceName == "" {
253		return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
254	}
255	if deploymentName == "" {
256		return "", fmt.Errorf(errParamNotSpecified, "deploymentName")
257	}
258	if roleName == "" {
259		return "", fmt.Errorf(errParamNotSpecified, "roleName")
260	}
261
262	restartRoleOperationBytes, err := xml.Marshal(RestartRoleOperation{
263		OperationType: "RestartRoleOperation",
264	})
265	if err != nil {
266		return "", err
267	}
268
269	requestURL := fmt.Sprintf(azureOperationsURL, cloudServiceName, deploymentName, roleName)
270	return vm.client.SendAzurePostRequest(requestURL, restartRoleOperationBytes)
271}
272
273func (vm VirtualMachineClient) DeleteRole(cloudServiceName, deploymentName, roleName string, deleteVHD bool) (management.OperationID, error) {
274	if cloudServiceName == "" {
275		return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
276	}
277	if deploymentName == "" {
278		return "", fmt.Errorf(errParamNotSpecified, "deploymentName")
279	}
280	if roleName == "" {
281		return "", fmt.Errorf(errParamNotSpecified, "roleName")
282	}
283
284	requestURL := fmt.Sprintf(azureRoleURL, cloudServiceName, deploymentName, roleName)
285	if deleteVHD {
286		requestURL += "?comp=media"
287	}
288	return vm.client.SendAzureDeleteRequest(requestURL)
289}
290
291func (vm VirtualMachineClient) GetRoleSizeList() (RoleSizeList, error) {
292	roleSizeList := RoleSizeList{}
293
294	response, err := vm.client.SendAzureGetRequest(azureRoleSizeListURL)
295	if err != nil {
296		return roleSizeList, err
297	}
298
299	err = xml.Unmarshal(response, &roleSizeList)
300	return roleSizeList, err
301}
302
303// CaptureRole captures a VM role. If reprovisioningConfigurationSet is non-nil,
304// the VM role is redeployed after capturing the image, otherwise, the original
305// VM role is deleted.
306//
307// NOTE: an image resulting from this operation shows up in
308// osimage.GetImageList() as images with Category "User".
309func (vm VirtualMachineClient) CaptureRole(cloudServiceName, deploymentName, roleName, imageName, imageLabel string,
310	reprovisioningConfigurationSet *ConfigurationSet) (management.OperationID, error) {
311	if cloudServiceName == "" {
312		return "", fmt.Errorf(errParamNotSpecified, "cloudServiceName")
313	}
314	if deploymentName == "" {
315		return "", fmt.Errorf(errParamNotSpecified, "deploymentName")
316	}
317	if roleName == "" {
318		return "", fmt.Errorf(errParamNotSpecified, "roleName")
319	}
320
321	if reprovisioningConfigurationSet != nil &&
322		!(reprovisioningConfigurationSet.ConfigurationSetType == ConfigurationSetTypeLinuxProvisioning ||
323			reprovisioningConfigurationSet.ConfigurationSetType == ConfigurationSetTypeWindowsProvisioning) {
324		return "", fmt.Errorf("ConfigurationSet type can only be WindowsProvisioningConfiguration or LinuxProvisioningConfiguration")
325	}
326
327	operation := CaptureRoleOperation{
328		OperationType:             "CaptureRoleOperation",
329		PostCaptureAction:         PostCaptureActionReprovision,
330		ProvisioningConfiguration: reprovisioningConfigurationSet,
331		TargetImageLabel:          imageLabel,
332		TargetImageName:           imageName,
333	}
334	if reprovisioningConfigurationSet == nil {
335		operation.PostCaptureAction = PostCaptureActionDelete
336	}
337
338	data, err := xml.Marshal(operation)
339	if err != nil {
340		return "", err
341	}
342
343	return vm.client.SendAzurePostRequest(fmt.Sprintf(azureOperationsURL, cloudServiceName, deploymentName, roleName), data)
344}
345