cloudpub 1.5.0__py3-none-any.whl → 1.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cloudpub/ms_azure/service.py +379 -131
- cloudpub/ms_azure/session.py +4 -4
- cloudpub/ms_azure/utils.py +39 -16
- {cloudpub-1.5.0.dist-info → cloudpub-1.6.0.dist-info}/METADATA +1 -1
- {cloudpub-1.5.0.dist-info → cloudpub-1.6.0.dist-info}/RECORD +8 -8
- {cloudpub-1.5.0.dist-info → cloudpub-1.6.0.dist-info}/LICENSE +0 -0
- {cloudpub-1.5.0.dist-info → cloudpub-1.6.0.dist-info}/WHEEL +0 -0
- {cloudpub-1.5.0.dist-info → cloudpub-1.6.0.dist-info}/top_level.txt +0 -0
cloudpub/ms_azure/service.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
+
from enum import IntEnum
|
|
5
6
|
from typing import Any, Dict, Iterator, List, Optional, Tuple, Union, cast
|
|
6
7
|
|
|
7
8
|
from deepdiff import DeepDiff
|
|
@@ -18,6 +19,7 @@ from cloudpub.models.ms_azure import (
|
|
|
18
19
|
AzureResource,
|
|
19
20
|
ConfigureStatus,
|
|
20
21
|
CustomerLeads,
|
|
22
|
+
DiskVersion,
|
|
21
23
|
Listing,
|
|
22
24
|
ListingAsset,
|
|
23
25
|
ListingTrailer,
|
|
@@ -38,6 +40,7 @@ from cloudpub.models.ms_azure import (
|
|
|
38
40
|
from cloudpub.ms_azure.session import PartnerPortalSession
|
|
39
41
|
from cloudpub.ms_azure.utils import (
|
|
40
42
|
AzurePublishingMetadata,
|
|
43
|
+
TechnicalConfigLookUpData,
|
|
41
44
|
create_disk_version_from_scratch,
|
|
42
45
|
is_azure_job_not_complete,
|
|
43
46
|
is_sas_present,
|
|
@@ -69,6 +72,15 @@ AZURE_PRODUCT_RESOURCES = Union[
|
|
|
69
72
|
]
|
|
70
73
|
|
|
71
74
|
|
|
75
|
+
class SasFoundStatus(IntEnum):
|
|
76
|
+
"""Represent the submission target level of SAS found in a given product."""
|
|
77
|
+
|
|
78
|
+
missing = 0
|
|
79
|
+
draft = 1
|
|
80
|
+
preview = 2
|
|
81
|
+
live = 3
|
|
82
|
+
|
|
83
|
+
|
|
72
84
|
class AzureService(BaseService[AzurePublishingMetadata]):
|
|
73
85
|
"""Service provider for Microsoft Azure using the Product Ingestion API."""
|
|
74
86
|
|
|
@@ -103,7 +115,10 @@ class AzureService(BaseService[AzurePublishingMetadata]):
|
|
|
103
115
|
Returns:
|
|
104
116
|
The job ID to track its status alongside the initial status.
|
|
105
117
|
"""
|
|
106
|
-
log.
|
|
118
|
+
if log.isEnabledFor(logging.DEBUG):
|
|
119
|
+
log.debug(
|
|
120
|
+
"Received the following data to create/modify: %s", json.dumps(data, indent=2)
|
|
121
|
+
)
|
|
107
122
|
resp = self.session.post(path="configure", json=data)
|
|
108
123
|
self._raise_for_status(response=resp)
|
|
109
124
|
rsp_data = resp.json()
|
|
@@ -121,7 +136,7 @@ class AzureService(BaseService[AzurePublishingMetadata]):
|
|
|
121
136
|
Returns:
|
|
122
137
|
The updated job status.
|
|
123
138
|
"""
|
|
124
|
-
log.debug(
|
|
139
|
+
log.debug("Query job details for \"%s\"", job_id)
|
|
125
140
|
resp = self.session.get(path=f"configure/{job_id}/status")
|
|
126
141
|
|
|
127
142
|
# We don't want to fail if there's a server error thus we make a fake
|
|
@@ -129,9 +144,11 @@ class AzureService(BaseService[AzurePublishingMetadata]):
|
|
|
129
144
|
if resp.status_code >= 500:
|
|
130
145
|
log.warning(
|
|
131
146
|
(
|
|
132
|
-
|
|
133
|
-
" Considering the job_status as \"pending\"."
|
|
134
|
-
)
|
|
147
|
+
"Got HTTP %s from server when querying job %s status."
|
|
148
|
+
" Considering the job_status as \"pending\".",
|
|
149
|
+
),
|
|
150
|
+
resp.status_code,
|
|
151
|
+
job_id,
|
|
135
152
|
)
|
|
136
153
|
return ConfigureStatus.from_json(
|
|
137
154
|
{
|
|
@@ -177,24 +194,25 @@ class AzureService(BaseService[AzurePublishingMetadata]):
|
|
|
177
194
|
error_message = f"Job {job_id} failed: \n{job_details.errors}"
|
|
178
195
|
self._raise_error(InvalidStateError, error_message)
|
|
179
196
|
elif job_details.job_result == "succeeded":
|
|
180
|
-
log.debug(
|
|
197
|
+
log.debug("Job %s succeeded", job_id)
|
|
181
198
|
return job_details
|
|
182
199
|
|
|
183
|
-
def configure(self,
|
|
200
|
+
def configure(self, resources: List[AzureResource]) -> ConfigureStatus:
|
|
184
201
|
"""
|
|
185
202
|
Create or update a resource and wait until it's done.
|
|
186
203
|
|
|
187
204
|
Args:
|
|
188
|
-
|
|
189
|
-
The
|
|
205
|
+
resources (List[AzureResource]):
|
|
206
|
+
The list of resources to create/modify in Azure.
|
|
190
207
|
Returns:
|
|
191
208
|
dict: The result of job execution
|
|
192
209
|
"""
|
|
193
210
|
data = {
|
|
194
211
|
"$schema": self.CONFIGURE_SCHEMA.format(AZURE_API_VERSION=self.AZURE_API_VERSION),
|
|
195
|
-
"resources": [
|
|
212
|
+
"resources": [x.to_json() for x in resources],
|
|
196
213
|
}
|
|
197
|
-
log.
|
|
214
|
+
if log.isEnabledFor(logging.DEBUG):
|
|
215
|
+
log.debug("Data to configure: %s", json.dumps(data, indent=2))
|
|
198
216
|
res = self._configure(data=data)
|
|
199
217
|
return self._wait_for_job_completion(job_id=res.job_id)
|
|
200
218
|
|
|
@@ -205,7 +223,7 @@ class AzureService(BaseService[AzurePublishingMetadata]):
|
|
|
205
223
|
params: Dict[str, str] = {}
|
|
206
224
|
|
|
207
225
|
while has_next:
|
|
208
|
-
log.
|
|
226
|
+
log.info("Requesting the products list.")
|
|
209
227
|
resp = self.session.get(path="/product", params=params)
|
|
210
228
|
data = self._assert_dict(resp)
|
|
211
229
|
|
|
@@ -230,11 +248,26 @@ class AzureService(BaseService[AzurePublishingMetadata]):
|
|
|
230
248
|
Returns:
|
|
231
249
|
list: A list with ProductSummary for all products in Azure.
|
|
232
250
|
"""
|
|
251
|
+
log.info("Listing the products on Azure server.")
|
|
233
252
|
if not self._products:
|
|
234
253
|
self._products = [p for p in self.products]
|
|
235
254
|
return self._products
|
|
236
255
|
|
|
237
|
-
def
|
|
256
|
+
def get_productid(self, product_name: str) -> str:
|
|
257
|
+
"""Retrieve the desired product ID for the requested product name.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
product_name (str): the product's name to retrieve its product ID.
|
|
261
|
+
Returns:
|
|
262
|
+
The requested product ID when found.
|
|
263
|
+
Raises NotFoundError when the product was not found.
|
|
264
|
+
"""
|
|
265
|
+
for product in self.list_products():
|
|
266
|
+
if product.identity.name == product_name:
|
|
267
|
+
return product.id
|
|
268
|
+
raise NotFoundError(f"No such product with name {product_name}")
|
|
269
|
+
|
|
270
|
+
def get_product(self, product_id: str, target: str) -> Product:
|
|
238
271
|
"""
|
|
239
272
|
Return the requested Product by its ID.
|
|
240
273
|
|
|
@@ -246,37 +279,31 @@ class AzureService(BaseService[AzurePublishingMetadata]):
|
|
|
246
279
|
Args:
|
|
247
280
|
product_durable_id (str)
|
|
248
281
|
The product UUID
|
|
249
|
-
|
|
250
|
-
The
|
|
282
|
+
target (str)
|
|
283
|
+
The submission target to retrieve the product from.
|
|
251
284
|
Returns:
|
|
252
285
|
Product: the requested product
|
|
253
286
|
"""
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
path=f"/resource-tree/product/{product_id}", params={"targetType": t}
|
|
264
|
-
)
|
|
265
|
-
data = self._assert_dict(resp)
|
|
266
|
-
return Product.from_json(data)
|
|
267
|
-
except (ValueError, HTTPError):
|
|
268
|
-
log.debug("Couldn't find the product \"%s\" with state \"%s\"", product_id, t)
|
|
287
|
+
log.info("Requesting the product ID \"%s\" with state \"%s\".", product_id, target)
|
|
288
|
+
try:
|
|
289
|
+
resp = self.session.get(
|
|
290
|
+
path=f"/resource-tree/product/{product_id}", params={"targetType": target}
|
|
291
|
+
)
|
|
292
|
+
data = self._assert_dict(resp)
|
|
293
|
+
return Product.from_json(data)
|
|
294
|
+
except (ValueError, HTTPError):
|
|
295
|
+
log.debug("Couldn't find the product \"%s\" with state \"%s\"", product_id, target)
|
|
269
296
|
self._raise_error(NotFoundError, f"No such product with id \"{product_id}\"")
|
|
270
297
|
|
|
271
|
-
def get_product_by_name(self, product_name: str,
|
|
298
|
+
def get_product_by_name(self, product_name: str, target: str) -> Product:
|
|
272
299
|
"""
|
|
273
300
|
Return the requested Product by its name from Legacy CPP API.
|
|
274
301
|
|
|
275
302
|
Args:
|
|
276
303
|
product_name (str)
|
|
277
304
|
The product name according to Legacy CPP API.
|
|
278
|
-
|
|
279
|
-
The
|
|
305
|
+
target (str, optional)
|
|
306
|
+
The submission target to retrieve the product from.
|
|
280
307
|
Returns:
|
|
281
308
|
Product: the requested product when found
|
|
282
309
|
Raises:
|
|
@@ -285,7 +312,7 @@ class AzureService(BaseService[AzurePublishingMetadata]):
|
|
|
285
312
|
for product in self.products:
|
|
286
313
|
if product.identity.name == product_name:
|
|
287
314
|
log.debug("Product alias \"%s\" has the ID \"%s\"", product_name, product.id)
|
|
288
|
-
return self.get_product(product.id,
|
|
315
|
+
return self.get_product(product.id, target=target)
|
|
289
316
|
self._raise_error(NotFoundError, f"No such product with name \"{product_name}\"")
|
|
290
317
|
|
|
291
318
|
def get_submissions(self, product_id: str) -> List[ProductSubmission]:
|
|
@@ -314,6 +341,7 @@ class AzureService(BaseService[AzurePublishingMetadata]):
|
|
|
314
341
|
Returns:
|
|
315
342
|
Optional[ProductSubmission]: The requested submission when found.
|
|
316
343
|
"""
|
|
344
|
+
log.info("Looking up for submission in state \"%s\" for \"%s\"", state, product_id)
|
|
317
345
|
submissions = self.get_submissions(product_id)
|
|
318
346
|
for sub in submissions:
|
|
319
347
|
if sub.target.targetType == state:
|
|
@@ -369,47 +397,49 @@ class AzureService(BaseService[AzurePublishingMetadata]):
|
|
|
369
397
|
self._raise_error(NotFoundError, f"No such plan with name \"{plan_name}\"")
|
|
370
398
|
|
|
371
399
|
def get_product_plan_by_name(
|
|
372
|
-
self,
|
|
400
|
+
self,
|
|
401
|
+
product_name: str,
|
|
402
|
+
plan_name: str,
|
|
403
|
+
target: str,
|
|
373
404
|
) -> Tuple[Product, PlanSummary]:
|
|
374
405
|
"""Return a tuple with the desired Product and Plan after iterating over all targets.
|
|
375
406
|
|
|
376
407
|
Args:
|
|
377
408
|
product_name (str): The name of the product to search for
|
|
378
409
|
plan_name (str): The name of the plan to search for
|
|
379
|
-
|
|
410
|
+
target (str)
|
|
411
|
+
The submission target to retrieve the product/plan from.
|
|
380
412
|
Returns:
|
|
381
413
|
Tuple[Product, PlanSummary]: The Product and PlanSummary when fonud
|
|
382
414
|
Raises:
|
|
383
|
-
NotFoundError whenever
|
|
384
|
-
"""
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
continue
|
|
394
|
-
self._raise_error(
|
|
395
|
-
NotFoundError, f"No such plan with name \"{plan_name} for {product_name}\""
|
|
396
|
-
)
|
|
415
|
+
NotFoundError whenever no information was found in the respective submission target.
|
|
416
|
+
"""
|
|
417
|
+
try:
|
|
418
|
+
product = self.get_product_by_name(product_name, target=target)
|
|
419
|
+
plan = self.get_plan_by_name(product, plan_name)
|
|
420
|
+
return product, plan
|
|
421
|
+
except NotFoundError:
|
|
422
|
+
self._raise_error(
|
|
423
|
+
NotFoundError, f"No such plan with name \"{plan_name} for {product_name}\""
|
|
424
|
+
)
|
|
397
425
|
|
|
398
|
-
def diff_offer(self, product: Product,
|
|
426
|
+
def diff_offer(self, product: Product, target: str) -> DeepDiff:
|
|
399
427
|
"""Compute the difference between the provided product and the one in the remote.
|
|
400
428
|
|
|
401
429
|
Args:
|
|
402
430
|
product (Product)
|
|
403
431
|
The local product to diff with the remote one.
|
|
404
|
-
|
|
405
|
-
The
|
|
432
|
+
target (str)
|
|
433
|
+
The submission target to retrieve the product from.
|
|
406
434
|
Returns:
|
|
407
435
|
DeepDiff: The diff data.
|
|
408
436
|
"""
|
|
409
|
-
remote = self.get_product(product.id,
|
|
437
|
+
remote = self.get_product(product.id, target=target)
|
|
410
438
|
return DeepDiff(remote.to_json(), product.to_json(), exclude_regex_paths=self.DIFF_EXCLUDES)
|
|
411
439
|
|
|
412
|
-
def submit_to_status(
|
|
440
|
+
def submit_to_status(
|
|
441
|
+
self, product_id: str, status: str, resources: Optional[List[AzureResource]] = None
|
|
442
|
+
) -> ConfigureStatus:
|
|
413
443
|
"""
|
|
414
444
|
Send a submission request to Microsoft with a new Product status.
|
|
415
445
|
|
|
@@ -418,9 +448,12 @@ class AzureService(BaseService[AzurePublishingMetadata]):
|
|
|
418
448
|
The product ID to submit the new status.
|
|
419
449
|
status (str)
|
|
420
450
|
The new status: 'preview' or 'live'
|
|
451
|
+
resources (optional(list(AzureRerouce)))
|
|
452
|
+
Additional resources for modular push.
|
|
421
453
|
Returns:
|
|
422
454
|
The response from configure request.
|
|
423
455
|
"""
|
|
456
|
+
log.info("Submitting the status of \"%s\" to \"%s\"", product_id, status)
|
|
424
457
|
# We need to get the previous state of the given one to request the submission
|
|
425
458
|
prev_state_mapping = {
|
|
426
459
|
"preview": "draft",
|
|
@@ -437,9 +470,12 @@ class AzureService(BaseService[AzurePublishingMetadata]):
|
|
|
437
470
|
|
|
438
471
|
# Update the status with the expected one
|
|
439
472
|
submission.target.targetType = status
|
|
473
|
+
cfg_res: List[AzureResource] = [submission]
|
|
474
|
+
if resources:
|
|
475
|
+
log.info("Performing a modular push to \"%s\" for \"%s\"", status, product_id)
|
|
476
|
+
cfg_res = resources + cfg_res
|
|
440
477
|
log.debug("Set the status \"%s\" to submission.", status)
|
|
441
|
-
|
|
442
|
-
return self.configure(resource=submission)
|
|
478
|
+
return self.configure(resources=cfg_res)
|
|
443
479
|
|
|
444
480
|
@retry(
|
|
445
481
|
wait=wait_fixed(300),
|
|
@@ -459,6 +495,7 @@ class AzureService(BaseService[AzurePublishingMetadata]):
|
|
|
459
495
|
Raises:
|
|
460
496
|
RuntimeError: whenever a publishing is already in progress.
|
|
461
497
|
"""
|
|
498
|
+
log.info("Ensuring no other publishing jobs are in progress for \"%s\"", product_id)
|
|
462
499
|
submission_targets = ["preview", "live"]
|
|
463
500
|
|
|
464
501
|
for target in submission_targets:
|
|
@@ -491,6 +528,79 @@ class AzureService(BaseService[AzurePublishingMetadata]):
|
|
|
491
528
|
)
|
|
492
529
|
return tconfigs[0] # It should have only one VMIPlanTechConfig per plan.
|
|
493
530
|
|
|
531
|
+
def get_modular_resources_to_publish(
|
|
532
|
+
self, product: Product, tech_config: VMIPlanTechConfig
|
|
533
|
+
) -> List[AzureResource]:
|
|
534
|
+
"""Return the required resources for a modular publishing.
|
|
535
|
+
|
|
536
|
+
According to Microsoft docs:
|
|
537
|
+
"For a modular publish, all resources are required except for the product level details
|
|
538
|
+
(for example, listing, availability, packages, reseller) as applicable to your
|
|
539
|
+
product type."
|
|
540
|
+
|
|
541
|
+
Args:
|
|
542
|
+
product (Product): The original product to filter the resources from
|
|
543
|
+
tech_config (VMIPlanTechConfig): The updated tech config to publish
|
|
544
|
+
|
|
545
|
+
Returns:
|
|
546
|
+
List[AzureResource]: _description_
|
|
547
|
+
"""
|
|
548
|
+
# The following resources shouldn't be required:
|
|
549
|
+
# -> customer-leads
|
|
550
|
+
# -> test-drive
|
|
551
|
+
# -> property
|
|
552
|
+
# -> *listing*
|
|
553
|
+
# -> reseller
|
|
554
|
+
# -> price-and-availability-*
|
|
555
|
+
# NOTE: The "submission" resource will be already added by the "submit_to_status" method
|
|
556
|
+
#
|
|
557
|
+
# With that it needs only the related "product" and "plan" resources alongisde the
|
|
558
|
+
# updated tech_config
|
|
559
|
+
product_id = tech_config.product_id
|
|
560
|
+
plan_id = tech_config.plan_id
|
|
561
|
+
prod_res = cast(
|
|
562
|
+
List[ProductSummary],
|
|
563
|
+
[
|
|
564
|
+
prd
|
|
565
|
+
for prd in self.filter_product_resources(product=product, resource="product")
|
|
566
|
+
if prd.id == product_id
|
|
567
|
+
],
|
|
568
|
+
)[0]
|
|
569
|
+
plan_res = cast(
|
|
570
|
+
List[PlanSummary],
|
|
571
|
+
[
|
|
572
|
+
pln
|
|
573
|
+
for pln in self.filter_product_resources(product=product, resource="plan")
|
|
574
|
+
if pln.id == plan_id
|
|
575
|
+
],
|
|
576
|
+
)[0]
|
|
577
|
+
return [prod_res, plan_res, tech_config]
|
|
578
|
+
|
|
579
|
+
def compute_targets(self, product_id: str) -> List[str]:
|
|
580
|
+
"""List all the possible publishing targets order to seek data from Azure.
|
|
581
|
+
|
|
582
|
+
It also returns the ordered list of targets with the following precedence:
|
|
583
|
+
``live`` -> ``preview`` -> ``draft``
|
|
584
|
+
|
|
585
|
+
Args:
|
|
586
|
+
product_id (str)
|
|
587
|
+
The product_id to retrieve all existing submission targets.
|
|
588
|
+
|
|
589
|
+
Returns:
|
|
590
|
+
List[Str]: The ordered list with targets to lookup.
|
|
591
|
+
"""
|
|
592
|
+
all_targets = ["live", "preview", "draft"]
|
|
593
|
+
computed_targets = []
|
|
594
|
+
|
|
595
|
+
# We cannot simply return all targets above because the existing product might
|
|
596
|
+
# lack one of them. So now we need to filter out unexisting targets.
|
|
597
|
+
product_submissions = self.get_submissions(product_id)
|
|
598
|
+
product_targets = [s.target.targetType for s in product_submissions]
|
|
599
|
+
for t in all_targets:
|
|
600
|
+
if t in product_targets:
|
|
601
|
+
computed_targets.append(t)
|
|
602
|
+
return computed_targets
|
|
603
|
+
|
|
494
604
|
def _is_submission_in_preview(self, current: ProductSubmission) -> bool:
|
|
495
605
|
"""Return True if the latest submission state is "preview", False otherwise.
|
|
496
606
|
|
|
@@ -518,42 +628,33 @@ class AzureService(BaseService[AzurePublishingMetadata]):
|
|
|
518
628
|
stop=stop_after_attempt(3),
|
|
519
629
|
reraise=True,
|
|
520
630
|
)
|
|
521
|
-
def _publish_preview(
|
|
631
|
+
def _publish_preview(
|
|
632
|
+
self, product: Product, product_name: str, resources: Optional[List[AzureResource]] = None
|
|
633
|
+
) -> None:
|
|
522
634
|
"""
|
|
523
|
-
Submit the product to 'preview'
|
|
635
|
+
Submit the product to 'preview' after going through Azure Marketplace Validatoin.
|
|
524
636
|
|
|
525
637
|
This is required to execute the validation pipeline on Azure side.
|
|
526
638
|
|
|
527
639
|
Args:
|
|
528
640
|
product
|
|
529
|
-
The product with changes to publish
|
|
641
|
+
The product with changes to publish to preview
|
|
530
642
|
product_name
|
|
531
643
|
The product name to display in logs.
|
|
644
|
+
resources:
|
|
645
|
+
Additional resources for modular push.
|
|
532
646
|
"""
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
if not self._is_submission_in_preview(submission):
|
|
543
|
-
log.info(
|
|
544
|
-
"Submitting the product \"%s (%s)\" to \"preview\"." % (product_name, product.id)
|
|
647
|
+
res = self.submit_to_status(product_id=product.id, status='preview', resources=resources)
|
|
648
|
+
|
|
649
|
+
if res.job_result != 'succeeded' or not self.get_submission_state(
|
|
650
|
+
product.id, state="preview"
|
|
651
|
+
):
|
|
652
|
+
errors = "\n".join(res.errors)
|
|
653
|
+
failure_msg = (
|
|
654
|
+
f"Failed to submit the product {product_name} ({product.id}) to preview. "
|
|
655
|
+
f"Status: {res.job_result} Errors: {errors}"
|
|
545
656
|
)
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
if res.job_result != 'succeeded' or not self.get_submission_state(
|
|
549
|
-
product.id, state="preview"
|
|
550
|
-
):
|
|
551
|
-
errors = "\n".join(res.errors)
|
|
552
|
-
failure_msg = (
|
|
553
|
-
f"Failed to submit the product {product.id} to preview. "
|
|
554
|
-
f"Status: {res.job_result} Errors: {errors}"
|
|
555
|
-
)
|
|
556
|
-
raise RuntimeError(failure_msg)
|
|
657
|
+
raise RuntimeError(failure_msg)
|
|
557
658
|
|
|
558
659
|
@retry(
|
|
559
660
|
wait=wait_fixed(wait=60),
|
|
@@ -572,17 +673,133 @@ class AzureService(BaseService[AzurePublishingMetadata]):
|
|
|
572
673
|
"""
|
|
573
674
|
# Note: the offer can only go `live` after successfully being changed to `preview`
|
|
574
675
|
# which takes up to 4 days.
|
|
575
|
-
log.info("Submitting the product \"%s (%s)\" to \"live\"." % (product_name, product.id))
|
|
576
676
|
res = self.submit_to_status(product_id=product.id, status='live')
|
|
577
677
|
|
|
578
678
|
if res.job_result != 'succeeded' or not self.get_submission_state(product.id, state="live"):
|
|
579
679
|
errors = "\n".join(res.errors)
|
|
580
680
|
failure_msg = (
|
|
581
|
-
f"Failed to submit the product {product.id} to live. "
|
|
681
|
+
f"Failed to submit the product {product_name} ({product.id}) to live. "
|
|
582
682
|
f"Status: {res.job_result} Errors: {errors}"
|
|
583
683
|
)
|
|
584
684
|
raise RuntimeError(failure_msg)
|
|
585
685
|
|
|
686
|
+
def _overwrite_disk_version(
|
|
687
|
+
self,
|
|
688
|
+
metadata: AzurePublishingMetadata,
|
|
689
|
+
product_name: str,
|
|
690
|
+
plan_name: str,
|
|
691
|
+
source: VMImageSource,
|
|
692
|
+
target: str,
|
|
693
|
+
) -> TechnicalConfigLookUpData:
|
|
694
|
+
"""Private method to overwrite the technical config with a new DiskVersion.
|
|
695
|
+
|
|
696
|
+
Args:
|
|
697
|
+
metadata (AzurePublishingMetadata): the incoming publishing metadata
|
|
698
|
+
product_name (str): the product (offer) name
|
|
699
|
+
plan_name (str): the plan name
|
|
700
|
+
source (VMImageSource): the source VMI to create and overwrite the new DiskVersion
|
|
701
|
+
target (str): the submission target.
|
|
702
|
+
|
|
703
|
+
Returns:
|
|
704
|
+
TechnicalConfigLookUpData: The overwritten tech_config for the product/plan
|
|
705
|
+
"""
|
|
706
|
+
product, plan = self.get_product_plan_by_name(product_name, plan_name, target)
|
|
707
|
+
log.warning(
|
|
708
|
+
"Overwriting the plan \"%s\" on \"%s\" with the given image: \"%s\".",
|
|
709
|
+
plan_name,
|
|
710
|
+
target,
|
|
711
|
+
metadata.image_path,
|
|
712
|
+
)
|
|
713
|
+
tech_config = self.get_plan_tech_config(product, plan)
|
|
714
|
+
disk_version = create_disk_version_from_scratch(metadata, source)
|
|
715
|
+
tech_config.disk_versions = [disk_version]
|
|
716
|
+
return {
|
|
717
|
+
"metadata": metadata,
|
|
718
|
+
"tech_config": tech_config,
|
|
719
|
+
"sas_found": False,
|
|
720
|
+
"product": product,
|
|
721
|
+
"plan": plan,
|
|
722
|
+
"target": target,
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
def _look_up_sas_on_technical_config(
|
|
726
|
+
self, metadata: AzurePublishingMetadata, product_name: str, plan_name: str, target: str
|
|
727
|
+
) -> TechnicalConfigLookUpData:
|
|
728
|
+
"""Private method to lookup for the TechnicalConfig of a given target.
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
metadata (AzurePublishingMetadata): the incoming publishing metadata.
|
|
732
|
+
product_name (str): the product (offer) name
|
|
733
|
+
plan_name (str): the plan name
|
|
734
|
+
target (str): the submission target to look up the TechnicalConfig object
|
|
735
|
+
|
|
736
|
+
Returns:
|
|
737
|
+
TechnicalConfigLookUpData: The data retrieved for the given submission target.
|
|
738
|
+
"""
|
|
739
|
+
product, plan = self.get_product_plan_by_name(product_name, plan_name, target)
|
|
740
|
+
log.info(
|
|
741
|
+
"Retrieving the technical config for \"%s\" on \"%s\".",
|
|
742
|
+
metadata.destination,
|
|
743
|
+
target,
|
|
744
|
+
)
|
|
745
|
+
tech_config = self.get_plan_tech_config(product, plan)
|
|
746
|
+
sas_found = False
|
|
747
|
+
|
|
748
|
+
if is_sas_present(tech_config, metadata.image_path, metadata.check_base_sas_only):
|
|
749
|
+
log.info(
|
|
750
|
+
"The destination \"%s\" on \"%s\" already contains the SAS URI: \"%s\".",
|
|
751
|
+
metadata.destination,
|
|
752
|
+
target,
|
|
753
|
+
metadata.image_path,
|
|
754
|
+
)
|
|
755
|
+
sas_found = True
|
|
756
|
+
return {
|
|
757
|
+
"metadata": metadata,
|
|
758
|
+
"tech_config": tech_config,
|
|
759
|
+
"sas_found": sas_found,
|
|
760
|
+
"product": product,
|
|
761
|
+
"plan": plan,
|
|
762
|
+
"target": target,
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
def _create_or_update_disk_version(
|
|
766
|
+
self,
|
|
767
|
+
tech_config_lookup: TechnicalConfigLookUpData,
|
|
768
|
+
source: VMImageSource,
|
|
769
|
+
disk_version: Optional[DiskVersion],
|
|
770
|
+
) -> DiskVersion:
|
|
771
|
+
"""Private method to create/update the DiskVersion of a given TechnicalConfig object.
|
|
772
|
+
|
|
773
|
+
Args:
|
|
774
|
+
tech_config_lookup (TechnicalConfigLookUpData): the incoming data to process
|
|
775
|
+
source (VMImageSource): the new VMI source to attach
|
|
776
|
+
disk_version (Optional[DiskVersion]): the disk version if it exists (for updates).
|
|
777
|
+
|
|
778
|
+
Returns:
|
|
779
|
+
DiskVersion: The updated DiskVersion
|
|
780
|
+
"""
|
|
781
|
+
metadata = tech_config_lookup["metadata"]
|
|
782
|
+
target = tech_config_lookup["target"]
|
|
783
|
+
tech_config = tech_config_lookup["tech_config"]
|
|
784
|
+
|
|
785
|
+
# Check the images of the selected DiskVersion if it exists
|
|
786
|
+
if disk_version:
|
|
787
|
+
log.info(
|
|
788
|
+
"DiskVersion \"%s\" exists in \"%s\" on \"%s\" for the image \"%s\".",
|
|
789
|
+
disk_version.version_number,
|
|
790
|
+
metadata.destination,
|
|
791
|
+
target,
|
|
792
|
+
metadata.image_path,
|
|
793
|
+
)
|
|
794
|
+
# Update the disk version with the new SAS
|
|
795
|
+
disk_version = set_new_sas_disk_version(disk_version, metadata, source)
|
|
796
|
+
return disk_version
|
|
797
|
+
# The disk version doesn't exist, we need to create one from scratch
|
|
798
|
+
log.info("The DiskVersion doesn't exist, creating one from scratch.")
|
|
799
|
+
disk_version = create_disk_version_from_scratch(metadata, source)
|
|
800
|
+
tech_config.disk_versions.append(disk_version)
|
|
801
|
+
return disk_version
|
|
802
|
+
|
|
586
803
|
def publish(self, metadata: AzurePublishingMetadata) -> None:
|
|
587
804
|
"""
|
|
588
805
|
Associate a VM image with a given product listing (destination) and publish it if required.
|
|
@@ -596,71 +813,102 @@ class AzureService(BaseService[AzurePublishingMetadata]):
|
|
|
596
813
|
# "product-name/plan-name"
|
|
597
814
|
product_name = metadata.destination.split("/")[0]
|
|
598
815
|
plan_name = metadata.destination.split("/")[-1]
|
|
599
|
-
|
|
816
|
+
product_id = self.get_productid(product_name)
|
|
817
|
+
sas_in_target = SasFoundStatus.missing
|
|
600
818
|
log.info(
|
|
601
|
-
"Preparing to associate the image with the plan \"%s\" from product \"%s\""
|
|
602
|
-
|
|
819
|
+
"Preparing to associate the image \"%s\" with the plan \"%s\" from product \"%s\"",
|
|
820
|
+
metadata.image_path,
|
|
821
|
+
plan_name,
|
|
822
|
+
product_name,
|
|
603
823
|
)
|
|
604
824
|
|
|
605
|
-
# 2.
|
|
606
|
-
log.
|
|
607
|
-
tech_config = self.get_plan_tech_config(product, plan)
|
|
608
|
-
|
|
609
|
-
# 3. Prepare the Disk Version
|
|
610
|
-
log.debug("Creating the VMImageResource with SAS: \"%s\"" % metadata.image_path)
|
|
825
|
+
# 2. Prepare the Disk Version
|
|
826
|
+
log.info("Creating the VMImageResource with SAS for image: \"%s\"", metadata.image_path)
|
|
611
827
|
sas = OSDiskURI(uri=metadata.image_path)
|
|
612
828
|
source = VMImageSource(source_type="sasUri", os_disk=sas.to_json(), data_disks=[])
|
|
613
829
|
|
|
830
|
+
# 3. Set the new Disk Version into the product/plan if required
|
|
831
|
+
#
|
|
614
832
|
# Note: If `overwrite` is True it means we can set this VM image as the only one in the
|
|
615
833
|
# plan's technical config and discard all other VM images which may've been present.
|
|
616
|
-
disk_version = None # just to make mypy happy
|
|
617
834
|
if metadata.overwrite is True:
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
tech_config
|
|
621
|
-
|
|
622
|
-
# We just want to append a new image if the SAS is not already present.
|
|
623
|
-
elif not is_sas_present(tech_config, metadata.image_path, metadata.check_base_sas_only):
|
|
624
|
-
# Here we can have the metadata.disk_version set or empty.
|
|
625
|
-
# When set we want to get the existing disk_version which matches its value.
|
|
626
|
-
log.debug("Scanning the disk versions from %s" % metadata.destination)
|
|
627
|
-
disk_version = seek_disk_version(tech_config, metadata.disk_version)
|
|
628
|
-
|
|
629
|
-
# Check the images of the selected DiskVersion if it exists
|
|
630
|
-
if disk_version:
|
|
631
|
-
log.debug(
|
|
632
|
-
"DiskVersion \"%s\" exists in \"%s\"."
|
|
633
|
-
% (disk_version.version_number, metadata.destination)
|
|
634
|
-
)
|
|
635
|
-
disk_version = set_new_sas_disk_version(disk_version, metadata, source)
|
|
636
|
-
|
|
637
|
-
else: # The disk version doesn't exist, we need to create one from scratch
|
|
638
|
-
log.debug("The DiskVersion doesn't exist, creating one from scratch.")
|
|
639
|
-
disk_version = create_disk_version_from_scratch(metadata, source)
|
|
640
|
-
tech_config.disk_versions.append(disk_version)
|
|
835
|
+
target = "draft" # It's expected to exist for whenever product.
|
|
836
|
+
res = self._overwrite_disk_version(metadata, product_name, plan_name, source, target)
|
|
837
|
+
tech_config = res["tech_config"]
|
|
641
838
|
else:
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
839
|
+
# Otherwise we need to check whether SAS isn't already present
|
|
840
|
+
# in any of the targets "preview", "live" or "draft" and if not attach and publish it.
|
|
841
|
+
for target in self.compute_targets(product_id):
|
|
842
|
+
res = self._look_up_sas_on_technical_config(
|
|
843
|
+
metadata, product_name, plan_name, target
|
|
844
|
+
)
|
|
845
|
+
tech_config = res["tech_config"]
|
|
846
|
+
# We don't want to seek for SAS anymore as it was already found
|
|
847
|
+
if res["sas_found"]:
|
|
848
|
+
sas_in_target = SasFoundStatus[target]
|
|
849
|
+
break
|
|
850
|
+
else:
|
|
851
|
+
# At this point there's no SAS URI in any target so we can safely add it
|
|
852
|
+
|
|
853
|
+
# Here we can have the metadata.disk_version set or empty.
|
|
854
|
+
# When set we want to get the existing disk_version which matches its value.
|
|
855
|
+
log.info(
|
|
856
|
+
"Scanning the disk versions from \"%s\" on \"%s\" for the image \"%s\"",
|
|
857
|
+
metadata.destination,
|
|
858
|
+
target,
|
|
859
|
+
metadata.image_path,
|
|
860
|
+
)
|
|
861
|
+
dv = seek_disk_version(tech_config, metadata.disk_version)
|
|
862
|
+
self._create_or_update_disk_version(res, source, dv)
|
|
646
863
|
|
|
647
864
|
# 4. With the updated disk_version we should adjust the SKUs and submit the changes
|
|
648
|
-
if
|
|
649
|
-
log.
|
|
865
|
+
if sas_in_target == SasFoundStatus.missing:
|
|
866
|
+
log.info("Updating SKUs for \"%s\" on \"%s\".", metadata.destination, target)
|
|
650
867
|
tech_config.skus = update_skus(
|
|
651
868
|
disk_versions=tech_config.disk_versions,
|
|
652
869
|
generation=metadata.generation,
|
|
653
870
|
plan_name=plan_name,
|
|
654
871
|
old_skus=tech_config.skus,
|
|
655
872
|
)
|
|
656
|
-
log.
|
|
657
|
-
|
|
873
|
+
log.info(
|
|
874
|
+
"Updating the technical configuration for \"%s\" on \"%s\".",
|
|
875
|
+
metadata.destination,
|
|
876
|
+
target,
|
|
877
|
+
)
|
|
878
|
+
self.configure(resources=[tech_config])
|
|
658
879
|
|
|
659
880
|
# 5. Proceed to publishing if it was requested.
|
|
660
881
|
# Note: The publishing will only occur if it made changes in disk_version.
|
|
661
|
-
if
|
|
662
|
-
|
|
663
|
-
|
|
882
|
+
if not metadata.keepdraft:
|
|
883
|
+
product = res["product"]
|
|
884
|
+
# Get the submission state
|
|
885
|
+
submission: ProductSubmission = cast(
|
|
886
|
+
List[ProductSubmission],
|
|
887
|
+
self.filter_product_resources(product=product, resource="submission"),
|
|
888
|
+
)[0]
|
|
889
|
+
|
|
890
|
+
# We should only publish if there are new changes OR
|
|
891
|
+
# the existing offer was already in preview
|
|
892
|
+
if sas_in_target <= SasFoundStatus.draft or self._is_submission_in_preview(submission):
|
|
893
|
+
log.info(
|
|
894
|
+
"Publishing the new changes for \"%s\" on plan \"%s\"", product_name, plan_name
|
|
895
|
+
)
|
|
896
|
+
logdiff(self.diff_offer(product, target))
|
|
897
|
+
self.ensure_can_publish(product.id)
|
|
898
|
+
|
|
899
|
+
# According to the documentation we only need to pass the
|
|
900
|
+
# required resources for modular publish on "preview"
|
|
901
|
+
# https://learn.microsoft.com/en-us/partner-center/marketplace-offers/product-ingestion-api#method-2-publish-specific-draft-resources-also-known-as-modular-publish # noqa: E501
|
|
902
|
+
modular_resources = None
|
|
903
|
+
if metadata.modular_push:
|
|
904
|
+
modular_resources = self.get_modular_resources_to_publish(product, tech_config)
|
|
905
|
+
if sas_in_target < SasFoundStatus.preview:
|
|
906
|
+
self._publish_preview(product, product_name, resources=modular_resources)
|
|
907
|
+
if sas_in_target < SasFoundStatus.live:
|
|
908
|
+
self._publish_live(product, product_name)
|
|
664
909
|
|
|
665
|
-
|
|
666
|
-
|
|
910
|
+
log.info(
|
|
911
|
+
"Finished publishing the image \"%s\" to \"%s\"",
|
|
912
|
+
metadata.image_path,
|
|
913
|
+
metadata.destination,
|
|
914
|
+
)
|
cloudpub/ms_azure/session.py
CHANGED
|
@@ -24,7 +24,7 @@ class AccessToken:
|
|
|
24
24
|
"""
|
|
25
25
|
self.expires_on = datetime.fromtimestamp(int(json["expires_on"]))
|
|
26
26
|
self.access_token = json["access_token"]
|
|
27
|
-
log.debug(
|
|
27
|
+
log.debug("Obtained token with expiration date on %s", self.expires_on)
|
|
28
28
|
|
|
29
29
|
def is_expired(self) -> bool:
|
|
30
30
|
"""Return True if the token is expired and False otherwise."""
|
|
@@ -108,7 +108,7 @@ class PartnerPortalSession:
|
|
|
108
108
|
"AZURE_API_SECRET",
|
|
109
109
|
]
|
|
110
110
|
for key in mandatory_keys:
|
|
111
|
-
log.debug(
|
|
111
|
+
log.debug("Validating mandatory key \"%s\"", key)
|
|
112
112
|
if key not in auth_keys.keys() or not auth_keys.get(key):
|
|
113
113
|
err_msg = f'The key/value for "{key}" must be set.'
|
|
114
114
|
log.error(err_msg)
|
|
@@ -117,7 +117,7 @@ class PartnerPortalSession:
|
|
|
117
117
|
|
|
118
118
|
def _login(self) -> AccessToken:
|
|
119
119
|
"""Retrieve the authentication token from Microsoft."""
|
|
120
|
-
log.
|
|
120
|
+
log.debug("Retrieving the bearer token from Microsoft")
|
|
121
121
|
url = self.LOGIN_URL_TMPL.format(**self.auth_keys)
|
|
122
122
|
|
|
123
123
|
headers = {
|
|
@@ -156,7 +156,7 @@ class PartnerPortalSession:
|
|
|
156
156
|
params = {}
|
|
157
157
|
params.update(self._mandatory_params)
|
|
158
158
|
|
|
159
|
-
log.
|
|
159
|
+
log.debug("Sending a %s request to %s", method, path)
|
|
160
160
|
formatted_url = self._prefix_url.format(**self.auth_keys)
|
|
161
161
|
url = join_url(formatted_url, path)
|
|
162
162
|
return self.session.request(method, url=url, params=params, headers=headers, **kwargs)
|
cloudpub/ms_azure/utils.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
2
|
import logging
|
|
3
3
|
from operator import attrgetter
|
|
4
|
-
from typing import Any, Dict, List, Optional, Tuple
|
|
4
|
+
from typing import Any, Dict, List, Optional, Tuple, TypedDict
|
|
5
5
|
|
|
6
6
|
from deepdiff import DeepDiff
|
|
7
7
|
|
|
@@ -9,6 +9,8 @@ from cloudpub.common import PublishingMetadata # Cannot circular import AzurePu
|
|
|
9
9
|
from cloudpub.models.ms_azure import (
|
|
10
10
|
ConfigureStatus,
|
|
11
11
|
DiskVersion,
|
|
12
|
+
PlanSummary,
|
|
13
|
+
Product,
|
|
12
14
|
VMImageDefinition,
|
|
13
15
|
VMImageSource,
|
|
14
16
|
VMIPlanTechConfig,
|
|
@@ -54,6 +56,11 @@ class AzurePublishingMetadata(PublishingMetadata):
|
|
|
54
56
|
check_base_sas_only (bool, optional):
|
|
55
57
|
Indicates to skip checking SAS parameters when set as ``True``.
|
|
56
58
|
Default to ``False``
|
|
59
|
+
modular_push (bool, optional):
|
|
60
|
+
Indicate whether to perform a modular push or not.
|
|
61
|
+
The modular push causes the effect to only publish
|
|
62
|
+
the changed plan instead of the whole offer to preview/live.
|
|
63
|
+
Default to ``False``.
|
|
57
64
|
**kwargs
|
|
58
65
|
Arguments for :class:`~cloudpub.common.PublishingMetadata`.
|
|
59
66
|
"""
|
|
@@ -64,6 +71,7 @@ class AzurePublishingMetadata(PublishingMetadata):
|
|
|
64
71
|
self.recommended_sizes = recommended_sizes or []
|
|
65
72
|
self.legacy_sku_id = kwargs.pop("legacy_sku_id", None)
|
|
66
73
|
self.check_base_sas_only = kwargs.pop("check_base_sas_only", False)
|
|
74
|
+
self.modular_push = kwargs.pop("modular_push", None) or False
|
|
67
75
|
|
|
68
76
|
if generation == "V1" or not support_legacy:
|
|
69
77
|
self.legacy_sku_id = None
|
|
@@ -107,6 +115,17 @@ class AzurePublishingMetadata(PublishingMetadata):
|
|
|
107
115
|
raise ValueError(f"Invalid SAS URI \"{self.image_path}\". Expected: http/https URL.")
|
|
108
116
|
|
|
109
117
|
|
|
118
|
+
class TechnicalConfigLookUpData(TypedDict):
|
|
119
|
+
"""A typed dict to be used for private methods data exchange."""
|
|
120
|
+
|
|
121
|
+
metadata: AzurePublishingMetadata
|
|
122
|
+
tech_config: VMIPlanTechConfig
|
|
123
|
+
sas_found: bool
|
|
124
|
+
product: Product
|
|
125
|
+
plan: PlanSummary
|
|
126
|
+
target: str
|
|
127
|
+
|
|
128
|
+
|
|
110
129
|
def get_image_type_mapping(architecture: str, generation: str) -> str:
|
|
111
130
|
"""Return the image type required by VMImageDefinition."""
|
|
112
131
|
gen_map = {
|
|
@@ -150,22 +169,23 @@ def is_sas_eq(sas1: str, sas2: str, base_only=False) -> bool:
|
|
|
150
169
|
|
|
151
170
|
# Base URL differs
|
|
152
171
|
if base_sas1 != base_sas2:
|
|
153
|
-
log.debug("Got different base SAS: %s - Expected: %s"
|
|
172
|
+
log.debug("Got different base SAS: %s - Expected: %s", base_sas1, base_sas2)
|
|
154
173
|
return False
|
|
155
174
|
|
|
156
175
|
if not base_only:
|
|
157
176
|
# Parameters lengh differs
|
|
158
177
|
if len(params_sas1) != len(params_sas2):
|
|
159
178
|
log.debug(
|
|
160
|
-
"Got different lengh of SAS parameters: len(%s) - Expected len(%s)"
|
|
161
|
-
|
|
179
|
+
"Got different lengh of SAS parameters: len(%s) - Expected len(%s)",
|
|
180
|
+
params_sas1,
|
|
181
|
+
params_sas2,
|
|
162
182
|
)
|
|
163
183
|
return False
|
|
164
184
|
|
|
165
185
|
# Parameters values differs
|
|
166
186
|
for k, v in params_sas1.items():
|
|
167
187
|
if v != params_sas2.get(k, None):
|
|
168
|
-
log.debug("The SAS parameter %s doesn't match %s."
|
|
188
|
+
log.debug("The SAS parameter %s doesn't match %s.", v, params_sas2.get(k, None))
|
|
169
189
|
return False
|
|
170
190
|
|
|
171
191
|
# Equivalent SAS
|
|
@@ -203,8 +223,8 @@ def is_azure_job_not_complete(job_details: ConfigureStatus) -> bool:
|
|
|
203
223
|
Returns:
|
|
204
224
|
bool: False if job completed, True otherwise
|
|
205
225
|
"""
|
|
206
|
-
log.debug(
|
|
207
|
-
log.debug(
|
|
226
|
+
log.debug("Checking if the job \"%s\" is still running", job_details.job_id)
|
|
227
|
+
log.debug("job %s is in %s state", job_details.job_id, job_details.job_status)
|
|
208
228
|
if job_details.job_status != "completed":
|
|
209
229
|
return True
|
|
210
230
|
return False
|
|
@@ -556,16 +576,18 @@ def set_new_sas_disk_version(
|
|
|
556
576
|
Returns:
|
|
557
577
|
The changed disk version with the given source.
|
|
558
578
|
"""
|
|
579
|
+
log.info("Setting up a new SAS disk version for \"%s\"", metadata.image_path)
|
|
559
580
|
# If we already have a VMImageDefinition let's use it
|
|
560
581
|
if disk_version.vm_images:
|
|
561
|
-
log.debug("The DiskVersion \"%s\" contains inner images."
|
|
582
|
+
log.debug("The DiskVersion \"%s\" contains inner images.", disk_version.version_number)
|
|
562
583
|
img, img_legacy = vm_images_by_generation(disk_version, metadata.architecture)
|
|
563
584
|
|
|
564
585
|
# Now we replace the SAS URI for the vm_images
|
|
565
|
-
log.
|
|
586
|
+
log.info(
|
|
566
587
|
"Adjusting the VMImages from existing DiskVersion \"%s\""
|
|
567
|
-
"to fit the new image with SAS \"%s\"."
|
|
568
|
-
|
|
588
|
+
"to fit the new image with SAS \"%s\".",
|
|
589
|
+
disk_version.version_number,
|
|
590
|
+
metadata.image_path,
|
|
569
591
|
)
|
|
570
592
|
disk_version.vm_images = prepare_vm_images(
|
|
571
593
|
metadata=metadata,
|
|
@@ -577,11 +599,12 @@ def set_new_sas_disk_version(
|
|
|
577
599
|
# If no VMImages, we need to create them from scratch
|
|
578
600
|
else:
|
|
579
601
|
log.debug(
|
|
580
|
-
"The DiskVersion \"%s\" does not contain inner images."
|
|
602
|
+
"The DiskVersion \"%s\" does not contain inner images.", disk_version.version_number
|
|
581
603
|
)
|
|
582
|
-
log.
|
|
583
|
-
"Setting the new image \"%s\" on DiskVersion \"%s\"."
|
|
584
|
-
|
|
604
|
+
log.info(
|
|
605
|
+
"Setting the new image \"%s\" on DiskVersion \"%s\".",
|
|
606
|
+
metadata.image_path,
|
|
607
|
+
disk_version.version_number,
|
|
585
608
|
)
|
|
586
609
|
disk_version.vm_images = create_vm_image_definitions(metadata, source)
|
|
587
610
|
|
|
@@ -591,4 +614,4 @@ def set_new_sas_disk_version(
|
|
|
591
614
|
def logdiff(diff: DeepDiff) -> None:
|
|
592
615
|
"""Log the offer diff if it exists."""
|
|
593
616
|
if diff:
|
|
594
|
-
log.warning(
|
|
617
|
+
log.warning("Found the following offer diff before publishing:\n%s", diff.pretty())
|
|
@@ -10,11 +10,11 @@ cloudpub/models/aws.py,sha256=arzFqLmFw8O9Otk_VatLR5dmQ9FsdWT3f0Ibap7EW0o,42850
|
|
|
10
10
|
cloudpub/models/common.py,sha256=iZ503VVFL9y0P_wXiK0f3flXV32VWBs9i-9NoYfJZUg,4970
|
|
11
11
|
cloudpub/models/ms_azure.py,sha256=nzTp9IvAW-WEJuN20IAc93yY6YPHCTE0j116EfQUsPg,55974
|
|
12
12
|
cloudpub/ms_azure/__init__.py,sha256=eeYXPd_wzDBmh0Hmzd5o4yzocFzM6n4r8qpCDy00kYk,117
|
|
13
|
-
cloudpub/ms_azure/service.py,sha256=
|
|
14
|
-
cloudpub/ms_azure/session.py,sha256=
|
|
15
|
-
cloudpub/ms_azure/utils.py,sha256=
|
|
16
|
-
cloudpub-1.
|
|
17
|
-
cloudpub-1.
|
|
18
|
-
cloudpub-1.
|
|
19
|
-
cloudpub-1.
|
|
20
|
-
cloudpub-1.
|
|
13
|
+
cloudpub/ms_azure/service.py,sha256=ySc3ktibNhMw-ZvTgTIB1EZjliYu0OYSBi8sRBlpslg,36250
|
|
14
|
+
cloudpub/ms_azure/session.py,sha256=PXCSJ1dFkx43lQV0WFPnRxbpyOBccdtrMiWGPORT3Ro,6356
|
|
15
|
+
cloudpub/ms_azure/utils.py,sha256=goADEmIZBFuIDG5sH9dAZed_EWIUWUAtjzDTw9HyNsI,21512
|
|
16
|
+
cloudpub-1.6.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
17
|
+
cloudpub-1.6.0.dist-info/METADATA,sha256=bXsSCTG8RdWlsD5SjlqYrWgxsOQKOL5bRwk-fVVVP6Y,754
|
|
18
|
+
cloudpub-1.6.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
19
|
+
cloudpub-1.6.0.dist-info/top_level.txt,sha256=YnnJuTiWBpRI9zMkYUVcZNuvjzzJYblASj-7Q8m3Gzg,9
|
|
20
|
+
cloudpub-1.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|