3535from .names import is_valid_application
3636from .offerendpoints import ParseError as OfferParseError
3737from .offerendpoints import parse_local_endpoint , parse_offer_url
38+ from .origin import Channel , Risk
3839from .placement import parse as parse_placement
3940from .tag import application as application_tag
4041from .url import URL , Schema
@@ -1424,34 +1425,67 @@ async def deploy(
14241425 is_local = False
14251426 is_bundle = False
14261427 identifier = None
1428+ origin = None
1429+ result = None
1430+
14271431 url = URL .parse (str (entity_url ))
1428- if url .schema .matches (Schema .Local ):
1432+ architecture = await self ._resolve_architecture (url )
1433+
1434+ if Schema .LOCAL .matches (url .schema ):
14291435 entity_url = url .path ()
14301436 entity_path = Path (entity_url )
14311437 bundle_path = entity_path / 'bundle.yaml'
14321438
14331439 identifier = entity_url
1434- is_local = (
1435- entity_path .is_dir () or
1436- entity_path . is_file ( )
1437- )
1440+ origin = client . CharmOrigin ( source = "local" , architecture = architecture )
1441+ if not ( entity_path .is_dir () or entity_path . is_file ()):
1442+ raise JujuError ( '{} path not found' . format ( entity_url ) )
1443+
14381444 is_bundle = (
14391445 (entity_url .endswith (".yaml" ) and entity_path .exists ()) or
14401446 bundle_path .exists ()
14411447 )
1442- elif url .schema .matches (Schema .CHARM_STORE ):
1443- charm = await self .charmstore .entity (str (url ), channel = channel ,
1448+
1449+ elif Schema .CHARM_STORE .matches (url .schema ):
1450+ result = await self .charmstore .entity (str (url ),
1451+ channel = channel ,
14441452 include_stats = False )
1445- identifier = charm ['Id' ]
1453+ identifier = result ['Id' ]
1454+ origin = client .CharmOrigin (source = "charm-store" ,
1455+ architecture = architecture ,
1456+ risk = channel )
14461457 is_bundle = url .series == "bundle"
1447- elif url .schema .matches (Schema .CHARM_HUB ):
1448- # TODO (stickupkid): request to get the charm id.
1449-
1450-
1458+ if not series :
1459+ series = self ._get_series (entity_url , result )
1460+
1461+ elif Schema .CHARM_HUB .matches (url .schema ):
1462+ ch = Channel ('latest' , 'stable' )
1463+ if channel :
1464+ ch = Channel .parse (channel ).normalize ()
1465+ origin = client .CharmOrigin (source = "charm-hub" ,
1466+ architecture = architecture ,
1467+ risk = ch .risk ,
1468+ track = ch .track )
1469+ charm_url , origin = await self ._resolve_charm (url , origin )
1470+
1471+ identifier = charm_url
1472+ is_bundle = origin .type_ == "bundle"
1473+
1474+ if identifier is None :
1475+ raise JujuError ('unknown charm or bundle {}' .format (entity_url ))
1476+
1477+ if not application_name :
1478+ if Schema .LOCAL .matches (url .schema ) and Schema .CHARM_STORE .matches (url .schema ):
1479+ application_name = result ['Meta' ]['charm-metadata' ]['Name' ]
1480+ else :
1481+ # For charmhub charms, we don't have the metadata and we're not
1482+ # going to get it, so fallback to the url and use that one if a
1483+ # user didn't specify it.
1484+ application_name = url .name
14511485
14521486 if is_bundle :
14531487 handler = BundleHandler (self , trusted = trust , forced = force )
1454- await handler .fetch_plan (entity_id )
1488+ await handler .fetch_plan (url , origin )
14551489 await handler .execute_plan ()
14561490 extant_apps = {app for app in self .applications }
14571491 pending_apps = set (handler .applications ) - extant_apps
@@ -1467,42 +1501,47 @@ async def deploy(
14671501 return [app for name , app in self .applications .items ()
14681502 if name in handler .applications ]
14691503 else :
1504+ # XXX: we're dropping local resources here, but we don't
1505+ # actually support them yet anyway
14701506 if not is_local :
1471- if not application_name :
1472- application_name = entity ['Meta' ]['charm-metadata' ]['Name' ]
1473- if not series :
1474- series = self ._get_series (entity_url , entity )
1475-
1476- self ._add_charm (channel , entity_id )
1477- # XXX: we're dropping local resources here, but we don't
1478- # actually support them yet anyway
1479- resources = await self ._add_store_resources (application_name ,
1480- entity_id ,
1481- entity = entity )
1507+ await self ._add_charm (identifier , origin )
1508+
1509+ # TODO (stickupkid): Handle charmhub charms, for now we'll only
1510+ # handle charmstore charms.
1511+ if Schema .CHARM_STORE .matches (url .schema ):
1512+ resources = await self ._add_store_resources (application_name ,
1513+ identifier ,
1514+ entity = result )
14821515 else :
14831516 if not application_name :
1517+ entity_url = url .path ()
1518+ entity_path = Path (entity_url )
14841519 if str (entity_path ).endswith ('.charm' ):
14851520 with zipfile .ZipFile (entity_path , 'r' ) as charm_file :
14861521 metadata = yaml .load (charm_file .read ('metadata.yaml' ), Loader = yaml .FullLoader )
14871522 else :
1523+ metadata_path = entity_path / 'metadata.yaml'
14881524 metadata = yaml .load (metadata_path .read_text (), Loader = yaml .FullLoader )
14891525 application_name = metadata ['name' ]
1526+
14901527 # We have a local charm dir that needs to be uploaded
14911528 charm_dir = os .path .abspath (
1492- os .path .expanduser (entity_id ))
1529+ os .path .expanduser (identifier ))
14931530 series = series or get_charm_series (charm_dir )
14941531 if not series :
14951532 raise JujuError (
14961533 "Couldn't determine series for charm at {}. "
14971534 "Pass a 'series' kwarg to Model.deploy()." .format (
14981535 charm_dir ))
1499- entity_id = await self .add_local_charm_dir (charm_dir , series )
1536+ identifier = await self .add_local_charm_dir (charm_dir , series )
1537+
15001538 if config is None :
15011539 config = {}
15021540 if trust :
15031541 config ["trust" ] = "true"
1542+
15041543 return await self ._deploy (
1505- charm_url = entity_id ,
1544+ charm_url = identifier ,
15061545 application = application_name ,
15071546 series = series ,
15081547 config = config ,
@@ -1516,16 +1555,49 @@ async def deploy(
15161555 devices = devices ,
15171556 )
15181557
1519- async def _add_charm (self , channel , entity_id ):
1558+ async def _add_charm (self , charm_url , origin ):
15201559 # client facade is deprecated with in Juju, and smaller, more focused
15211560 # facades have been created and we'll use that if it's available.
15221561 charms_cls = client .CharmsFacade
15231562 if charms_cls .best_facade_version (self .connection ()) > 2 :
15241563 charms_facade = charms_cls .from_connection (self .connection ())
1525- return await charms_facade .AddCharm (channel = channel , url = entity_id , force = False )
1564+ return await charms_facade .AddCharm (charm_origin = origin , url = charm_url , force = False )
15261565
15271566 client_facade = client .ClientFacade .from_connection (self .connection ())
1528- await client_facade .AddCharm (channel = channel , url = entity_id , force = False )
1567+ await client_facade .AddCharm (channel = origin .channel , url = charm_url , force = False )
1568+
1569+ async def _resolve_charm (self , url , origin ):
1570+ charms_cls = client .CharmsFacade
1571+ if charms_cls .best_facade_version (self .connection ()) < 3 :
1572+ raise JujuError ("resolve charm" )
1573+
1574+ charms_facade = charms_cls .from_connection (self .connection ())
1575+
1576+ resp = await charms_facade .ResolveCharms (resolve = [{
1577+ 'reference' : str (url ),
1578+ 'charm-origin' : {
1579+ 'source' : 'charm-hub' ,
1580+ 'architecture' : origin .architecture ,
1581+ }
1582+ }])
1583+ if len (resp .results ) != 1 :
1584+ raise JujuError ("expected one result, received {}" .format (resp .results ))
1585+
1586+ result = resp .results [0 ]
1587+ if result .error :
1588+ raise JujuError (result .error .message )
1589+
1590+ return (result .url , result .charm_origin )
1591+
1592+ async def _resolve_architecture (self , url ):
1593+ if url .architecture :
1594+ return url .architecture
1595+
1596+ constraints = await self .get_constraints ()
1597+ if 'arch' in constraints :
1598+ return constraints ['arch' ]
1599+
1600+ return "amd64"
15291601
15301602 async def _add_store_resources (self , application , entity_url ,
15311603 overrides = None , entity = None ):
0 commit comments