markusjura: The current code would fail if the `value` would contain a `=` sign, e.g.

```
secret = ah5k=sdh
```

We should split the line by key value only by the first `=` sign.
huntc: Good catch! I shall fix tomorrow!
diff --git a/conductr_cli/resolvers/bintray_resolver.py b/conductr_cli/resolvers/bintray_resolver.py
index cfb0ce9..2186cb6 100644
--- a/conductr_cli/resolvers/bintray_resolver.py
+++ b/conductr_cli/resolvers/bintray_resolver.py
@@ -5,13 +5,14 @@ from requests.exceptions import HTTPError
 import json
 import logging
 import os
+import re
 import requests
 
 
 BINTRAY_API_BASE_URL = 'https://api.bintray.com'
 BINTRAY_DOWNLOAD_BASE_URL = 'https://dl.bintray.com'
-BINTRAY_DOWNLOAD_REALM = 'Bintray'
 BINTRAY_CREDENTIAL_FILE_PATH = '{}/.bintray/.credentials'.format(os.path.expanduser('~'))
+BINTRAY_PROPERTIES_RE = re.compile('^(\S+)\s*=\s*([\S]+)$')
 
 
 def resolve_bundle(cache_dir, uri):
@@ -23,8 +24,7 @@ def resolve_bundle(cache_dir, uri):
         bundle_download_url = bintray_download_url(bintray_username, bintray_password, org, repo, package_name,
                                                    compatibility_version, digest)
         if bundle_download_url:
-            auth = (BINTRAY_DOWNLOAD_REALM, bintray_username, bintray_password) if bintray_username else None
-            return uri_resolver.resolve_bundle(cache_dir, bundle_download_url, auth)
+            return uri_resolver.resolve_bundle(cache_dir, bundle_download_url)
         else:
             return False, None, None
     except MalformedBundleUriError:
@@ -61,8 +61,13 @@ def load_bintray_credentials():
             lines = [line.replace('\n', '') for line in cred_file.readlines()]
             data = dict()
             for line in lines:
-                key, value = line.replace(' = ', '=').split('=')
-                data[key] = value
+                match = BINTRAY_PROPERTIES_RE.match(line)
+                if match is not None:
+                    try:
+                        key, value = match.group(1, 2)
+                        data[key] = value
+                    except IndexError:
+                        pass
 
             username = None if 'user' not in data else data['user']
             password = None if 'password' not in data else data['password']
diff --git a/conductr_cli/resolvers/uri_resolver.py b/conductr_cli/resolvers/uri_resolver.py
index 8207d26..45a12df 100644
--- a/conductr_cli/resolvers/uri_resolver.py
+++ b/conductr_cli/resolvers/uri_resolver.py
@@ -6,10 +6,9 @@ from conductr_cli import screen_utils
 import os
 import logging
 import shutil
-import urllib
 
 
-def resolve_bundle(cache_dir, uri, auth=None):
+def resolve_bundle(cache_dir, uri):
     log = logging.getLogger(__name__)
 
     if not os.path.exists(cache_dir):
@@ -24,7 +23,7 @@ def resolve_bundle(cache_dir, uri, auth=None):
         if os.path.exists(tmp_download_path):
             os.remove(tmp_download_path)
 
-        download_bundle(log, bundle_url, tmp_download_path, auth)
+        download_bundle(log, bundle_url, tmp_download_path)
 
         shutil.move(tmp_download_path, cached_file)
         return True, bundle_name, cached_file
@@ -63,22 +62,12 @@ def cache_path(cache_dir, uri):
     return '{}/{}'.format(cache_dir, basename)
 
 
-def download_bundle(log, bundle_url, tmp_download_path, auth):
+def download_bundle(log, bundle_url, tmp_download_path):
     log.info('Retrieving {}'.format(bundle_url))
 
     parsed = urlparse(bundle_url, scheme='file')
     is_http_download = parsed.scheme == 'http' or parsed.scheme == 'https'
 
-    if is_http_download and auth:
-        realm, username, password = auth
-        authinfo = urllib.request.HTTPBasicAuthHandler()
-        authinfo.add_password(realm=realm,
-                              uri=bundle_url,
-                              user=username,
-                              passwd=password)
-        opener = urllib.request.build_opener(authinfo)
-        urllib.request.install_opener(opener)
-
     if log.is_progress_enabled() and is_http_download:
         urlretrieve(bundle_url, tmp_download_path, reporthook=show_progress(log))
     else:

bintray credential parsing
Apparently bintray credential file formats can screw up our parsing of them. Our parser will have to become more sophisticated. From @henrikengstrom:

> I haven’t changed the credentials file lately and it contained more than `user` and `password`, e.g. `realm` and `host`.

diff --git a/conductr_cli/resolvers/test/test_bintray_resolver.py b/conductr_cli/resolvers/test/test_bintray_resolver.py
index 2c8a77b..1d7b723 100644
--- a/conductr_cli/resolvers/test/test_bintray_resolver.py
+++ b/conductr_cli/resolvers/test/test_bintray_resolver.py
@@ -32,8 +32,7 @@ class TestResolveBundle(TestCase):
         parse_mock.assert_called_with('bundle-name:v1')
         bintray_download_url_mock.assert_called_with('username', 'password', 'typesafe', 'bundle', 'bundle-name', 'v1',
                                                      'digest')
-        resolve_bundle_mock.assert_called_with('/cache-dir', 'https://dl.bintray.com/download.zip',
-                                               ('Bintray', 'username', 'password'))
+        resolve_bundle_mock.assert_called_with('/cache-dir', 'https://dl.bintray.com/download.zip')
 
     def test_bintray_version_not_found(self):
         load_bintray_credentials_mock = MagicMock(return_value=('username', 'password'))
