@@ -1746,6 +1746,8 @@ async def deploy(
17461746 if base :
17471747 charm_origin .base = utils .parse_base_arg (base )
17481748
1749+ server_side_deploy = False
1750+
17491751 if res .is_bundle :
17501752 handler = BundleHandler (self , trusted = trust , forced = force )
17511753 await handler .fetch_plan (url , charm_origin , overlays = overlays )
@@ -1777,9 +1779,20 @@ async def deploy(
17771779 else :
17781780 charm_origin = add_charm_res .charm_origin
17791781 if Schema .CHARM_HUB .matches (url .schema ):
1780- resources = await self ._add_charmhub_resources (res .app_name ,
1781- identifier ,
1782- add_charm_res .charm_origin )
1782+
1783+ if client .ApplicationFacade .best_facade_version (self .connection ()) >= 19 :
1784+ server_side_deploy = True
1785+ else :
1786+ # TODO (cderici): this is an awkward workaround for basically not calling
1787+ # the AddPendingResources in case this is a server side deploy.
1788+ # If that's the case, then the store resources (and revisioned local
1789+ # resources) are handled at the server side if this is a server side deploy
1790+ # (local uploads are handled right after we get the pendingIDs returned
1791+ # from the facade call).
1792+ resources = await self ._add_charmhub_resources (res .app_name ,
1793+ identifier ,
1794+ add_charm_res .charm_origin )
1795+
17831796 is_sub = await self .charmhub .is_subordinate (url .name )
17841797 if is_sub :
17851798 if num_units > 1 :
@@ -1832,6 +1845,7 @@ async def deploy(
18321845 charm_origin = charm_origin ,
18331846 attach_storage = attach_storage ,
18341847 force = force ,
1848+ server_side_deploy = server_side_deploy ,
18351849 )
18361850
18371851 async def _add_charm (self , charm_url , origin ):
@@ -2032,42 +2046,42 @@ async def add_local_resources(self, application, entity_url, metadata, resources
20322046 'username' : '' ,
20332047 'password' : '' ,
20342048 }
2035-
20362049 data = yaml .dump (docker_image_details )
2050+ else :
2051+ p = Path (path )
2052+ data = p .read_text () if p .exists () else ''
20372053
2038- hash_alg = hashlib .sha3_384
2039-
2040- charmresource ['fingerprint' ] = hash_alg (bytes (data , 'utf-8' )).digest ()
2054+ self ._upload (data , path , application , name , resource_type , pending_id )
20412055
2042- conn , headers , path_prefix = self . connection (). https_connection ()
2056+ return resource_map
20432057
2044- query = "?pendingid={}" .format (pending_id )
2045- url = "{}/applications/{}/resources/{}{}" .format (
2046- path_prefix , application , name , query )
2047- if resource_type == "oci-image" :
2048- disp = "multipart/form-data; filename=\" {}\" " .format (path )
2049- else :
2050- disp = "form-data; filename=\" {}\" " .format (path )
2058+ def _upload (self , data , path , app_name , res_name , res_type , pending_id ):
2059+ conn , headers , path_prefix = self .connection ().https_connection ()
20512060
2052- headers ['Content-Type' ] = 'application/octet-stream'
2053- headers ['Content-Length' ] = len (data )
2054- headers ['Content-Sha384' ] = charmresource ['fingerprint' ].hex ()
2055- headers ['Content-Disposition' ] = disp
2061+ query = "?pendingid={}" .format (pending_id )
2062+ url = "{}/applications/{}/resources/{}{}" .format (path_prefix , app_name , res_name , query )
2063+ if res_type == "oci-image" :
2064+ disp = "multipart/form-data; filename=\" {}\" " .format (path )
2065+ else :
2066+ disp = "form-data; filename=\" {}\" " .format (path )
20562067
2057- conn .request ('PUT' , url , data , headers )
2068+ headers ['Content-Type' ] = 'application/octet-stream'
2069+ headers ['Content-Length' ] = len (data )
2070+ headers ['Content-Sha384' ] = hashlib .sha384 (bytes (data , 'utf-8' )).hexdigest ()
2071+ headers ['Content-Disposition' ] = disp
20582072
2059- response = conn .getresponse ()
2060- result = response .read ().decode ()
2061- if not response .status == 200 :
2062- raise JujuError (result )
2073+ conn .request ('PUT' , url , data , headers )
20632074
2064- return resource_map
2075+ response = conn .getresponse ()
2076+ result = response .read ().decode ()
2077+ if not response .status == 200 :
2078+ raise JujuError (result )
20652079
20662080 async def _deploy (self , charm_url , application , series , config ,
20672081 constraints , endpoint_bindings , resources , storage ,
20682082 channel = None , num_units = None , placement = None ,
20692083 devices = None , charm_origin = None , attach_storage = [],
2070- force = False ):
2084+ force = False , server_side_deploy = False ):
20712085 """Logic shared between `Model.deploy` and `BundleHandler.deploy`.
20722086 """
20732087 log .info ('Deploying %s' , charm_url )
@@ -2080,7 +2094,7 @@ async def _deploy(self, charm_url, application, series, config,
20802094
20812095 app_facade = client .ApplicationFacade .from_connection (self .connection ())
20822096
2083- if client . ApplicationFacade . best_facade_version ( self . connection ()) >= 19 :
2097+ if server_side_deploy :
20842098 # Call DeployFromRepository
20852099 app = client .DeployFromRepositoryArg (
20862100 applicationname = application ,
@@ -2102,10 +2116,18 @@ async def _deploy(self, charm_url, application, series, config,
21022116 revision = charm_origin .revision ,
21032117 )
21042118 result = await app_facade .DeployFromRepository ([app ])
2119+ # Collect the errors
21052120 errors = []
21062121 for r in result .results :
21072122 if r .errors :
21082123 errors .extend ([e .message for e in r .errors ])
2124+ # Upload pending local resources if any
2125+ for _result in result .results :
2126+ for pending_upload_resource in getattr (_result , 'pendingresourceuploads' , []):
2127+ _path = pending_upload_resource .filename
2128+ p = Path (_path )
2129+ data = p .read_text () if p .exists () else ''
2130+ self ._upload (data , _path , application , pending_upload_resource .name , 'file' , '' )
21092131 else :
21102132 app = client .ApplicationDeploy (
21112133 charm_url = charm_url ,
@@ -2128,6 +2150,7 @@ async def _deploy(self, charm_url, application, series, config,
21282150 errors = [r .error .message for r in result .results if r .error ]
21292151 if errors :
21302152 raise JujuError ('\n ' .join (errors ))
2153+
21312154 return await self ._wait_for_new ('application' , application )
21322155
21332156 async def destroy_unit (self , unit_id , destroy_storage = False , dry_run = False , force = False , max_wait = None ):
@@ -2732,10 +2755,10 @@ def _raise_for_status(entities, status):
27322755 # errors to raise at the end
27332756 break
27342757 for unit in app .units :
2735- if unit .machine is not None and unit .machine .status == "error" :
2758+ if raise_on_error and unit .machine is not None and unit .machine .status == "error" :
27362759 errors .setdefault ("Machine" , []).append (unit .machine .id )
27372760 continue
2738- if unit .agent_status == "error" :
2761+ if raise_on_error and unit .agent_status == "error" :
27392762 errors .setdefault ("Agent" , []).append (unit .name )
27402763 continue
27412764 if raise_on_error and unit .workload_status == "error" :
0 commit comments