Repository: ARMmbed/mbed-tools

Issue:
Obtuse error message when board is not found locally or remotely
### Description

When a board is being looked for, and it isn't found in the offline database, and no network connectivity is available (say, perhaps [when a proxy is required to access the net](https://github.com/ARMmbed/mbed-tools/issues/200), the error message we receive is as follows:

```
$ mbedtools detect -a
ERROR: A problem occurred when looking up board data for connected devices.

More information may be available by using the command line option '-v'.
```

Using verbose output isn't too much better.

```
$ mbedtools -vvv detect -a
DEBUG: Failed getting an attribute value: <unknown>.AdditionalAvailability
DEBUG: Failed getting an attribute value: <unknown>.IdentifyingDescriptions
DEBUG: Failed getting an attribute value: <unknown>.MaxQuiesceTime
DEBUG: Failed getting an attribute value: <unknown>.OtherIdentifyingInfo
DEBUG: Failed getting an attribute value: <unknown>.PowerOnHours
DEBUG: Failed getting an attribute value: <unknown>.TotalPowerOnHours
DEBUG: Failed getting an attribute value: <unknown>.AdditionalAvailability
DEBUG: Failed getting an attribute value: <unknown>.IdentifyingDescriptions
DEBUG: Failed getting an attribute value: <unknown>.MaxQuiesceTime
DEBUG: Failed getting an attribute value: <unknown>.OtherIdentifyingInfo
DEBUG: Failed getting an attribute value: <unknown>.PowerOnHours
DEBUG: Failed getting an attribute value: <unknown>.TotalPowerOnHours
DEBUG: Failed getting an attribute value: <unknown>.AdditionalAvailability
DEBUG: Failed getting an attribute value: <unknown>.IdentifyingDescriptions
DEBUG: Failed getting an attribute value: <unknown>.MaxQuiesceTime
DEBUG: Failed getting an attribute value: <unknown>.OtherIdentifyingInfo
DEBUG: Failed getting an attribute value: <unknown>.PowerOnHours
DEBUG: Failed getting an attribute value: <unknown>.TotalPowerOnHours
DEBUG: Failed getting an attribute value: <unknown>.AdditionalAvailability
DEBUG: Failed getting an attribute value: <unknown>.IdentifyingDescriptions
DEBUG: Failed getting an attribute value: <unknown>.MaxQuiesceTime
DEBUG: Failed getting an attribute value: <unknown>.OtherIdentifyingInfo
DEBUG: Failed getting an attribute value: <unknown>.PowerOnHours
DEBUG: Failed getting an attribute value: <unknown>.TotalPowerOnHours
DEBUG: Failed getting an attribute value: <unknown>.AdditionalAvailability
DEBUG: Failed getting an attribute value: <unknown>.IdentifyingDescriptions
DEBUG: Failed getting an attribute value: <unknown>.MaxQuiesceTime
DEBUG: Failed getting an attribute value: <unknown>.OtherIdentifyingInfo
DEBUG: Failed getting an attribute value: <unknown>.PowerOnHours
DEBUG: Failed getting an attribute value: <unknown>.TotalPowerOnHours
DEBUG: Cannot retrieve the real name of volume Y:\. Reason: (87, 'GetVolumeNameForVolumeMountPoint', 'The parameter is incorrect.')
DEBUG: Attribute [DiskIndex] is undefined on this instance DiskPartition({}): 'DiskPartition' object has no attribute 'DiskIndex'
DEBUG: Attribute [PNPDeviceID] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'PNPDeviceID'
DEBUG: Attribute [SerialNumber] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'SerialNumber'
DEBUG: Attribute [DeviceID] is undefined on this instance DiskPartition({}): 'DiskPartition' object has no attribute 'DeviceID'
DEBUG: Attribute [Type] is undefined on this instance DiskPartition({}): 'DiskPartition' object has no attribute 'Type'
DEBUG: Attribute [Caption] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'Caption'
DEBUG: Attribute [DeviceID] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'DeviceID'
DEBUG: Attribute [Model] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'Model'
DEBUG: Attribute [InterfaceType] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'InterfaceType'
DEBUG: Attribute [MediaType] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'MediaType'
DEBUG: Attribute [Manufacturer] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'Manufacturer'
DEBUG: Attribute [SerialNumber] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'SerialNumber'
DEBUG: Attribute [Status] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'Status'
DEBUG: Attribute [PNPDeviceID] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'PNPDeviceID'
DEBUG: Cannot retrieve the real name of volume Y:\. Reason: (87, 'GetVolumeNameForVolumeMountPoint', 'The parameter is incorrect.')
DEBUG: Attribute [DiskIndex] is undefined on this instance DiskPartition({}): 'DiskPartition' object has no attribute 'DiskIndex'
DEBUG: Attribute [PNPDeviceID] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'PNPDeviceID'
DEBUG: Attribute [SerialNumber] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'SerialNumber'
DEBUG: Attribute [DeviceID] is undefined on this instance DiskPartition({}): 'DiskPartition' object has no attribute 'DeviceID'
DEBUG: Attribute [Type] is undefined on this instance DiskPartition({}): 'DiskPartition' object has no attribute 'Type'
DEBUG: Attribute [Caption] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'Caption'
DEBUG: Attribute [DeviceID] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'DeviceID'
DEBUG: Attribute [Model] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'Model'
DEBUG: Attribute [InterfaceType] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'InterfaceType'
DEBUG: Attribute [MediaType] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'MediaType'
DEBUG: Attribute [Manufacturer] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'Manufacturer'
DEBUG: Attribute [SerialNumber] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'SerialNumber'
DEBUG: Attribute [Status] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'Status'
DEBUG: Attribute [PNPDeviceID] is undefined on this instance DiskDrive({}): 'DiskDrive' object has no attribute 'PNPDeviceID'
INFO: Using the offline database to identify boards.
INFO: Unable to identify a board using the offline database, trying the online database.
DEBUG: Starting new HTTPS connection (1): os.mbed.com:443
WARNING: There was an error connecting to the online database. Please check your internet connection.
ERROR: A problem occurred when looking up board data for connected devices.

More information may be available by using the command line option '--traceback'.
```