@@ -390,7 +389,8 @@ class TestLoadBintrayCredentials(TestCase):
     def test_success(self):
         bintray_credential_file = strip_margin(
             """|user = user1
-               |password = secret
+               |password = sec=ret
+               |# Some comment
                |""")
 
         exists_mock = MagicMock(return_value=True)
@@ -400,7 +400,7 @@ class TestLoadBintrayCredentials(TestCase):
                 patch('builtins.open', open_mock):
             username, password = bintray_resolver.load_bintray_credentials()
             self.assertEqual('user1', username)
-            self.assertEqual('secret', password)
+            self.assertEqual('sec=ret', password)
 
         exists_mock.assert_called_with('{}/.bintray/.credentials'.format(os.path.expanduser('~')))
         open_mock.assert_called_with('{}/.bintray/.credentials'.format(os.path.expanduser('~')), 'r')

pip install -e .
parse_log_pytest
pytest
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
argcomplete==3.6.1
arrow==1.3.0
certifi==2025.1.31
charset-normalizer==3.4.1
-e git+https://github.com/typesafehub/conductr-cli.git@5f065ac8f67877d0c393f26f789b22c270cb24d9#egg=conductr_cli
coverage==7.8.0
exceptiongroup @ file:///croot/exceptiongroup_1706031385326/work
execnet==2.1.1
idna==3.10
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
packaging @ file:///croot/packaging_1734472117206/work
pluggy @ file:///croot/pluggy_1733169602837/work
pyhocon==0.2.1
pyparsing==2.0.3
pytest @ file:///croot/pytest_1738938843180/work
pytest-asyncio==0.26.0
pytest-cov==6.0.0
pytest-mock==3.14.0
pytest-xdist==3.6.1
python-dateutil==2.9.0.post0
requests==2.32.3
six==1.17.0
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
types-python-dateutil==2.9.0.20241206
typing_extensions==4.13.0
urllib3==2.3.0

name: conductr-cli
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py39h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - packaging=24.2=py39h06a4308_0
  - pip=25.0=py39h06a4308_0
  - pluggy=1.5.0=py39h06a4308_0
  - pytest=8.3.4=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py39h06a4308_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - argcomplete==3.6.1
      - arrow==1.3.0
      - certifi==2025.1.31
      - charset-normalizer==3.4.1
      - coverage==7.8.0
      - execnet==2.1.1
      - idna==3.10
      - pyhocon==0.2.1
      - pyparsing==2.0.3
      - pytest-asyncio==0.26.0
      - pytest-cov==6.0.0
      - pytest-mock==3.14.0
      - pytest-xdist==3.6.1
      - python-dateutil==2.9.0.post0
      - requests==2.32.3
      - six==1.17.0
      - types-python-dateutil==2.9.0.20241206
      - typing-extensions==4.13.0
      - urllib3==2.3.0
prefix: /opt/conda/envs/conductr-cli

conductr_cli/resolvers/test/test_bintray_resolver.py::TestResolveBundle::test_bintray_version_found conductr_cli/resolvers/test/test_bintray_resolver.py::TestLoadBintrayCredentials::test_success
conductr_cli/resolvers/test/test_bintray_resolver.py::TestResolveBundle::test_bintray_version_not_found conductr_cli/resolvers/test/test_bintray_resolver.py::TestResolveBundle::test_failure_http_error conductr_cli/resolvers/test/test_bintray_resolver.py::TestResolveBundle::test_failure_malformed_bundle_uri conductr_cli/resolvers/test/test_bintray_resolver.py::TestLoadFromCache::test_bintray_version_found conductr_cli/resolvers/test/test_bintray_resolver.py::TestLoadFromCache::test_bintray_version_not_found conductr_cli/resolvers/test/test_bintray_resolver.py::TestLoadFromCache::test_failure_http_error conductr_cli/resolvers/test/test_bintray_resolver.py::TestLoadFromCache::test_failure_malformed_bundle_uri conductr_cli/resolvers/test/test_bintray_resolver.py::TestBintrayDownloadUrl::test_failure_multiple_versions_found conductr_cli/resolvers/test/test_bintray_resolver.py::TestBintrayDownloadUrl::test_failure_version_not_found conductr_cli/resolvers/test/test_bintray_resolver.py::TestBintrayDownloadUrl::test_success conductr_cli/resolvers/test/test_bintray_resolver.py::TestBintrayDownloadUrlLatest::test_failure_latest_version_malformed conductr_cli/resolvers/test/test_bintray_resolver.py::TestBintrayDownloadUrlLatest::test_latest_version_from_attribute_names conductr_cli/resolvers/test/test_bintray_resolver.py::TestBintrayDownloadUrlLatest::test_latest_version_from_attribute_names_not_found conductr_cli/resolvers/test/test_bintray_resolver.py::TestBintrayDownloadUrlLatest::test_success conductr_cli/resolvers/test/test_bintray_resolver.py::TestBintrayDownloadUrlLatestCompatibilityVersion::test_no_version conductr_cli/resolvers/test/test_bintray_resolver.py::TestBintrayDownloadUrlLatestCompatibilityVersion::test_success conductr_cli/resolvers/test/test_bintray_resolver.py::TestLoadBintrayCredentials::test_credential_file_not_having_username_password conductr_cli/resolvers/test/test_bintray_resolver.py::TestLoadBintrayCredentials::test_missing_credential_file conductr_cli/resolvers/test/test_bintray_resolver.py::TestGetJson::test_get_json conductr_cli/resolvers/test/test_bintray_resolver.py::TestGetJson::test_get_json_no_credentials
conductr_cli/resolvers/uri_resolver.py conductr_cli/resolvers/bintray_resolver.py
conductr_cli/resolvers/uri_resolver.py conductr_cli/resolvers/bintray_resolver.py