Skip to content

Commit 7c703ac

Browse files
authored
Feat: Introducing AIRBYTE_OFFLINE_MODE for air-gapped environments (#432)
1 parent 02f5ede commit 7c703ac

File tree

4 files changed

+62
-10
lines changed

4 files changed

+62
-10
lines changed

airbyte/_executors/util.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from airbyte._executors.python import VenvExecutor
1717
from airbyte._util.meta import which
1818
from airbyte._util.telemetry import EventState, log_install_state # Non-public API
19-
from airbyte.constants import TEMP_DIR_OVERRIDE
19+
from airbyte.constants import AIRBYTE_OFFLINE_MODE, TEMP_DIR_OVERRIDE
2020
from airbyte.sources.registry import ConnectorMetadata, InstallType, get_connector_metadata
2121
from airbyte.version import get_version
2222

@@ -115,7 +115,7 @@ def _get_local_executor(
115115
)
116116

117117

118-
def get_connector_executor( # noqa: PLR0912, PLR0913 # Too complex
118+
def get_connector_executor( # noqa: PLR0912, PLR0913, PLR0915 # Too many branches/arugments/statements
119119
name: str,
120120
*,
121121
version: str | None = None,
@@ -161,6 +161,21 @@ def get_connector_executor( # noqa: PLR0912, PLR0913 # Too complex
161161
# Fail the install.
162162
log_install_state(name, state=EventState.FAILED, exception=ex)
163163
raise
164+
except requests.exceptions.ConnectionError as ex:
165+
if not AIRBYTE_OFFLINE_MODE:
166+
# If the user has not enabled offline mode, raise an error.
167+
raise exc.AirbyteConnectorRegistryError(
168+
message="Failed to connect to the connector registry.",
169+
context={"connector_name": name},
170+
guidance=(
171+
"\nThere was a problem connecting to the Airbyte connector registry. "
172+
"Please check your internet connection and try again.\nTo operate "
173+
"offline, set the `AIRBYTE_OFFLINE_MODE` environment variable to `1`."
174+
"This will prevent errors related to registry connectivity and disable "
175+
"telemetry. \nIf you have a custom registry, set `_REGISTRY_ENV_VAR` "
176+
"environment variable to the URL of your custom registry."
177+
),
178+
) from ex
164179

165180
if install_method_count == 0:
166181
# User has not specified how to install the connector.

airbyte/_util/telemetry.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
WriterRuntimeInfo,
5151
)
5252
from airbyte._util.hashing import one_way_hash
53+
from airbyte.constants import AIRBYTE_OFFLINE_MODE
5354
from airbyte.version import get_version
5455

5556

@@ -89,7 +90,7 @@ def _setup_analytics() -> str | bool:
8990
anonymous_user_id: str | None = None
9091
issues: list[str] = []
9192

92-
if os.environ.get(DO_NOT_TRACK):
93+
if os.environ.get(DO_NOT_TRACK) or AIRBYTE_OFFLINE_MODE:
9394
# User has opted out of tracking.
9495
return False
9596

@@ -207,7 +208,7 @@ def send_telemetry(
207208
exception: Exception | None = None,
208209
) -> None:
209210
# If DO_NOT_TRACK is set, we don't send any telemetry
210-
if os.environ.get(DO_NOT_TRACK):
211+
if os.environ.get(DO_NOT_TRACK) or AIRBYTE_OFFLINE_MODE:
211212
return
212213

213214
payload_props: dict[str, str | int | dict] = {

airbyte/constants.py

+20
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,23 @@ def _str_to_bool(value: str) -> bool:
8989
This value is read from the `AIRBYTE_TEMP_FILE_CLEANUP` environment variable. If the variable is
9090
not set, the default value is `True`.
9191
"""
92+
93+
AIRBYTE_OFFLINE_MODE = _str_to_bool(
94+
os.getenv(
95+
key="AIRBYTE_OFFLINE_MODE",
96+
default="false",
97+
)
98+
)
99+
"""Enable or disable offline mode.
100+
101+
When offline mode is enabled, PyAirbyte will attempt to fetch metadata for connectors from the
102+
Airbyte registry but will not raise an error if the registry is unavailable. This can be useful in
103+
environments without internet access or with air-gapped networks.
104+
105+
Offline mode also disables telemetry, similar to a `DO_NOT_TRACK` setting, ensuring no usage data
106+
is sent from your environment. You may also specify a custom registry URL via the`_REGISTRY_ENV_VAR`
107+
environment variable if you prefer to use a different registry source for metadata.
108+
109+
This setting helps you make informed choices about data privacy and operation in restricted and
110+
air-gapped environments.
111+
"""

airbyte/sources/registry.py

+22-6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
from airbyte import exceptions as exc
1818
from airbyte._util.meta import is_docker_installed
19+
from airbyte.constants import AIRBYTE_OFFLINE_MODE
20+
from airbyte.logs import warn_once
1921
from airbyte.version import get_version
2022

2123

@@ -180,6 +182,10 @@ def _get_registry_url() -> str:
180182
return _REGISTRY_URL
181183

182184

185+
def _is_registry_disabled(url: str) -> bool:
186+
return url.upper() in {"0", "F", "FALSE"} or AIRBYTE_OFFLINE_MODE
187+
188+
183189
def _registry_entry_to_connector_metadata(entry: dict) -> ConnectorMetadata:
184190
name = entry["dockerRepository"].replace("airbyte/", "")
185191
latest_version: str | None = entry.get("dockerImageTag")
@@ -233,6 +239,10 @@ def _get_registry_cache(*, force_refresh: bool = False) -> dict[str, ConnectorMe
233239
return __cache
234240

235241
registry_url = _get_registry_url()
242+
243+
if _is_registry_disabled(registry_url):
244+
return {}
245+
236246
if registry_url.startswith("http"):
237247
response = requests.get(
238248
registry_url,
@@ -256,23 +266,29 @@ def _get_registry_cache(*, force_refresh: bool = False) -> dict[str, ConnectorMe
256266
new_cache[connector_metadata.name] = connector_metadata
257267

258268
if len(new_cache) == 0:
259-
raise exc.PyAirbyteInternalError(
260-
message="Connector registry is empty.",
261-
context={
262-
"registry_url": _get_registry_url(),
263-
},
269+
# This isn't necessarily fatal, since users can bring their own
270+
# connector definitions.
271+
warn_once(
272+
message=f"Connector registry is empty: {registry_url}",
273+
with_stack=False,
264274
)
265275

266276
__cache = new_cache
267277
return __cache
268278

269279

270-
def get_connector_metadata(name: str) -> ConnectorMetadata:
280+
def get_connector_metadata(name: str) -> None | ConnectorMetadata:
271281
"""Check the cache for the connector.
272282
273283
If the cache is empty, populate by calling update_cache.
274284
"""
285+
registry_url = _get_registry_url()
286+
287+
if _is_registry_disabled(registry_url):
288+
return None
289+
275290
cache = copy(_get_registry_cache())
291+
276292
if not cache:
277293
raise exc.PyAirbyteInternalError(
278294
message="Connector registry could not be loaded.",

0 commit comments

Comments
 (0)