Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Send HTTP GET header with settings when looking for a 'package_reference' #8046

Merged
merged 10 commits into from
Nov 13, 2020
11 changes: 7 additions & 4 deletions conans/client/graph/graph_binaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,14 @@ def _evaluate_cache_pkg(self, node, package_layout, pref, metadata, remote, remo
node.prev = metadata.packages[pref.id].revision
assert node.prev, "PREV for %s is None: %s" % (str(pref), metadata.dumps())

def _get_package_info(self, node, pref, remote):
return self._remote_manager.get_package_info(pref, remote, info=node.conanfile.info)

def _evaluate_remote_pkg(self, node, pref, remote, remotes):
remote_info = None
if remote:
try:
remote_info, pref = self._remote_manager.get_package_info(pref, remote)
remote_info, pref = self._get_package_info(node, pref, remote)
except NotFoundException:
pass
except Exception:
Expand All @@ -107,9 +110,9 @@ def _evaluate_remote_pkg(self, node, pref, remote, remotes):
# If the "remote" came from the registry but the user didn't specified the -r, with
# revisions iterate all remotes
if not remote or (not remote_info and self._cache.config.revisions_enabled):
for r in remotes.values():
for r in remotes.values(): # FIXME: Here we hit the same remote we did before
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch

try:
remote_info, pref = self._remote_manager.get_package_info(pref, r)
remote_info, pref = self._get_package_info(node, pref, r)
except NotFoundException:
pass
else:
Expand Down Expand Up @@ -236,7 +239,7 @@ def _process_node(self, node, pref, build_mode, update, remotes):
if build_mode.outdated:
if node.binary in (BINARY_CACHE, BINARY_DOWNLOAD, BINARY_UPDATE):
if node.binary == BINARY_UPDATE:
info, pref = self._remote_manager.get_package_info(pref, remote)
info, pref = self._get_package_info(node, pref, remote)
recipe_hash = info.recipe_hash
elif node.binary == BINARY_CACHE:
package_folder = package_layout.package(pref)
Expand Down
53 changes: 39 additions & 14 deletions conans/client/remote_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,27 @@
log_recipe_download, log_recipe_sources_download,
log_uncompressed_file)

CONAN_REQUEST_HEADER_SETTINGS = 'Conan-PkgID-Settings'
CONAN_REQUEST_HEADER_OPTIONS = 'Conan-PkgID-Options'


def _headers_for_info(info):
if not info:
return None

r = {}
settings = info.full_settings.as_list()
if settings:
settings = ['{}={}'.format(*it) for it in settings]
r.update({CONAN_REQUEST_HEADER_SETTINGS: ';'.join(settings)})

options = info.options.as_list()
if options:
options = filter(lambda u: u[0] in ['shared', 'fPIC', 'header_only'], options)
options = ['{}={}'.format(*it) for it in options]
r.update({CONAN_REQUEST_HEADER_OPTIONS: ';'.join(options)})
return r


class RemoteManager(object):
""" Will handle the remotes to get recipes, packages etc """
Expand All @@ -38,7 +59,7 @@ def get_recipe_snapshot(self, ref, remote):
return self._call_remote(remote, "get_recipe_snapshot", ref)

def get_package_snapshot(self, pref, remote):
assert pref.ref.revision, "upload_package requires RREV"
assert pref.ref.revision, "get_package_snapshot requires RREV"
assert pref.revision, "get_package_snapshot requires PREV"
return self._call_remote(remote, "get_package_snapshot", pref)

Expand All @@ -58,14 +79,16 @@ def get_recipe_manifest(self, ref, remote):
return self._call_remote(remote, "get_recipe_manifest", ref), ref

def get_package_manifest(self, pref, remote):
pref = self._resolve_latest_pref(pref, remote)
pref = self._resolve_latest_pref(pref, remote, headers=None)
return self._call_remote(remote, "get_package_manifest", pref), pref

def get_package_info(self, pref, remote):
def get_package_info(self, pref, remote, info=None):
""" Read a package ConanInfo from remote
"""
pref = self._resolve_latest_pref(pref, remote)
return self._call_remote(remote, "get_package_info", pref), pref
headers = _headers_for_info(info)
pref = self._resolve_latest_pref(pref, remote, headers=headers)
# FIXME Conan 2.0: With revisions, it is not needed to pass headers to this second function
return self._call_remote(remote, "get_package_info", pref, headers=headers), pref

def get_recipe(self, ref, remote):
"""
Expand Down Expand Up @@ -140,16 +163,18 @@ def get_package(self, conanfile, pref, layout, remote, output, recorder):
output.info("Retrieving package %s from remote '%s' " % (pref.id, remote.name))
layout.package_remove(pref) # Remove first the destination folder
with layout.set_dirty_context_manager(pref):
self._get_package(layout, pref, remote, output, recorder)
info = getattr(conanfile, 'info', None)
self._get_package(layout, pref, remote, output, recorder, info=info)

self._hook_manager.execute("post_download_package", conanfile_path=conanfile_path,
reference=pref.ref, package_id=pref.id, remote=remote,
conanfile=conanfile)

def _get_package(self, layout, pref, remote, output, recorder):
def _get_package(self, layout, pref, remote, output, recorder, info):
t1 = time.time()
try:
pref = self._resolve_latest_pref(pref, remote)
headers = _headers_for_info(info)
pref = self._resolve_latest_pref(pref, remote, headers=headers)
snapshot = self._call_remote(remote, "get_package_snapshot", pref)
if not is_package_snapshot_complete(snapshot):
raise PackageNotFoundException(pref)
Expand Down Expand Up @@ -230,8 +255,8 @@ def get_latest_recipe_revision(self, ref, remote):
revision = self._call_remote(remote, "get_latest_recipe_revision", ref)
return revision

def get_latest_package_revision(self, pref, remote):
revision = self._call_remote(remote, "get_latest_package_revision", pref)
def get_latest_package_revision(self, pref, remote, headers=None):
revision = self._call_remote(remote, "get_latest_package_revision", pref, headers=headers)
return revision

def _resolve_latest_ref(self, ref, remote):
Expand All @@ -242,16 +267,16 @@ def _resolve_latest_ref(self, ref, remote):
ref = ref.copy_with_rev(DEFAULT_REVISION_V1)
return ref

def _resolve_latest_pref(self, pref, remote):
def _resolve_latest_pref(self, pref, remote, headers):
if pref.revision is None:
try:
pref = self.get_latest_package_revision(pref, remote)
pref = self.get_latest_package_revision(pref, remote, headers=headers)
except NoRestV2Available:
pref = pref.copy_with_revs(pref.ref.revision, DEFAULT_REVISION_V1)
return pref

def _call_remote(self, remote, method, *args, **kwargs):
assert(isinstance(remote, Remote))
assert (isinstance(remote, Remote))
try:
return self._auth_manager.call_rest_api_method(remote, method, *args, **kwargs)
except ConnectionError as exc:
Expand Down Expand Up @@ -294,7 +319,7 @@ def uncompress_file(src_path, dest_folder, output):
t1 = time.time()
try:
with progress_bar.open_binary(src_path, output, "Decompressing %s" % os.path.basename(
src_path)) as file_handler:
src_path)) as file_handler:
tar_extract(file_handler, dest_folder)
except Exception as e:
error_msg = "Error while downloading/extracting files to %s\n%s\n" % (dest_folder, str(e))
Expand Down
8 changes: 4 additions & 4 deletions conans/client/rest/rest_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ def get_recipe_manifest(self, ref):
def get_package_manifest(self, pref):
return self._get_api().get_package_manifest(pref)

def get_package_info(self, pref):
return self._get_api().get_package_info(pref)
def get_package_info(self, pref, headers):
return self._get_api().get_package_info(pref, headers=headers)

def get_recipe(self, ref, dest_folder):
return self._get_api().get_recipe(ref, dest_folder)
Expand Down Expand Up @@ -161,5 +161,5 @@ def get_package_revisions(self, pref):
def get_latest_recipe_revision(self, ref):
return self._get_api().get_latest_recipe_revision(ref)

def get_latest_package_revision(self, pref):
return self._get_api().get_latest_package_revision(pref)
def get_latest_package_revision(self, pref, headers):
return self._get_api().get_latest_package_revision(pref, headers=headers)
18 changes: 11 additions & 7 deletions conans/client/rest/rest_client_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

class JWTAuth(AuthBase):
"""Attaches JWT Authentication to the given Request object."""

def __init__(self, token):
self.token = token

Expand Down Expand Up @@ -42,6 +43,7 @@ def handle_return_deserializer(deserializer=None):
Map exceptions and http return codes and deserialize if needed.

deserializer: Function for deserialize values"""

def handle_return(method):
def inner(*argc, **argv):
ret = method(*argc, **argv)
Expand All @@ -50,7 +52,9 @@ def inner(*argc, **argv):
text = ret.text if ret.status_code != 404 else "404 Not found"
raise get_exception_from_error(ret.status_code)(text)
return deserializer(ret.content) if deserializer else decode_text(ret.content)

return inner

return handle_return


Expand Down Expand Up @@ -169,19 +173,20 @@ def server_capabilities(self, user=None, password=None):

return [cap.strip() for cap in server_capabilities.split(",") if cap]

