|
18 | 18 | import pathlib |
19 | 19 |
|
20 | 20 | from . import model, tag, utils, jasyncio |
| 21 | +from .url import URL, Schema |
21 | 22 | from .status import derive_status |
22 | 23 | from .annotationhelper import _get_annotations, _set_annotations |
23 | 24 | from .client import client |
24 | 25 | from .errors import JujuError, JujuApplicationConfigError |
25 | 26 | from .bundle import get_charm_series |
26 | 27 | from .placement import parse as parse_placement |
| 28 | +from .origin import Channel |
27 | 29 |
|
28 | 30 | log = logging.getLogger(__name__) |
29 | 31 |
|
@@ -641,92 +643,135 @@ async def refresh( |
641 | 643 | if switch is not None and revision is not None: |
642 | 644 | raise ValueError("switch and revision are mutually exclusive") |
643 | 645 |
|
644 | | - charms_facade = client.CharmsFacade.from_connection(self.connection) |
645 | | - resources_facade = client.ResourcesFacade.from_connection( |
646 | | - self.connection) |
647 | 646 | app_facade = self._facade() |
| 647 | + resources_facade = client.ResourcesFacade.from_connection(self.connection) |
| 648 | + charms_facade = client.CharmsFacade.from_connection(self.connection) |
648 | 649 |
|
649 | | - charmstore = self.model.charmstore |
650 | | - charmstore_entity = None |
651 | | - |
652 | | - if switch is not None: |
653 | | - charm_url = switch |
654 | | - if not charm_url.startswith('cs:'): |
655 | | - charm_url = 'cs:' + charm_url |
| 650 | + # 1 - Figure out the destination origin and destination charm_url |
| 651 | + # 2 - Then take care of the resources |
| 652 | + # 3 - Finally execute the upgrade |
| 653 | + |
| 654 | + # Get the charm URL and charm origin of the given application is running at present. |
| 655 | + charm_url_origin_result = await app_facade.GetCharmURLOrigin(application=self.name) |
| 656 | + if charm_url_origin_result.error is not None: |
| 657 | + err = charm_url_origin_result.error |
| 658 | + raise JujuError(f'{err.code} : {err.message}') |
| 659 | + charm_url = switch or charm_url_origin_result.url |
| 660 | + origin = charm_url_origin_result.charm_origin |
| 661 | + |
| 662 | + parsed_url = URL.parse(charm_url) |
| 663 | + charm_name = parsed_url.name |
| 664 | + |
| 665 | + if parsed_url.schema is None: |
| 666 | + raise JujuError(f'A ch: or cs: schema is required for application refresh, given : {str(parsed_url)}') |
| 667 | + |
| 668 | + if revision is not None: |
| 669 | + origin.revision = revision |
| 670 | + |
| 671 | + # Make the source-specific changes to the origin/channel/url |
| 672 | + # (and also get the resources necessary to deploy the (destination) charm -- for later) |
| 673 | + if Schema.CHARM_HUB.matches(parsed_url.schema): |
| 674 | + origin.source = 'charm-hub' |
| 675 | + if channel: |
| 676 | + ch = Channel.parse(channel).normalize() |
| 677 | + origin.risk = ch.risk |
| 678 | + origin.track = ch.track |
| 679 | + |
| 680 | + charmhub = self.model.charmhub |
| 681 | + charm_resources = await charmhub.list_resources(charm_name) |
656 | 682 | else: |
657 | | - charm_url = self.data['charm-url'] |
658 | | - charm_url = charm_url.rpartition('-')[0] |
659 | | - if revision is not None: |
660 | | - charm_url = "%s-%d" % (charm_url, revision) |
661 | | - else: |
662 | | - charmstore_entity = await charmstore.entity(charm_url, |
663 | | - channel=channel) |
664 | | - charm_url = charmstore_entity['Id'] |
665 | | - |
666 | | - if charm_url == self.data['charm-url']: |
667 | | - raise JujuError('already running charm "%s"' % charm_url) |
668 | | - |
669 | | - # TODO (caner) : this needs to be revisited and updated with the charmhub stuff |
670 | | - origin = client.CharmOrigin(source="charm-store", |
671 | | - risk=channel, |
672 | | - ) |
673 | | - # Update charm |
674 | | - await charms_facade.AddCharm( |
675 | | - url=charm_url, |
676 | | - force=force, |
677 | | - charm_origin=origin, |
678 | | - ) |
679 | | - |
680 | | - # Update resources |
681 | | - if not charmstore_entity: |
682 | | - charmstore_entity = await charmstore.entity(charm_url, |
683 | | - channel=channel) |
684 | | - store_resources = charmstore_entity['Meta']['resources'] |
685 | | - |
| 683 | + charmstore = self.model.charmstore |
| 684 | + charmstore_entity = None |
| 685 | + if switch is None: |
| 686 | + charm_url = charm_url.rpartition('-')[0] |
| 687 | + if revision is not None: |
| 688 | + charm_url = "%s-%d" % (charm_url, revision) |
| 689 | + else: |
| 690 | + charmstore_entity = await charmstore.entity(charm_url, channel=channel) |
| 691 | + charm_url = charmstore_entity['Id'] |
| 692 | + origin.source = 'charm-store' |
| 693 | + if channel: |
| 694 | + origin.risk = channel |
| 695 | + if charmstore_entity is None: |
| 696 | + charmstore_entity = await charmstore.entity(charm_url, channel=channel) |
| 697 | + charm_resources = charmstore_entity['Meta']['resources'] |
| 698 | + |
| 699 | + # Resolve the given charm URLs with an optionally specified preferred channel. |
| 700 | + # Channel provided via CharmOrigin. |
| 701 | + resolved_charm_with_channel_results = await charms_facade.ResolveCharms( |
| 702 | + resolve=[client.ResolveCharmWithChannel( |
| 703 | + charm_origin=origin, |
| 704 | + switch_charm=True if switch else False, # rpc expects boolean type |
| 705 | + reference=charm_url, |
| 706 | + )]) |
| 707 | + resolved_charm = resolved_charm_with_channel_results.results[0] |
| 708 | + |
| 709 | + # Get the destination origin and destination charm_url from the resolved charm |
| 710 | + if resolved_charm.error is not None: |
| 711 | + err = resolved_charm.error |
| 712 | + raise JujuError(f'{err.code} : {err.message}') |
| 713 | + dest_origin = resolved_charm.charm_origin |
| 714 | + charm_url = resolved_charm.url |
| 715 | + |
| 716 | + # Add the charm with the destination url and origin |
| 717 | + charm_origin_result = await charms_facade.AddCharm(url=charm_url, |
| 718 | + force=force, |
| 719 | + charm_origin=dest_origin) |
| 720 | + if charm_origin_result.error is not None: |
| 721 | + err = charm_origin_result.error |
| 722 | + raise JujuError(f'{err.code} : {err.message}') |
| 723 | + |
| 724 | + # Now take care of the resources: |
| 725 | + |
| 726 | + # Already prepped the charm_resources |
| 727 | + # Now get the existing resources from the ResourcesFacade |
686 | 728 | request_data = [client.Entity(self.tag)] |
687 | 729 | response = await resources_facade.ListResources(entities=request_data) |
688 | 730 | existing_resources = { |
689 | 731 | resource.name: resource |
690 | 732 | for resource in response.results[0].resources |
691 | 733 | } |
692 | 734 |
|
| 735 | + # Compute the difference btw resources needed and the existing resources |
693 | 736 | resources_to_update = [ |
694 | | - resource for resource in store_resources |
695 | | - if resource['Name'] not in existing_resources or |
696 | | - existing_resources[resource['Name']].origin != 'upload' |
| 737 | + resource for resource in charm_resources |
| 738 | + if resource.get('Name', resource.get('name')) not in existing_resources or |
| 739 | + existing_resources[resource.get('Name', resource.get('name'))].origin != 'upload' |
697 | 740 | ] |
698 | 741 |
|
| 742 | + # Update the resources |
699 | 743 | if resources_to_update: |
700 | | - request_data = [ |
701 | | - client.CharmResource( |
702 | | - description=resource.get('Description'), |
703 | | - fingerprint=resource['Fingerprint'], |
704 | | - name=resource['Name'], |
705 | | - path=resource['Path'], |
706 | | - revision=resource['Revision'], |
707 | | - size=resource['Size'], |
708 | | - type_=resource['Type'], |
| 744 | + request_data = [] |
| 745 | + for resource in resources_to_update: |
| 746 | + request_data.append(client.CharmResource( |
| 747 | + description=resource.get('Description', resource.get('description')), |
| 748 | + fingerprint=resource.get('Fingerprint', resource.get('fingerprint')), |
| 749 | + name=resource.get('Name', resource.get('name')), |
| 750 | + path=resource.get('Path', resource.get('filename')), |
| 751 | + revision=resource.get('Revision', resource.get('revision', -1)), |
| 752 | + size=resource.get('Size', resource.get('size')), |
| 753 | + type_=resource.get('Type', resource.get('type')), |
709 | 754 | origin='store', |
710 | | - ) for resource in resources_to_update |
711 | | - ] |
| 755 | + )) |
712 | 756 | response = await resources_facade.AddPendingResources( |
713 | 757 | application_tag=self.tag, |
714 | 758 | charm_url=charm_url, |
715 | | - resources=request_data |
| 759 | + resources=request_data, |
| 760 | + charm_origin=dest_origin, |
716 | 761 | ) |
717 | 762 | pending_ids = response.pending_ids |
718 | 763 | resource_ids = { |
719 | | - resource['Name']: id |
| 764 | + resource.get('Name', resource.get('name')): id |
720 | 765 | for resource, id in zip(resources_to_update, pending_ids) |
721 | 766 | } |
722 | 767 | else: |
723 | 768 | resource_ids = None |
724 | 769 |
|
725 | | - # Update application |
| 770 | + # Update the application |
726 | 771 | await app_facade.SetCharm( |
727 | 772 | application=self.entity_id, |
728 | | - channel=channel, |
729 | 773 | charm_url=charm_url, |
| 774 | + charm_origin=dest_origin, |
730 | 775 | config_settings=None, |
731 | 776 | config_settings_yaml=None, |
732 | 777 | force=force, |
|
0 commit comments