@@ -1743,6 +1743,8 @@ async def deploy(
17431743 if base :
17441744 charm_origin .base = utils .parse_base_arg (base )
17451745
1746+ server_side_deploy = False
1747+
17461748 if res .is_bundle :
17471749 handler = BundleHandler (self , trusted = trust , forced = force )
17481750 await handler .fetch_plan (url , charm_origin , overlays = overlays )
@@ -1774,9 +1776,20 @@ async def deploy(
17741776 else :
17751777 charm_origin = add_charm_res .charm_origin
17761778 if Schema .CHARM_HUB .matches (url .schema ):
1777- resources = await self ._add_charmhub_resources (res .app_name ,
1778- identifier ,
1779- add_charm_res .charm_origin )
1779+
1780+ if client .ApplicationFacade .best_facade_version (self .connection ()) >= 19 :
1781+ server_side_deploy = True
1782+ else :
1783+ # TODO (cderici): this is an awkward workaround for basically not calling
1784+ # the AddPendingResources in case this is a server side deploy.
1785+ # If that's the case, then the store resources (and revisioned local
1786+ # resources) are handled at the server side if this is a server side deploy
1787+ # (local uploads are handled right after we get the pendingIDs returned
1788+ # from the facade call).
1789+ resources = await self ._add_charmhub_resources (res .app_name ,
1790+ identifier ,
1791+ add_charm_res .charm_origin )
1792+
17801793 is_sub = await self .charmhub .is_subordinate (url .name )
17811794 if is_sub :
17821795 if num_units > 1 :
@@ -1829,6 +1842,7 @@ async def deploy(
18291842 charm_origin = charm_origin ,
18301843 attach_storage = attach_storage ,
18311844 force = force ,
1845+ server_side_deploy = server_side_deploy ,
18321846 )
18331847
18341848 async def _add_charm (self , charm_url , origin ):
@@ -2029,42 +2043,42 @@ async def add_local_resources(self, application, entity_url, metadata, resources
20292043 'username' : '' ,
20302044 'password' : '' ,
20312045 }
2032-
20332046 data = yaml .dump (docker_image_details )
2047+ else :
2048+ p = Path (path )
2049+ data = p .read_text () if p .exists () else ''
20342050
2035- hash_alg = hashlib .sha3_384
2036-
2037- charmresource ['fingerprint' ] = hash_alg (bytes (data , 'utf-8' )).digest ()
2051+ self ._upload (data , path , application , name , resource_type , pending_id )
20382052
2039- conn , headers , path_prefix = self . connection (). https_connection ()
2053+ return resource_map
20402054
2041- query = "?pendingid={}" .format (pending_id )
2042- url = "{}/applications/{}/resources/{}{}" .format (
2043- path_prefix , application , name , query )
2044- if resource_type == "oci-image" :
2045- disp = "multipart/form-data; filename=\" {}\" " .format (path )
2046- else :
2047- disp = "form-data; filename=\" {}\" " .format (path )
2055+ def _upload (self , data , path , app_name , res_name , res_type , pending_id ):
2056+ conn , headers , path_prefix = self .connection ().https_connection ()
20482057
2049- headers ['Content-Type' ] = 'application/octet-stream'
2050- headers ['Content-Length' ] = len (data )
2051- headers ['Content-Sha384' ] = charmresource ['fingerprint' ].hex ()
2052- headers ['Content-Disposition' ] = disp
2058+ query = "?pendingid={}" .format (pending_id )
2059+ url = "{}/applications/{}/resources/{}{}" .format (path_prefix , app_name , res_name , query )
2060+ if res_type == "oci-image" :
2061+ disp = "multipart/form-data; filename=\" {}\" " .format (path )
2062+ else :
2063+ disp = "form-data; filename=\" {}\" " .format (path )
20532064
2054- conn .request ('PUT' , url , data , headers )
2065+ headers ['Content-Type' ] = 'application/octet-stream'
2066+ headers ['Content-Length' ] = len (data )
2067+ headers ['Content-Sha384' ] = hashlib .sha384 (bytes (data , 'utf-8' )).hexdigest ()
2068+ headers ['Content-Disposition' ] = disp
20552069
2056- response = conn .getresponse ()
2057- result = response .read ().decode ()
2058- if not response .status == 200 :
2059- raise JujuError (result )
2070+ conn .request ('PUT' , url , data , headers )
20602071
2061- return resource_map
2072+ response = conn .getresponse ()
2073+ result = response .read ().decode ()
2074+ if not response .status == 200 :
2075+ raise JujuError (result )
20622076
20632077 async def _deploy (self , charm_url , application , series , config ,
20642078 constraints , endpoint_bindings , resources , storage ,
20652079 channel = None , num_units = None , placement = None ,
20662080 devices = None , charm_origin = None , attach_storage = [],
2067- force = False ):
2081+ force = False , server_side_deploy = False ):
20682082 """Logic shared between `Model.deploy` and `BundleHandler.deploy`.
20692083 """
20702084 log .info ('Deploying %s' , charm_url )
@@ -2077,7 +2091,7 @@ async def _deploy(self, charm_url, application, series, config,
20772091
20782092 app_facade = client .ApplicationFacade .from_connection (self .connection ())
20792093
2080- if client . ApplicationFacade . best_facade_version ( self . connection ()) >= 19 :
2094+ if server_side_deploy :
20812095 # Call DeployFromRepository
20822096 app = client .DeployFromRepositoryArg (
20832097 applicationname = application ,
@@ -2125,6 +2139,14 @@ async def _deploy(self, charm_url, application, series, config,
21252139 errors = [r .error .message for r in result .results if r .error ]
21262140 if errors :
21272141 raise JujuError ('\n ' .join (errors ))
2142+
2143+ for _result in result .results :
2144+ for pending_upload_resource in _result .pendingresourceuploads :
2145+ _path = pending_upload_resource .filename
2146+ p = Path (_path )
2147+ data = p .read_text () if p .exists () else ''
2148+ self ._upload (data , _path , application , pending_upload_resource .name , 'file' , '' )
2149+
21282150 return await self ._wait_for_new ('application' , application )
21292151
21302152 async def destroy_unit (self , unit_id , destroy_storage = False , dry_run = False , force = False , max_wait = None ):
0 commit comments