def get_json(self, url, data=None):
headers = self.custom_headers
def get_json(self, url, data=None, headers=None):
req_headers = self.custom_headers.copy()
req_headers.update(headers or {})
if data: # POST request
headers.update({'Content-type': 'application/json',
'Accept': 'application/json'})
req_headers.update({'Content-type': 'application/json',
'Accept': 'application/json'})
logger.debug("REST: post: %s" % url)
response = self.requester.post(url, auth=self.auth, headers=headers,
response = self.requester.post(url, auth=self.auth, headers=req_headers,
verify=self.verify_ssl,
stream=True,
data=json.dumps(data))
else:
logger.debug("REST: get: %s" % url)
response = self.requester.get(url, auth=self.auth, headers=headers,
response = self.requester.get(url, auth=self.auth, headers=req_headers,
verify=self.verify_ssl,
stream=True)

Expand Down Expand Up @@ -244,4 +249,3 @@ def search_packages(self, ref, query):
url = self.router.search_packages(ref, query)
package_infos = self.get_json(url)
return package_infos

10 changes: 5 additions & 5 deletions conans/client/rest/rest_client_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,11 @@ def get_package_manifest(self, pref):
logger.error(traceback.format_exc())
raise ConanException(msg)

def get_package_info(self, pref):
def get_package_info(self, pref, headers):
"""Gets a ConanInfo file from a package"""
pref = pref.copy_with_revs(None, None)
url = self.router.package_download_urls(pref)
urls = self._get_file_to_url_dict(url)
urls = self._get_file_to_url_dict(url, headers=headers)
if not urls:
raise PackageNotFoundException(pref)

Expand All @@ -124,10 +124,10 @@ def get_package_info(self, pref):
contents = {key: decode_text(value) for key, value in dict(contents).items()}
return ConanInfo.loads(contents[CONANINFO])

def _get_file_to_url_dict(self, url, data=None):
def _get_file_to_url_dict(self, url, data=None, headers=None):
"""Call to url and decode the json returning a dict of {filepath: url} dict
converting the url to a complete url when needed"""
urls = self.get_json(url, data=data)
urls = self.get_json(url, data=data, headers=headers)
return {filepath: complete_url(self.remote_url, url) for filepath, url in urls.items()}

def _upload_recipe(self, ref, files_to_upload, retry, retry_wait):
Expand Down Expand Up @@ -340,7 +340,7 @@ def get_package_revisions(self, pref):
def get_latest_recipe_revision(self, ref):
raise NoRestV2Available("The remote doesn't support revisions")

def get_latest_package_revision(self, pref):
def get_latest_package_revision(self, pref, headers):
raise NoRestV2Available("The remote doesn't support revisions")

def _post_json(self, url, payload):
Expand Down
12 changes: 6 additions & 6 deletions conans/client/rest/rest_client_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ def _get_file_list_json(self, url):
data["files"] = list(data["files"].keys())
return data

def _get_remote_file_contents(self, url, use_cache):
def _get_remote_file_contents(self, url, use_cache, headers=None):
# We don't want traces in output of these downloads, they are ugly in output
downloader = FileDownloader(self.requester, None, self.verify_ssl, self._config)
if use_cache and self._config.download_cache:
downloader = CachedFileDownloader(self._config.download_cache, downloader)
contents = downloader.download(url, auth=self.auth)
contents = downloader.download(url, auth=self.auth, headers=headers)
return contents

def _get_snapshot(self, url):
Expand Down Expand Up @@ -77,10 +77,10 @@ def get_package_manifest(self, pref):
logger.error(traceback.format_exc())
raise ConanException(msg)

def get_package_info(self, pref):
def get_package_info(self, pref, headers):
url = self.router.package_info(pref)
cache = (pref.revision != DEFAULT_REVISION_V1)
content = self._get_remote_file_contents(url, use_cache=cache)
content = self._get_remote_file_contents(url, use_cache=cache, headers=headers)
return ConanInfo.loads(decode_text(content))

def get_recipe(self, ref, dest_folder):
Expand Down Expand Up @@ -329,9 +329,9 @@ def get_latest_recipe_revision(self, ref):
# Ignored data["time"]
return ref.copy_with_rev(rev)

def get_latest_package_revision(self, pref):
def get_latest_package_revision(self, pref, headers):
url = self.router.package_latest(pref)
data = self.get_json(url)
data = self.get_json(url, headers=headers)
prev = data["revision"]
# Ignored data["time"]
return pref.copy_with_revs(pref.ref.revision, prev)
2 changes: 1 addition & 1 deletion conans/test/functional/remote/rest_api_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def test_get_package_info(self):
self._upload_package(pref, {CONANINFO: conan_info})

# Get the package info
info = self.api.get_package_info(pref)
info = self.api.get_package_info(pref, headers=None)
self.assertIsInstance(info, ConanInfo)
self.assertEqual(info, ConanInfo.loads(conan_info))

Expand Down
Loading