Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Fix airflow connections import corrupting JSON values in .env files - #73083

Open
Eason09053360 wants to merge 1 commit into
apache:mainfrom
Eason09053360:fix-connections-import-env-json-round-trip
Open

Fix airflow connections import corrupting JSON values in .env files#73083
Eason09053360 wants to merge 1 commit into
apache:mainfrom
Eason09053360:fix-connections-import-env-json-round-trip

Conversation

@Eason09053360

Copy link
Copy Markdown
Contributor

Why

airflow connections export --file-format env --serialization-format json writes a file that
airflow connections import cannot read back. In _create_connection, a string value from a
.env file was always treated as a URI, and urlsplit() never rejects anything, so the JSON
payload was parsed as a path: conn_type, host, login and port came out empty and the rest
of the JSON blob landed in the schema column. The command printed Imported connection <id> and
exited 0, so the corruption is silent until a task fails to connect.

$ airflow connections export conns.env --file-format env --serialization-format json
$ airflow connections import conns.env
Imported connection my_conn
conn_type host login port schema
before (empty) (empty) (empty) (empty) "conn_type": "mysql", ... }
after mysql myhost mylogin 3306 mysch

That value is the same string one would put in AIRFLOW_CONN_*, and every other secrets backend
already accepts both forms there: BaseSecretsBackend._deserialize_connection_value treats a
{-prefixed value as JSON and anything else as a URI. local_filesystem was the only backend
missing that test.

What

  • airflow-core/src/airflow/secrets/local_filesystem.py: a {-prefixed string value is
    deserialized with Connection.from_json() instead of being parsed as a URI. A URI can never
    start with { (RFC 3986 requires the scheme to begin with a letter), so the two forms are
    unambiguous and existing URI files are untouched. Going through from_json() rather than this
    module's object branch also keeps .env values consistent with AIRFLOW_CONN_* for conn_type
    normalization and port coercion. Malformed JSON, which previously produced a garbage
    connection, now raises a ValueError naming the connection.
  • airflow-core/docs/security/secrets/secrets-backend/local-filesystem-secrets-backend.rst:
    document that a .env value may be a URI or a JSON object.
  • Tests: test_cli_connections_import_should_round_trip_env_file_exported_as_json covers the
    reported export/import path;
    test_env_file_json_connection_is_normalized_like_an_environment_variable pins the
    postgresql -> postgres and "5432" -> 5432 parity with the environment variable backend.

.json and .yaml files still go through this module's object branch, which does not normalize
conn_type or coerce port. That predates this change and is left alone here — happy to follow
up separately if reviewers want the two paths unified.

A connection exported with `--file-format env --serialization-format json`
could not be read back: the value was always parsed as a URI, so the whole
JSON payload silently ended up in the schema column while the command still
reported success. That value is the very string one would put in
AIRFLOW_CONN_*, where every other secrets backend already deserializes it
as JSON.
@Eason09053360
Eason09053360 force-pushed the fix-connections-import-env-json-round-trip branch from 2aefd6d to 80751d5 Compare September 13, 2026 14:47

@Vamsi-klu Vamsi-klu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The bug is real. Export writes unquoted {...}, Connection(uri=value) stuffed JSON into schema, and import printed success. The { prefix plus from_json is the right helper, and the round-trip test fails on revert.

Two leftovers: this reimplements the { test instead of BaseSecretsBackend._deserialize_connection_value (strip vs lstrip), and quoted dotenv JSON (CONN_ID='{...}') still fails the prefix check. Export does not quote, so the reported path is fixed. Hand-written quoted .env files are not.

if isinstance(value, str):
# A URI can never start with "{" (RFC 3986: the scheme begins with a letter), and a ``.env``
# value is the string one would put in ``AIRFLOW_CONN_*`` -- so JSON deserializes as it does there.
if value.lstrip().startswith("{"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This reimplements the { test instead of calling BaseSecretsBackend._deserialize_connection_value. The helper does value.strip() then value[0] == "{"; this lstrip()s only for the check, then passes the original string.

Also not dotenv: quoted CONN_ID='{"conn_type":...}' still fails the { test and still corrupts. Export does not quote, so the reported path is fine. Hand-written .env files that quote JSON still die the old way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants