|
15 | 15 | import asyncio |
16 | 16 | import json |
17 | 17 | import logging |
| 18 | +import zipfile |
| 19 | +import os |
| 20 | +import yaml |
| 21 | +from pathlib import Path |
18 | 22 |
|
19 | 23 | from . import model, tag |
20 | 24 | from .status import derive_status |
21 | 25 | from .annotationhelper import _get_annotations, _set_annotations |
22 | 26 | from .client import client |
23 | 27 | from .errors import JujuError |
| 28 | +from .bundle import get_charm_series |
24 | 29 | from .placement import parse as parse_placement |
25 | 30 |
|
26 | 31 | log = logging.getLogger(__name__) |
@@ -578,9 +583,10 @@ async def refresh( |
578 | 583 | :param str switch: Crossgrade charm url |
579 | 584 |
|
580 | 585 | """ |
581 | | - # TODO: Support local upgrades |
582 | 586 | if path is not None: |
583 | | - raise NotImplementedError("path option is not implemented") |
| 587 | + await self.local_refresh(channel, force, force_series, force_units, |
| 588 | + path, resources) |
| 589 | + return |
584 | 590 | if resources is not None: |
585 | 591 | raise NotImplementedError("resources option is not implemented") |
586 | 592 |
|
@@ -684,6 +690,68 @@ async def refresh( |
684 | 690 |
|
685 | 691 | upgrade_charm = refresh |
686 | 692 |
|
| 693 | + async def local_refresh( |
| 694 | + self, channel=None, force=False, force_series=False, force_units=False, |
| 695 | + path=None, resources=None): |
| 696 | + """Refresh the charm for this application with a local charm. |
| 697 | +
|
| 698 | + :param str channel: Channel to use when getting the charm from the |
| 699 | + charm store, e.g. 'development' |
| 700 | + :param bool force_series: Refresh even if series of deployed |
| 701 | + application is not supported by the new charm |
| 702 | + :param bool force_units: Refresh all units immediately, even if in |
| 703 | + error state |
| 704 | + :param str path: Refresh to a charm located at path |
| 705 | + :param dict resources: Dictionary of resource name/filepath pairs |
| 706 | + :param int revision: Explicit refresh revision |
| 707 | + :param str switch: Crossgrade charm url |
| 708 | +
|
| 709 | + """ |
| 710 | + app_facade = self._facade() |
| 711 | + |
| 712 | + charm_dir = os.path.abspath( |
| 713 | + os.path.expanduser(path)) |
| 714 | + model_config = await self.get_config() |
| 715 | + |
| 716 | + series = get_charm_series(charm_dir) |
| 717 | + if not series: |
| 718 | + model_config = await self.get_config() |
| 719 | + default_series = model_config.get("default-series") |
| 720 | + if default_series: |
| 721 | + series = default_series.value |
| 722 | + |
| 723 | + charm_url = await self.model.add_local_charm_dir(charm_dir, series) |
| 724 | + if resources is not None: |
| 725 | + if str(path).endswith('.charm'): |
| 726 | + with zipfile.ZipFile(path, 'r') as charm_file: |
| 727 | + metadata = yaml.load(charm_file.read('metadata.yaml'), Loader=yaml.FullLoader) |
| 728 | + else: |
| 729 | + entity_path = Path(path) |
| 730 | + metadata_path = entity_path / 'metadata.yaml' |
| 731 | + metadata = yaml.load(metadata_path.read_text(), Loader=yaml.FullLoader) |
| 732 | + resources = await self.model.add_local_resources(self.entity_id, |
| 733 | + charm_url, |
| 734 | + metadata, |
| 735 | + resources=resources) |
| 736 | + |
| 737 | + # Update application |
| 738 | + await app_facade.SetCharm( |
| 739 | + application=self.entity_id, |
| 740 | + channel=channel, |
| 741 | + charm_url=charm_url, |
| 742 | + config_settings=None, |
| 743 | + config_settings_yaml=None, |
| 744 | + force=force, |
| 745 | + force_series=force_series, |
| 746 | + force_units=force_units, |
| 747 | + resource_ids=resources, |
| 748 | + storage_constraints=None, |
| 749 | + ) |
| 750 | + |
| 751 | + await self.model.block_until( |
| 752 | + lambda: self.data['charm-url'] == charm_url |
| 753 | + ) |
| 754 | + |
687 | 755 | async def get_metrics(self): |
688 | 756 | """Get metrics for this application's units. |
689 | 757 |
|
|
0 commit comments