From 5f489224669e8eaf78a2c8e7348fe9cc8b3150ad Mon Sep 17 00:00:00 2001 From: hussein-awala Date: Thu, 21 Mar 2024 01:50:55 +0100 Subject: [PATCH 1/8] Add a migration script for encrypted trigger kwargs --- .../0138_2_9_0_encrypt_trigger_kwargs.py | 69 +++++++++++++++++++ airflow/utils/db.py | 2 +- docs/apache-airflow/img/airflow_erd.sha256 | 2 +- docs/apache-airflow/img/airflow_erd.svg | 64 ++++++++--------- docs/apache-airflow/migrations-ref.rst | 4 +- 5 files changed, 106 insertions(+), 35 deletions(-) create mode 100644 airflow/migrations/versions/0138_2_9_0_encrypt_trigger_kwargs.py diff --git a/airflow/migrations/versions/0138_2_9_0_encrypt_trigger_kwargs.py b/airflow/migrations/versions/0138_2_9_0_encrypt_trigger_kwargs.py new file mode 100644 index 0000000000000..38871268f9198 --- /dev/null +++ b/airflow/migrations/versions/0138_2_9_0_encrypt_trigger_kwargs.py @@ -0,0 +1,69 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""encrypt trigger kwargs + +Revision ID: 1949afb29106 +Revises: 8e1c784a4fc7 +Create Date: 2024-03-17 22:09:09.406395 + +""" +import sqlalchemy as sa +from airflow.models.crypto import get_fernet + +from airflow.models.trigger import Trigger +from alembic import op + + +# revision identifiers, used by Alembic. +revision = "1949afb29106" +down_revision = "8e1c784a4fc7" +branch_labels = None +depends_on = None +airflow_version = "2.9.0" + + +def get_session() -> sa.orm.Session: + conn = op.get_bind() + sessionmaker = sa.orm.sessionmaker() + return sessionmaker(bind=conn) + + +def upgrade(): + """Apply encrypt trigger kwargs""" + session = get_session() + try: + for trigger in session.query(Trigger).all(): + # convert dict to string and encrypt it + trigger.encrypted_kwargs = trigger._encrypt_kwargs(trigger.encrypted_kwargs) + session.commit() + finally: + session.close() + + +def downgrade(): + """Unapply encrypt trigger kwargs""" + session = get_session() + try: + fernet = get_fernet() + for trigger in session.query(Trigger).all(): + # decrypt string and convert it to dict + trigger.encrypted_kwargs = trigger._decrypt_kwargs(trigger.encrypted_kwargs, fernet) + session.commit() + finally: + session.close() diff --git a/airflow/utils/db.py b/airflow/utils/db.py index 6cc0d7159b2f2..b7997498bb47d 100644 --- a/airflow/utils/db.py +++ b/airflow/utils/db.py @@ -90,7 +90,7 @@ "2.7.0": "405de8318b3a", "2.8.0": "10b52ebd31f7", "2.8.1": "88344c1d9134", - "2.9.0": "1fd565369930", + "2.9.0": "1949afb29106", } diff --git a/docs/apache-airflow/img/airflow_erd.sha256 b/docs/apache-airflow/img/airflow_erd.sha256 index e36d2162c6320..b61226732d910 100644 --- a/docs/apache-airflow/img/airflow_erd.sha256 +++ b/docs/apache-airflow/img/airflow_erd.sha256 @@ -1 +1 @@ -ce71fa2d8f6daea541c46d3528961bdee87421b3612ed2f2451e88630a52fd70 \ No newline at end of file +c6a12adc3097ace8272f6a454b952092b65de1cf5a65fc6760f7fd618098281a \ No newline at end of file diff --git a/docs/apache-airflow/img/airflow_erd.svg b/docs/apache-airflow/img/airflow_erd.svg index b2124c4e9edba..93b9475ee4f6e 100644 --- a/docs/apache-airflow/img/airflow_erd.svg +++ b/docs/apache-airflow/img/airflow_erd.svg @@ -5,10 +5,10 @@ --> - + viewBox="0.00 0.00 1591.00 5241.50" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + %3 - + job @@ -1369,14 +1369,14 @@ task_instance--xcom -0..N +1 1 task_instance--xcom -1 +0..N 1 @@ -1605,8 +1605,8 @@ NOT NULL kwargs - [JSON] - NOT NULL + [TEXT] + NOT NULL triggerer_id [INTEGER] @@ -1618,34 +1618,34 @@ 0..N {0,1} - + -alembic_version - -alembic_version - -version_num - [VARCHAR(32)] - NOT NULL +session + +session + +id + [INTEGER] + NOT NULL + +data + [BYTEA] + +expiry + [TIMESTAMP] + +session_id + [VARCHAR(255)] - + -session - -session - -id - [INTEGER] - NOT NULL - -data - [BYTEA] - -expiry - [TIMESTAMP] - -session_id - [VARCHAR(255)] +alembic_version + +alembic_version + +version_num + [VARCHAR(32)] + NOT NULL diff --git a/docs/apache-airflow/migrations-ref.rst b/docs/apache-airflow/migrations-ref.rst index cb72790575360..0641dffdcaee4 100644 --- a/docs/apache-airflow/migrations-ref.rst +++ b/docs/apache-airflow/migrations-ref.rst @@ -39,7 +39,9 @@ Here's the list of all the Database Migrations that are executed via when you ru +---------------------------------+-------------------+-------------------+--------------------------------------------------------------+ | Revision ID | Revises ID | Airflow Version | Description | +=================================+===================+===================+==============================================================+ -| ``8e1c784a4fc7`` (head) | ``ab34f260b71c`` | ``2.9.0`` | Adding max_consecutive_failed_dag_runs column to dag_model | +| ``1949afb29106`` (head) | ``8e1c784a4fc7`` | ``2.9.0`` | encrypt trigger kwargs | ++---------------------------------+-------------------+-------------------+--------------------------------------------------------------+ +| ``8e1c784a4fc7`` | ``ab34f260b71c`` | ``2.9.0`` | Adding max_consecutive_failed_dag_runs column to dag_model | | | | | table | +---------------------------------+-------------------+-------------------+--------------------------------------------------------------+ | ``ab34f260b71c`` | ``d75389605139`` | ``2.9.0`` | add dataset_expression in DagModel | From 95df482e0868a324ed3f900040dc1317992bb7a6 Mon Sep 17 00:00:00 2001 From: Hussein Awala Date: Thu, 28 Mar 2024 23:02:58 +0100 Subject: [PATCH 2/8] Apply suggestions from code review Co-authored-by: Utkarsh Sharma --- .../versions/0138_2_9_0_encrypt_trigger_kwargs.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/airflow/migrations/versions/0138_2_9_0_encrypt_trigger_kwargs.py b/airflow/migrations/versions/0138_2_9_0_encrypt_trigger_kwargs.py index 38871268f9198..4cb65a30c6f00 100644 --- a/airflow/migrations/versions/0138_2_9_0_encrypt_trigger_kwargs.py +++ b/airflow/migrations/versions/0138_2_9_0_encrypt_trigger_kwargs.py @@ -48,6 +48,7 @@ def upgrade(): """Apply encrypt trigger kwargs""" session = get_session() try: + op.alter_column(table_name="trigger", column_name="kwargs", type_=sa.String()) for trigger in session.query(Trigger).all(): # convert dict to string and encrypt it trigger.encrypted_kwargs = trigger._encrypt_kwargs(trigger.encrypted_kwargs) @@ -60,10 +61,11 @@ def downgrade(): """Unapply encrypt trigger kwargs""" session = get_session() try: - fernet = get_fernet() + op.alter_column(table_name="trigger", column_name="kwargs", type_=ExtendedJSON(), + postgresql_using='kwargs::json') for trigger in session.query(Trigger).all(): # decrypt string and convert it to dict - trigger.encrypted_kwargs = trigger._decrypt_kwargs(trigger.encrypted_kwargs, fernet) + trigger.encrypted_kwargs = trigger._decrypt_kwargs(trigger.encrypted_kwargs) session.commit() finally: session.close() From 4a886a1665d3c2e0fb9a5a9b670a66916ae1e28e Mon Sep 17 00:00:00 2001 From: hussein-awala Date: Sun, 31 Mar 2024 23:01:17 +0200 Subject: [PATCH 3/8] move encryption/decryption to db utils --- ... 0140_2_9_0_update_trigger_kwargs_type.py} | 26 +++------------ airflow/utils/db.py | 32 +++++++++++++++++++ docs/apache-airflow/img/airflow_erd.sha256 | 2 +- docs/apache-airflow/img/airflow_erd.svg | 4 +-- docs/apache-airflow/migrations-ref.rst | 2 +- 5 files changed, 41 insertions(+), 25 deletions(-) rename airflow/migrations/versions/{0140_2_9_0_encrypt_trigger_kwargs.py => 0140_2_9_0_update_trigger_kwargs_type.py} (61%) diff --git a/airflow/migrations/versions/0140_2_9_0_encrypt_trigger_kwargs.py b/airflow/migrations/versions/0140_2_9_0_update_trigger_kwargs_type.py similarity index 61% rename from airflow/migrations/versions/0140_2_9_0_encrypt_trigger_kwargs.py rename to airflow/migrations/versions/0140_2_9_0_update_trigger_kwargs_type.py index da5f669f49380..188c231f15a1e 100644 --- a/airflow/migrations/versions/0140_2_9_0_encrypt_trigger_kwargs.py +++ b/airflow/migrations/versions/0140_2_9_0_update_trigger_kwargs_type.py @@ -16,7 +16,7 @@ # specific language governing permissions and limitations # under the License. -"""encrypt trigger kwargs +"""update trigger kwargs type Revision ID: 1949afb29106 Revises: ee1467d4aa35 @@ -45,26 +45,10 @@ def get_session() -> sa.orm.Session: def upgrade(): - """Apply encrypt trigger kwargs""" - session = get_session() - try: - op.alter_column(table_name="trigger", column_name="kwargs", type_=sa.String()) - for trigger in session.query(Trigger).all(): - # convert dict to string and encrypt it - trigger.encrypted_kwargs = trigger._encrypt_kwargs(trigger.encrypted_kwargs) - session.commit() - finally: - session.close() + """Update trigger kwargs type to string""" + op.alter_column(table_name="trigger", column_name="kwargs", type_=sa.String()) def downgrade(): - """Unapply encrypt trigger kwargs""" - session = get_session() - try: - op.alter_column(table_name="trigger", column_name="kwargs", type_=ExtendedJSON()) - for trigger in session.query(Trigger).all(): - # decrypt string and convert it to dict - trigger.encrypted_kwargs = trigger._decrypt_kwargs(trigger.encrypted_kwargs) - session.commit() - finally: - session.close() + """Unapply update trigger kwargs type to string""" + op.alter_column(table_name="trigger", column_name="kwargs", type_=ExtendedJSON()) diff --git a/airflow/utils/db.py b/airflow/utils/db.py index b7997498bb47d..37cf344515b33 100644 --- a/airflow/utils/db.py +++ b/airflow/utils/db.py @@ -972,6 +972,26 @@ def synchronize_log_template(*, session: Session = NEW_SESSION) -> None: session.add(LogTemplate(filename=filename, elasticsearch_id=elasticsearch_id)) +def encrypt_trigger_kwargs(*, session: Session) -> None: + """Encrypt trigger kwargs.""" + from airflow.models.trigger import Trigger + + for trigger in session.query(Trigger): + # convert dict to string and encrypt it + trigger.encrypted_kwargs = trigger._encrypt_kwargs(trigger.encrypted_kwargs) + session.commit() + + +def decrypt_trigger_kwargs(*, session: Session) -> None: + """Decrypt trigger kwargs.""" + from airflow.models.trigger import Trigger + + for trigger in session.query(Trigger): + # decrypt the string and convert it to dict + trigger.encrypted_kwargs = trigger._decrypt_kwargs(trigger.encrypted_kwargs) + session.commit() + + def check_conn_id_duplicates(session: Session) -> Iterable[str]: """ Check unique conn_id in connection table. @@ -1639,6 +1659,12 @@ def upgradedb( _reserialize_dags(session=session) add_default_pool_if_not_exists(session=session) synchronize_log_template(session=session) + if _revision_greater( + config, + _REVISION_HEADS_MAP["2.9.0"], + _get_current_revision(session=session), + ): + encrypt_trigger_kwargs(session=session) @provide_session @@ -1711,6 +1737,12 @@ def downgrade(*, to_revision, from_revision=None, show_sql_only=False, session: else: log.info("Applying downgrade migrations.") command.downgrade(config, revision=to_revision, sql=show_sql_only) + if _revision_greater( + config, + _REVISION_HEADS_MAP["2.9.0"], + to_revision, + ): + decrypt_trigger_kwargs(session=session) def drop_airflow_models(connection): diff --git a/docs/apache-airflow/img/airflow_erd.sha256 b/docs/apache-airflow/img/airflow_erd.sha256 index a84dd005a1239..39c512da0319b 100644 --- a/docs/apache-airflow/img/airflow_erd.sha256 +++ b/docs/apache-airflow/img/airflow_erd.sha256 @@ -1 +1 @@ -be94cc607bc5f9d98dffe83b79b26dacebe351c1b2686cf68266032bad5a04ae \ No newline at end of file +038a5b1e0dc0555e22139727d954d36c7c7749ffc6297054df53b42cb5e48587 \ No newline at end of file diff --git a/docs/apache-airflow/img/airflow_erd.svg b/docs/apache-airflow/img/airflow_erd.svg index 144668b90032a..072caaddfb4c7 100644 --- a/docs/apache-airflow/img/airflow_erd.svg +++ b/docs/apache-airflow/img/airflow_erd.svg @@ -1368,14 +1368,14 @@ task_instance--xcom -1 +0..N 1 task_instance--xcom -0..N +1 1 diff --git a/docs/apache-airflow/migrations-ref.rst b/docs/apache-airflow/migrations-ref.rst index 3dfbc1b901213..13c70abe9d64f 100644 --- a/docs/apache-airflow/migrations-ref.rst +++ b/docs/apache-airflow/migrations-ref.rst @@ -39,7 +39,7 @@ Here's the list of all the Database Migrations that are executed via when you ru +---------------------------------+-------------------+-------------------+--------------------------------------------------------------+ | Revision ID | Revises ID | Airflow Version | Description | +=================================+===================+===================+==============================================================+ -| ``1949afb29106`` (head) | ``ee1467d4aa35`` | ``2.9.0`` | encrypt trigger kwargs | +| ``1949afb29106`` (head) | ``ee1467d4aa35`` | ``2.9.0`` | update trigger kwargs type | +---------------------------------+-------------------+-------------------+--------------------------------------------------------------+ | ``ee1467d4aa35`` | ``b4078ac230a1`` | ``2.9.0`` | add display name for dag and task instance | +---------------------------------+-------------------+-------------------+--------------------------------------------------------------+ From a6ed2503310bbe91bbf21f8979aafae140384ef8 Mon Sep 17 00:00:00 2001 From: hussein-awala Date: Sun, 31 Mar 2024 23:58:02 +0200 Subject: [PATCH 4/8] fix data migration methods --- airflow/utils/db.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/airflow/utils/db.py b/airflow/utils/db.py index 37cf344515b33..2eeb084befbfd 100644 --- a/airflow/utils/db.py +++ b/airflow/utils/db.py @@ -975,20 +975,22 @@ def synchronize_log_template(*, session: Session = NEW_SESSION) -> None: def encrypt_trigger_kwargs(*, session: Session) -> None: """Encrypt trigger kwargs.""" from airflow.models.trigger import Trigger + from airflow.serialization.serialized_objects import BaseSerialization for trigger in session.query(Trigger): - # convert dict to string and encrypt it - trigger.encrypted_kwargs = trigger._encrypt_kwargs(trigger.encrypted_kwargs) + # convert serialized dict to string and encrypt it + trigger.kwargs = BaseSerialization.deserialize(json.loads(trigger.encrypted_kwargs)) session.commit() def decrypt_trigger_kwargs(*, session: Session) -> None: """Decrypt trigger kwargs.""" from airflow.models.trigger import Trigger + from airflow.serialization.serialized_objects import BaseSerialization for trigger in session.query(Trigger): - # decrypt the string and convert it to dict - trigger.encrypted_kwargs = trigger._decrypt_kwargs(trigger.encrypted_kwargs) + # decrypt the string and convert it to serialized dict + trigger.encrypted_kwargs = json.dumps(BaseSerialization.serialize(trigger.kwargs)) session.commit() From 187a0568657b0f451fe1c34cbceeebdc08250574 Mon Sep 17 00:00:00 2001 From: hussein-awala Date: Mon, 1 Apr 2024 00:34:09 +0200 Subject: [PATCH 5/8] fix postgres migration --- .../versions/0140_2_9_0_update_trigger_kwargs_type.py | 4 +++- docs/apache-airflow/img/airflow_erd.sha256 | 2 +- docs/apache-airflow/img/airflow_erd.svg | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/airflow/migrations/versions/0140_2_9_0_update_trigger_kwargs_type.py b/airflow/migrations/versions/0140_2_9_0_update_trigger_kwargs_type.py index 188c231f15a1e..a9c4f453c39dc 100644 --- a/airflow/migrations/versions/0140_2_9_0_update_trigger_kwargs_type.py +++ b/airflow/migrations/versions/0140_2_9_0_update_trigger_kwargs_type.py @@ -51,4 +51,6 @@ def upgrade(): def downgrade(): """Unapply update trigger kwargs type to string""" - op.alter_column(table_name="trigger", column_name="kwargs", type_=ExtendedJSON()) + op.alter_column( + table_name="trigger", column_name="kwargs", type_=ExtendedJSON(), postgresql_using="kwargs::json" + ) diff --git a/docs/apache-airflow/img/airflow_erd.sha256 b/docs/apache-airflow/img/airflow_erd.sha256 index 39c512da0319b..3faad8928d8e9 100644 --- a/docs/apache-airflow/img/airflow_erd.sha256 +++ b/docs/apache-airflow/img/airflow_erd.sha256 @@ -1 +1 @@ -038a5b1e0dc0555e22139727d954d36c7c7749ffc6297054df53b42cb5e48587 \ No newline at end of file +d4b9984ecb52efeb53220face6f766fefead80a9fc8b92136a17d0467e5f331b \ No newline at end of file diff --git a/docs/apache-airflow/img/airflow_erd.svg b/docs/apache-airflow/img/airflow_erd.svg index 072caaddfb4c7..144668b90032a 100644 --- a/docs/apache-airflow/img/airflow_erd.svg +++ b/docs/apache-airflow/img/airflow_erd.svg @@ -1368,14 +1368,14 @@ task_instance--xcom -0..N +1 1 task_instance--xcom -1 +0..N 1 From 141d48e5eac8ff65ac00faa4b517439e67cb46fd Mon Sep 17 00:00:00 2001 From: hussein-awala Date: Mon, 1 Apr 2024 02:13:36 +0200 Subject: [PATCH 6/8] fix the new column type --- .../versions/0140_2_9_0_update_trigger_kwargs_type.py | 2 +- docs/apache-airflow/img/airflow_erd.sha256 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/airflow/migrations/versions/0140_2_9_0_update_trigger_kwargs_type.py b/airflow/migrations/versions/0140_2_9_0_update_trigger_kwargs_type.py index a9c4f453c39dc..44f98ae8b2ae6 100644 --- a/airflow/migrations/versions/0140_2_9_0_update_trigger_kwargs_type.py +++ b/airflow/migrations/versions/0140_2_9_0_update_trigger_kwargs_type.py @@ -46,7 +46,7 @@ def get_session() -> sa.orm.Session: def upgrade(): """Update trigger kwargs type to string""" - op.alter_column(table_name="trigger", column_name="kwargs", type_=sa.String()) + op.alter_column(table_name="trigger", column_name="kwargs", type_=sa.Text()) def downgrade(): diff --git a/docs/apache-airflow/img/airflow_erd.sha256 b/docs/apache-airflow/img/airflow_erd.sha256 index 3faad8928d8e9..2e12b4971e1b0 100644 --- a/docs/apache-airflow/img/airflow_erd.sha256 +++ b/docs/apache-airflow/img/airflow_erd.sha256 @@ -1 +1 @@ -d4b9984ecb52efeb53220face6f766fefead80a9fc8b92136a17d0467e5f331b \ No newline at end of file +eb73d817b5cb5beb68961133753ceff02168a2540c81ad15555efe273bfc0202 \ No newline at end of file From 9a7fac59da528e12457d945b248df8d81c00fb4a Mon Sep 17 00:00:00 2001 From: hussein-awala Date: Mon, 1 Apr 2024 02:35:22 +0200 Subject: [PATCH 7/8] filter too old versions --- airflow/utils/db.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/airflow/utils/db.py b/airflow/utils/db.py index 2eeb084befbfd..26697bb118c8d 100644 --- a/airflow/utils/db.py +++ b/airflow/utils/db.py @@ -988,6 +988,11 @@ def decrypt_trigger_kwargs(*, session: Session) -> None: from airflow.models.trigger import Trigger from airflow.serialization.serialized_objects import BaseSerialization + if not inspect(session.bind).has_table(Trigger.__tablename__): + # table does not exist, nothing to do + # this can happen when we downgrade to an old version before the Trigger table was added + return + for trigger in session.query(Trigger): # decrypt the string and convert it to serialized dict trigger.encrypted_kwargs = json.dumps(BaseSerialization.serialize(trigger.kwargs)) From c48d6cb461115c33ef66dea9bdc167f42a6399af Mon Sep 17 00:00:00 2001 From: hussein-awala Date: Mon, 1 Apr 2024 13:32:11 +0200 Subject: [PATCH 8/8] apply from review --- .../0140_2_9_0_update_trigger_kwargs_type.py | 14 ++++---------- docs/apache-airflow/img/airflow_erd.sha256 | 2 +- docs/apache-airflow/img/airflow_erd.svg | 8 ++++---- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/airflow/migrations/versions/0140_2_9_0_update_trigger_kwargs_type.py b/airflow/migrations/versions/0140_2_9_0_update_trigger_kwargs_type.py index 44f98ae8b2ae6..dbde1201e4cd0 100644 --- a/airflow/migrations/versions/0140_2_9_0_update_trigger_kwargs_type.py +++ b/airflow/migrations/versions/0140_2_9_0_update_trigger_kwargs_type.py @@ -38,19 +38,13 @@ airflow_version = "2.9.0" -def get_session() -> sa.orm.Session: - conn = op.get_bind() - sessionmaker = sa.orm.sessionmaker() - return sessionmaker(bind=conn) - - def upgrade(): """Update trigger kwargs type to string""" - op.alter_column(table_name="trigger", column_name="kwargs", type_=sa.Text()) + with op.batch_alter_table("trigger") as batch_op: + batch_op.alter_column("kwargs", type_=sa.Text(), ) def downgrade(): """Unapply update trigger kwargs type to string""" - op.alter_column( - table_name="trigger", column_name="kwargs", type_=ExtendedJSON(), postgresql_using="kwargs::json" - ) + with op.batch_alter_table("trigger") as batch_op: + batch_op.alter_column("kwargs", type_=ExtendedJSON(), postgresql_using="kwargs::json") diff --git a/docs/apache-airflow/img/airflow_erd.sha256 b/docs/apache-airflow/img/airflow_erd.sha256 index 2e12b4971e1b0..09f84daea2acb 100644 --- a/docs/apache-airflow/img/airflow_erd.sha256 +++ b/docs/apache-airflow/img/airflow_erd.sha256 @@ -1 +1 @@ -eb73d817b5cb5beb68961133753ceff02168a2540c81ad15555efe273bfc0202 \ No newline at end of file +2a24225537326f38be5df14e0b7a8dca867122093e0fa932f1a11ac12d1fb11c \ No newline at end of file diff --git a/docs/apache-airflow/img/airflow_erd.svg b/docs/apache-airflow/img/airflow_erd.svg index 144668b90032a..dc32fe0566902 100644 --- a/docs/apache-airflow/img/airflow_erd.svg +++ b/docs/apache-airflow/img/airflow_erd.svg @@ -1361,28 +1361,28 @@ task_instance--xcom -0..N +1 1 task_instance--xcom -1 +0..N 1 task_instance--xcom -0..N +1 1 task_instance--xcom -1 +0..N 1