Instead of failing with an error when we can't access the online board database, we should accept the board is unknown and display a user-friendly message, explaining that the board is unknown in the local database and that we couldn't access the online database, so we have to consider the board as unknown.

This issue is a subset of the issues reported in https://github.com/ARMmbed/mbed-tools/issues/186


### Issue request type

<!--
Please add only one `x` to one of the following types. Do not fill multiple types (split the issue otherwise).


For questions please use https://forums.mbed.com/
-->

- [ ] Enhancement
- [X] Bug


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/ARMmbed/mbed-tools/pull/208?src=pr&el=h1) Report
> Merging [#208](https://codecov.io/gh/ARMmbed/mbed-tools/pull/208?src=pr&el=desc) (2dec7c0) into [master](https://codecov.io/gh/ARMmbed/mbed-tools/commit/e9710eabb27a4f65e7c0e799224c3f8bcd2b4881?el=desc) (e9710ea) will **increase** coverage by `0.11%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/ARMmbed/mbed-tools/pull/208/graphs/tree.svg?width=650&height=150&src=pr&token=QFRIdRv91G)](https://codecov.io/gh/ARMmbed/mbed-tools/pull/208?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #208      +/-   ##
==========================================
+ Coverage   96.63%   96.75%   +0.11%     
==========================================
  Files          94       94              
  Lines        2674     2711      +37     
==========================================
+ Hits         2584     2623      +39     
+ Misses         90       88       -2     
```


| [Impacted Files](https://codecov.io/gh/ARMmbed/mbed-tools/pull/208?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/mbed\_tools/targets/\_internal/board\_database.py](https://codecov.io/gh/ARMmbed/mbed-tools/pull/208/diff?src=pr&el=tree#diff-c3JjL21iZWRfdG9vbHMvdGFyZ2V0cy9faW50ZXJuYWwvYm9hcmRfZGF0YWJhc2UucHk=) | `100.00% <100.00%> (ø)` | |
| [src/mbed\_tools/targets/get\_board.py](https://codecov.io/gh/ARMmbed/mbed-tools/pull/208/diff?src=pr&el=tree#diff-c3JjL21iZWRfdG9vbHMvdGFyZ2V0cy9nZXRfYm9hcmQucHk=) | `100.00% <100.00%> (ø)` | |
| [src/mbed\_tools/build/config.py](https://codecov.io/gh/ARMmbed/mbed-tools/pull/208/diff?src=pr&el=tree#diff-c3JjL21iZWRfdG9vbHMvYnVpbGQvY29uZmlnLnB5) | `100.00% <0.00%> (ø)` | |
| [...ls/build/\_internal/config/assemble\_build\_config.py](https://codecov.io/gh/ARMmbed/mbed-tools/pull/208/diff?src=pr&el=tree#diff-c3JjL21iZWRfdG9vbHMvYnVpbGQvX2ludGVybmFsL2NvbmZpZy9hc3NlbWJsZV9idWlsZF9jb25maWcucHk=) | `100.00% <0.00%> (ø)` | |
| [src/mbed\_tools/project/\_internal/project\_data.py](https://codecov.io/gh/ARMmbed/mbed-tools/pull/208/diff?src=pr&el=tree#diff-c3JjL21iZWRfdG9vbHMvcHJvamVjdC9faW50ZXJuYWwvcHJvamVjdF9kYXRhLnB5) | `100.00% <0.00%> (+2.81%)` | :arrow_up: |


Patch:
diff --git a/news/201.bugfix b/news/201.bugfix
new file mode 100644
index 0000000..0ef9e8e
--- /dev/null
+++ b/news/201.bugfix
@@ -0,0 +1,1 @@
+Define a board as unknown if it cannot be identified locally when unable to access the online database.
diff --git a/src/mbed_tools/targets/_internal/board_database.py b/src/mbed_tools/targets/_internal/board_database.py
index 6745f65..3619cba 100644
--- a/src/mbed_tools/targets/_internal/board_database.py
+++ b/src/mbed_tools/targets/_internal/board_database.py
@@ -106,5 +106,5 @@ def _get_request() -> requests.Response:
     try:
         return requests.get(_BOARD_API, headers=header)
     except requests.exceptions.ConnectionError as connection_error:
-        logger.warning("There was an error connecting to the online database. Please check your internet connection.")
+        logger.warning("Unable to connect to the online database. Please check your internet connection.")
         raise BoardAPIError("Failed to connect to the online database.") from connection_error
diff --git a/src/mbed_tools/targets/get_board.py b/src/mbed_tools/targets/get_board.py
index 5bd99b5..1760fe9 100644
--- a/src/mbed_tools/targets/get_board.py
+++ b/src/mbed_tools/targets/get_board.py
@@ -11,7 +11,7 @@ from enum import Enum
 from typing import Callable
 
 from mbed_tools.targets.env import env
-from mbed_tools.targets.exceptions import UnknownBoard, UnsupportedMode
+from mbed_tools.targets.exceptions import UnknownBoard, UnsupportedMode, BoardDatabaseError
 from mbed_tools.targets.board import Board
 from mbed_tools.targets.boards import Boards
 
@@ -70,7 +70,11 @@ def get_board(matching: Callable) -> Board:
         return Boards.from_offline_database().get_board(matching)
     except UnknownBoard:
         logger.info("Unable to identify a board using the offline database, trying the online database.")
-        return Boards.from_online_database().get_board(matching)
+        try:
+            return Boards.from_online_database().get_board(matching)
+        except BoardDatabaseError:
+            logger.error("Unable to access the online database to identify a board.")
+            raise UnknownBoard()
 
 
 class _DatabaseMode(Enum):


Test patch:
diff --git a/tests/targets/test_get_board.py b/tests/targets/test_get_board.py
index d2e5b69..0398f4a 100644
--- a/tests/targets/test_get_board.py
+++ b/tests/targets/test_get_board.py
@@ -5,6 +5,8 @@
 """Tests for `mbed_tools.targets.get_board`."""
 from unittest import mock, TestCase
 
+from mbed_tools.targets._internal.exceptions import BoardAPIError
+
 # Import from top level as this is the expected interface for users
 from mbed_tools.targets import get_board_by_online_id, get_board_by_product_code
 from mbed_tools.targets.get_board import (
@@ -59,6 +61,17 @@ class TestGetBoard(TestCase):
         mocked_boards.from_offline_database().get_board.assert_called_once_with(fn)
         mocked_boards.from_online_database().get_board.assert_called_once_with(fn)
 
+    def test_auto_mode_raises_when_board_not_found_offline_with_no_network(self, env, mocked_boards):
+        env.MBED_DATABASE_MODE = "AUTO"
+        mocked_boards.from_offline_database().get_board.side_effect = UnknownBoard
+        mocked_boards.from_online_database().get_board.side_effect = BoardAPIError
+        fn = mock.Mock()
+
+        with self.assertRaises(UnknownBoard):
+            get_board(fn)
+        mocked_boards.from_offline_database().get_board.assert_called_once_with(fn)
+        mocked_boards.from_online_database().get_board.assert_called_once_with(fn)
+
 
 class TestGetBoardByProductCode(TestCase):
     @mock.patch("mbed_tools.targets.get_board.get_board")