generated from microsoft/python-package-template
-
Notifications
You must be signed in to change notification settings - Fork 8
/
virtual_machine.py
171 lines (143 loc) · 6.82 KB
/
virtual_machine.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# ---------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# ---------------------------------------------------------
"""CLI Wrapper for Creating, Updating, or Deleting Azure Virtual Machines"""
import json
import os
from pathlib import Path
import requests
import yaml
from adal import AuthenticationContext
from azureiai.managed_apps.utils import AAD_CRED, AAD_ID, TENANT_ID
from azureiai.partner_center.cli_parser import CLIParser
from azureiai.partner_center.submission import Submission
AZURE_VIRTUAL_MACHINE = "AzureThirdPartyVirtualMachine"
RESOURCE_CPP_API = "https://cloudpartner.azure.com"
RESOURCE_PC_API = "https://api.partner.microsoft.com"
URL_BASE = RESOURCE_CPP_API + "/api/publishers"
class VirtualMachine(Submission):
"""Azure Partner Center Virtual Machine offer."""
def __init__(
self,
name=None,
notification_emails=None,
config_yaml=r"config.yml",
app_path: str = ".",
json_listing_config="vm_config.json",
):
super().__init__(
name=name,
config_yaml=config_yaml,
resource_type=AZURE_VIRTUAL_MACHINE,
app_path=app_path,
json_listing_config=json_listing_config,
)
self.notification_emails = notification_emails
self._legacy_authorization = None
def create(self):
"""
Create Virtual Machine offer
First, verify that the offer does not already exist by checking the offer name.
If the offer is found, this command should fail, and the user should instead try "update".
The 'Update' command is used to create a new offer when the offer does not exist.
"""
try:
if self.show()["id"]:
raise NameError("Virtual Machine offer already exists. Try using 'update'?")
except LookupError:
pass # Passing this error is the only way to determine that an offer does not exist
return self.update()
def update(self):
"""Update Existing Virtual Machine offer"""
headers, json_config, url = self._prepare_request()
response = requests.put(url, json=json_config, headers=headers)
if response.status_code != 200:
self._raise_connection_error(response)
return response.json()
def list_contents(self) -> dict:
"""list only the Virtual Machine offers"""
with open(self.config_yaml, encoding="utf8") as file:
settings = yaml.safe_load(file)
if "publisherId" not in settings:
raise ValueError(f"Key: publisherId is missing from {self.config_yaml}")
publisher_id = settings["publisherId"]
offer_type_filter = "offerTypeId eq 'microsoft-azure-virtualmachines'"
url = f"{URL_BASE}/{publisher_id}/offers?api-version=2017-10-31&$filter={offer_type_filter}"
headers = {"Authorization": self.get_auth(RESOURCE_CPP_API), "Content-Type": "application/json"}
response = requests.get(url, headers=headers)
return response.json()
def publish(self):
"""Publish Existing Virtual Machine offer"""
with open(Path(self.app_path).joinpath(self.json_listing_config), "r", encoding="utf8") as read_file:
json_config = json.load(read_file)
if "publisherId" not in json_config:
raise ValueError(f"Key: publisherId is missing from {self.app_path}/{self.json_listing_config}")
publisher_id = json_config["publisherId"]
offer_id = json_config["id"]
url = f"{URL_BASE}/{publisher_id}/offers/{offer_id}/publish?api-version=2017-10-31"
headers = {"Authorization": self.get_auth(RESOURCE_CPP_API), "Content-Type": "application/json"}
response = requests.post(
url, json={"metadata": {"notification-emails": self.notification_emails}}, headers=headers
)
if response.status_code != 202:
self._raise_connection_error(response)
return response
def get_auth(self, resource=RESOURCE_PC_API) -> str:
"""
Create Authentication Header
:return: Authorization Header contents
"""
if resource == RESOURCE_PC_API:
if self._authorization is None:
self._authorization = f"Bearer {self._get_auth(resource)}"
return self._authorization
if resource == RESOURCE_CPP_API:
if self._legacy_authorization is None:
self._legacy_authorization = f"Bearer {self._get_auth(resource)}"
return self._legacy_authorization
raise Exception("The provided resource is unsupported.")
def _get_auth(self, resource) -> str:
with open(self.config_yaml, encoding="utf8") as file:
settings = yaml.safe_load(file)
client_id = os.getenv(AAD_ID, settings["aad_id"])
client_secret = os.getenv(AAD_CRED, settings["aad_secret"])
tenant_id = os.getenv(TENANT_ID, settings["tenant_id"])
auth_context = AuthenticationContext(f"https://login.microsoftonline.com/{tenant_id}")
token_response = auth_context.acquire_token_with_client_credentials(
resource=resource,
client_id=client_id,
client_secret=client_secret,
)
return token_response["accessToken"]
def _prepare_request(self):
with open(Path(self.app_path).joinpath(self.json_listing_config), "r", encoding="utf8") as read_file:
json_config = json.load(read_file)
if "publisherId" not in json_config:
raise ValueError(f"Key: publisherId is missing from {self.app_path}/{self.json_listing_config}")
publisher_id = json_config["publisherId"]
offer_id = json_config["id"]
url = f"{URL_BASE}/{publisher_id}/offers/{offer_id}?api-version=2017-10-31"
headers = {"Authorization": self.get_auth(RESOURCE_CPP_API), "Content-Type": "application/json"}
return headers, json_config, url
def _raise_connection_error(self, response):
"""if response body is not provided, return with the error code instead"""
error_string = (
json.dumps(response.json(), indent=4).replace("\\r", "").replace("\\n", os.linesep)
if response.json()
else response.status_code
)
raise ConnectionError(error_string)
class VirtualMachineCLI(CLIParser):
"""Methods for Virtual Machine"""
def __init__(self):
super().__init__(submission_type=VirtualMachine)
def publish(self):
"""Publish a Virtual Machine Offer"""
args = self._add_name_notification_emails_argument()
return VirtualMachine(
args.name,
notification_emails=args.notification_emails,
app_path=args.app_path,
json_listing_config=args.config_json,
config_yaml=args.config_yml,
).publish()