dagshift rules¶
Generated by tools/gen_rules_doc.py; every example below is run through dagshift when the page is built.
Severity says what happened to a finding:
- fixed: rewritten, behaviour preserved
- manual: breaks on the target and has no safe rewrite; a recipe is attached
- warn: still imports, but behaves differently on the target
- info: worth knowing before cutover
Rules only fire when they matter for the upgrade you picked. 2.7 to 2.10 gets the 2.x deprecations; 2.10 to 3.3 gets those plus the Airflow 3 changes; 3.0 to 3.3 gets what changed inside Airflow 3.
Turn a rule off with --ignore DS102, ignore = ["DS102"] in [tool.dagshift], or
# dagshift: ignore[DS102] on a line.
- Import paths: DS101, DS102, DS103, DS104
- Removed functions and changed signatures: DS201, DS202, DS203, DS204, DS205, DS206, DS208, DS209, DS210, DS211
- Task context and templates: DS301, DS302, DS303, DS304
- Metadata database access: DS401, DS402
- Behaviour changes: DS501, DS502, DS503, DS504, DS505
- Code generators: DS601, DS602
- YAML and JSON configs: DS701, DS703, DS704
- Environment and platform: DS801, DS802, DS803, DS804, DS805, DS806, DS807
DS1xx Import paths¶
DS101¶
import-moved: Import path removed or deprecated on the target; rewritten to where it lives on the target.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, dags/etl.py:
@@ -1,2 +1,2 @@
-from airflow.operators.bash_operator import BashOperator
-from airflow.operators.python_operator import PythonOperator
+from airflow.providers.standard.operators.bash import BashOperator
+from airflow.providers.standard.operators.python import PythonOperator
fixed, line 1:
airflow.operators.bash_operator.BashOperator->airflow.providers.standard.operators.bash.BashOperator(removed on Airflow 3.3)fixed, line 2:
airflow.operators.python_operator.PythonOperator->airflow.providers.standard.operators.python.PythonOperator(removed on Airflow 3.3)
DS102¶
import-modernized: Import still works but has a newer home on the target (for example airflow.sdk); rewritten when modernize is on.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, dags/etl.py:
@@ -1,2 +1,2 @@
-from airflow import DAG
+from airflow.sdk import DAG
from airflow.decorators import task
fixed, line 1:
airflow.DAG->airflow.sdk.DAG(still works, but airflow.sdk is the Airflow 3 authoring interface)
DS103¶
import-removed: Imported name no longer exists on the target and has no drop-in replacement.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, dags/etl.py:
manual, line 1:
airflow.utils.dates.date_rangedoes not exist in Airflow 3.3.How to fix: Use pendulum.interval(...) or a Timetable.
DS104¶
module-family-removed: airflow.contrib / airflow.www / airflow.api.client / SubDAG modules, star imports.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, dags/etl.py:
manual, line 1:
from airflow.operators.subdag import SubDagOperator: SubDAGs are removed in Airflow 3. Rebuild as a TaskGroup or a separate DAG.
DS2xx Removed functions and changed signatures¶
DS201¶
days-ago: days_ago() deprecated or removed on the target; replaced with a fixed pendulum start_date.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, dags/etl.py:
@@ -1,5 +1,5 @@
from airflow import DAG
-from airflow.utils.dates import days_ago
+import pendulum
-with DAG("etl", start_date=days_ago(2), schedule="@daily"):
+with DAG("etl", start_date=pendulum.datetime(2025, 12, 30, tz="UTC"), schedule="@daily"):
pass
fixed, line 4: days_ago(2) was removed in Airflow 3.0 (target 3.3); replaced with a fixed start_date 2025-12-30 (what days_ago(2) returned on 2026-01-01). A fixed start_date also stops a new DAG version being created every day.
DS202¶
apply-defaults: @apply_defaults deprecated or removed on the target; decorator dropped.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, plugins/ops.py:
@@ -1,9 +1,7 @@
from airflow.models import BaseOperator
-from airflow.utils.decorators import apply_defaults
class LoadOperator(BaseOperator):
- @apply_defaults
def __init__(self, table, **kwargs):
super().__init__(**kwargs)
self.table = table
fixed, line 7: @apply_defaults was removed; operators apply default_args automatically.
DS203¶
dag-arguments: DAG() arguments the target renamed or removed (schedule_interval, concurrency, ...); positional arguments converted to keywords.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.7 to 2.10, dags/etl.py:
@@ -1,5 +1,5 @@
from datetime import datetime
from airflow import DAG
-dag = DAG("etl", "Nightly load", schedule_interval="@daily", concurrency=4,
+dag = DAG("etl", "Nightly load", schedule="@daily", max_active_tasks=4,
start_date=datetime(2024, 1, 1))
fixed, line 4: DAG(
schedule_interval=...) is deprecated in Airflow 2.10; renamed toschedule.fixed, line 4: DAG(
concurrency=...) is deprecated in Airflow 2.10; renamed tomax_active_tasks.
DS204¶
operator-arguments: Operator arguments the target rejects or renamed (provide_context, task_concurrency, TriggerDagRun execution_date).
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, dags/etl.py:
@@ -3,4 +3,4 @@
def load(**context):
pass
-PythonOperator(task_id="load", python_callable=load, provide_context=True, task_concurrency=1)
+PythonOperator(task_id="load", python_callable=load, max_active_tis_per_dag=1)
fixed, line 6:
provide_contextis rejected by Airflow 3 ('Invalid arguments were passed'); context is always passed, so it was dropped.fixed, line 6: Operator
task_concurrency=is not accepted in Airflow 3.3; renamed tomax_active_tis_per_dag=.
DS205¶
variable-get-default: Variable.get(default_var=) -> Variable.get(default=) where the target only accepts default.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, dags/etl.py:
@@ -1,3 +1,3 @@
from airflow.models import Variable
-bucket = Variable.get("bucket", default_var="raw")
+bucket = Variable.get("bucket", default="raw")
fixed, line 3: Variable.get(default_var=...) is Variable.get(default=...) in Airflow 3.3.
DS206¶
sla: SLAs are ignored from Airflow 3.0; Deadline Alerts replace them from 3.1.
Applies to: upgrades whose target is Airflow 3.0 or newer.
Example, Airflow 2.10 to 3.3, dags/etl.py:
from datetime import datetime, timedelta
from airflow import DAG
def alert(*args):
pass
with DAG("etl", start_date=datetime(2024, 1, 1), schedule="@daily", sla_miss_callback=alert,
default_args={"sla": timedelta(hours=1)}):
pass
warn, line 7:
sla_miss_callbackis still accepted but SLAs were removed in Airflow 3: it has no effect.How to fix: Replace SLAs with Deadline Alerts: DAG(deadline=DeadlineAlert(reference=..., interval=..., callback=...)). See the Airflow 'Deadline Alerts' docs for your version.
warn, line 8:
slais still accepted but SLAs were removed in Airflow 3: it has no effect.How to fix: Replace SLAs with Deadline Alerts: DAG(deadline=DeadlineAlert(reference=..., interval=..., callback=...)). See the Airflow 'Deadline Alerts' docs for your version.
DS208¶
schedule-default: DAG without schedule: Airflow 2 ran it daily, Airflow 3 never schedules it.
Applies to: upgrades from Airflow 2 to Airflow 3.
Example, Airflow 2.10 to 3.3, dags/etl.py:
@@ -1,5 +1,5 @@
-from datetime import datetime
+from datetime import timedelta, datetime
from airflow import DAG
-with DAG("etl", start_date=datetime(2024, 1, 1)):
+with DAG("etl", start_date=datetime(2024, 1, 1), schedule=timedelta(days=1)):
pass
fixed, line 4: DAG had no schedule: Airflow 2 ran it daily, Airflow 3 would never schedule it. Added schedule=timedelta(days=1) to keep it running. Set schedule=None if it should only run when triggered.
DS209¶
catchup-default: catchup default changed from True to False in Airflow 3.
Applies to: upgrades from Airflow 2 to Airflow 3.
Example, Airflow 2.10 to 3.3, dags/etl.py:
from datetime import datetime
from airflow import DAG
with DAG("etl", start_date=datetime(2024, 1, 1), schedule="@daily"):
pass
info, line 4: No explicit catchup: Airflow 2 used [scheduler] catchup_by_default (True unless changed); Airflow 3 defaults to False, so missed intervals are no longer backfilled.
How to fix: Set catchup=True on DAGs that rely on backfilling, or run with pin_catchup=true.
DS210¶
dynamic-dag-kwargs: DAG(**kwargs) built at runtime; keys not visible to static analysis.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, dags/etl.py:
from airflow import DAG
from config import load
for cfg in load():
globals()[cfg["dag_id"]] = DAG(**cfg)
info, line 5: DAG arguments come from **kwargs; dagshift cannot see those keys. Check the source (often a config file) for schedule_interval, concurrency, timetable or sla_miss_callback.
DS211¶
replaced-operators: PostgresOperator & co -> SQLExecuteQueryOperator, SimpleHttpOperator -> HttpOperator.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, dags/etl.py:
@@ -1,3 +1,3 @@
-from airflow.providers.postgres.operators.postgres import PostgresOperator
+from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator
-PostgresOperator(task_id="vacuum", postgres_conn_id="dwh", sql="VACUUM")
+SQLExecuteQueryOperator(task_id="vacuum", conn_id="dwh", sql="VACUUM")
fixed, line 3: PostgresOperator no longer exists in current provider releases; replaced with SQLExecuteQueryOperator from airflow.providers.common.sql.operators.sql (postgres_conn_id= -> conn_id=).
DS3xx Task context and templates¶
DS301¶
template-variables: Jinja template variables the target removed or deprecated (execution_date, next_ds, tomorrow_ds, conf, ...).
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, dags/etl.py:
@@ -1,3 +1,3 @@
from airflow.operators.bash import BashOperator
-BashOperator(task_id="load", bash_command="load.sh {{ execution_date }} {{ next_ds }} {{ prev_ds }}")
+BashOperator(task_id="load", bash_command="load.sh {{ logical_date }} {{ data_interval_end | ds }} {{ prev_ds }}")
fixed, line 3: Template variable
execution_datedoes not exist in Airflow 3.3; renamed tological_date(same value).fixed, line 3: Template variable
next_dsdoes not exist in Airflow 3.3; replaced withdata_interval_end | ds(same value).manual, line 3: Template variable
prev_dsdoes not exist in Airflow 3.3.How to fix:
prev_ds(logical date of the previous scheduled run) is removed. Closest:{{ prev_data_interval_start_success | ds }}(previous successful run), or for a fixed schedule{{ macros.ds_add(ds, -1) }}(daily). Pick the one that matches your intent.
DS302¶
context-keys: Task-context keys the target removed or deprecated, in Python (context['execution_date'], def f(next_ds)).
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, dags/etl.py:
@@ -1,3 +1,3 @@
def extract(execution_date, **context):
- next_day = context["next_ds"]
+ next_day = context["data_interval_end"].strftime("%Y-%m-%d")
return execution_date, next_day
fixed, line 2:
context["next_ds"]:next_dsdoes not exist in Airflow 3.3; replaced withcontext["data_interval_end"].strftime("%Y-%m-%d").
DS303¶
xcom-pull-task-ids: xcom_pull() without task_ids reads only the current task from Airflow 3.
Applies to: upgrades from Airflow 2 to Airflow 3.
Example, Airflow 2.10 to 3.3, dags/etl.py:
warn, line 2: xcom_pull() without task_ids reads only the current task's XComs from Airflow 3.0 on (Airflow 2 searched every task in the run).
How to fix: Pass task_ids explicitly: ti.xcom_pull(task_ids='producer', key='...').
DS304¶
execution-date-attribute: dag_run.execution_date / ti.execution_date -> .logical_date.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, dags/etl.py:
@@ -1,2 +1,2 @@
def report(dag_run, **context):
- return dag_run.execution_date
+ return dag_run.logical_date
fixed, line 2:
.execution_dateon the DAG run / task instance is gone in Airflow 3.3; now.logical_date(same value).
DS4xx Metadata database access¶
DS401¶
metadata-db-access: ORM sessions/models used from DAG or task code (blocked from Airflow 3).
Applies to: upgrades whose target is Airflow 3.0 or newer.
Example, Airflow 2.10 to 3.3, dags/etl.py:
from airflow.models import TaskInstance
from airflow.utils.session import provide_session
@provide_session
def failures(session=None, **context):
return session.query(TaskInstance).filter(TaskInstance.state == "failed").count()
manual, line 5:
failuresreads or writes the metadata DB directly (TaskInstance). Airflow 3 blocks database access from task code; this fails at run time, not at parse time.How to fix: Task-instance state: context['ti'].get_task_states(dag_id=..., run_ids=[...]) or get_ti_count(dag_id=..., states=[...]).
DS402¶
db-backed-methods: dag_run.get_task_instances(), ti.current_state() and similar ORM methods.
Applies to: upgrades whose target is Airflow 3.0 or newer.
Example, Airflow 2.10 to 3.3, dags/etl.py:
manual, line 2:
dag_run.get_task_instances()hits the metadata DB, which task code cannot reach in Airflow 3.How to fix: Ask the Task SDK instead of the ORM. Inside a task:
ti = context['ti'] states = ti.get_task_states(dag_id=ti.dag_id, run_ids=[ti.run_id])[ti.run_id] failed = [t for t, s in states.items() if s == 'failed' and t != ti.task_id] Signature on Airflow 3.3: get_task_states(dag_id: 'str', map_index: 'int | None' = None, task_ids: 'list[str] | None' = None, task_group_id: 'str | None' = None, logical_dates: 'list[datetime] | None' = None, run_ids: 'list[str] | None' = None) -> 'dict[str, Any]'
DS5xx Behaviour changes¶
DS501¶
guarded-imports: try/except ImportError around Airflow imports hides breakage.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, dags/etl.py:
try:
from airflow.operators.bash import BashOperator
except ImportError:
from airflow.operators.bash_operator import BashOperator
warn, line 1: Airflow imports are wrapped in try/except: if one of them is missing on Airflow 3 the except branch runs silently and the DAG imports with fallback objects (often a no-op decorator), failing later at run time instead of at parse time.
How to fix: Guard only the 'is Airflow installed' check (e.g.
import airflowalone inside the try), or catch ImportError and re-raise when running under Airflow. dagshift has rewritten the imports inside the block; rundagshift validateto confirm they resolve.
DS502¶
parse-time-values: now()/time()/uuid4() evaluated at parse time create a DAG version per parse.
Applies to: upgrades whose target is Airflow 3.0 or newer.
Example, Airflow 2.10 to 3.3, dags/etl.py:
from datetime import datetime
from airflow import DAG
with DAG("etl", start_date=datetime(2024, 1, 1), schedule="@daily",
tags=[datetime.now().strftime("%Y%m%d")]):
pass
warn, line 5:
datetime.datetime.now()is evaluated every time the DAG file is parsed. Airflow 3 versions DAGs, so a value that changes on each parse creates a new DAG version each time (and in Airflow 2 it was already non-deterministic).How to fix: Use a fixed value, or compute it at run time: in a templated field ('{{ data_interval_end }}', '{{ macros.datetime.now() }}') or inside the task callable.
DS503¶
schedule-semantics: Cron/timedelta schedules use trigger timetables in Airflow 3: logical_date and ds shift.
Applies to: upgrades from Airflow 2 to Airflow 3.
Example, Airflow 2.10 to 3.3, dags/etl.py:
from datetime import datetime
from airflow import DAG
from airflow.operators.bash import BashOperator
with DAG("etl", start_date=datetime(2024, 1, 1), schedule="30 4 * * *", catchup=False):
BashOperator(task_id="load", bash_command="load.sh {{ ds }}")
info, line 5: Schedule '30 4 * * *': Airflow 3 runs it with CronTriggerTimetable, so a run's logical_date is the trigger time instead of the start of the interval, and data_interval_start == data_interval_end. Nothing in this file reads those values.
How to fix: To keep Airflow 2 semantics: run dagshift with keep_cron_data_intervals=true (wraps the schedule in a *DataIntervalTimetable), or set [scheduler] create_cron_data_intervals = True for the whole environment (Cloud Composer: airflow_config_overrides 'scheduler-create_cron_data_intervals' = 'True') before the first Airflow 3 run.
DS504¶
plugins: Flask/FAB plugin views and plugin attributes the target removed.
Applies to: upgrades whose target is Airflow 3.0 or newer.
Example, Airflow 2.10 to 3.3, plugins/acme.py:
from airflow.plugins_manager import AirflowPlugin
from flask import Blueprint
bp = Blueprint("acme", __name__)
class AcmePlugin(AirflowPlugin):
name = "acme"
flask_blueprints = [bp]
manual, line 8: Plugin attribute
flask_blueprintsis a Flask/FAB UI extension. The Airflow 3 UI does not render it; it only works with the FAB provider's legacy views.How to fix: Port it to fastapi_apps, external_views and react_apps on Airflow 3.3, or install apache-airflow-providers-fab and use FabAuthManager to keep the legacy view.
DS505¶
templated-file: Jinja in code positions; rendered values are not visible to static analysis.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, templates/table_dag.py.j2:
from airflow import DAG
from airflow.operators.bash_operator import BashOperator
with DAG("{{ name }}", schedule_interval="{{ schedule }}") as dag:
{% for t in tables %}
BashOperator(task_id="load_{{ t }}", bash_command="load.sh {{ t }} {{ '{{' }} ds {{ '}}' }}")
{% endfor %}
info, line 1: Jinja template: 2 Jinja construct(s) in code positions were kept as-is. Values your generator injects there (schedules, operator classes, kwargs) are only visible after rendering.
How to fix: Render the templates and run
dagshift checkon the output, or usedagshift validate --render-cmd '<your build command>'.
DS6xx Code generators¶
DS601¶
embedded-code: Airflow code inside string literals (code generators) migrated recursively.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, generators/build.py:
@@ -1,4 +1,4 @@
TEMPLATE = """
-from airflow.operators.bash_operator import BashOperator
-BashOperator(task_id="{name}", bash_command="run {{{{ execution_date }}}}")
+from airflow.providers.standard.operators.bash import BashOperator
+BashOperator(task_id="{name}", bash_command="run {{{{ logical_date }}}}")
"""
fixed, line 1: Migrated Airflow code embedded in a string literal (DS101, DS301).
DS602¶
legacy-names-in-strings: Dotted paths / Dataset(...) inside string literals.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, generators/registry.py:
@@ -1 +1 @@
-OPERATORS = {"bash": "airflow.operators.bash_operator.BashOperator"}
+OPERATORS = {"bash": "airflow.providers.standard.operators.bash.BashOperator"}
fixed, line 1: Updated Airflow name inside a string literal: airflow.operators.bash_operator.BashOperator -> airflow.providers.standard.operators.bash.BashOperator
DS7xx YAML and JSON configs¶
DS701¶
config-import-paths: Dotted Airflow import paths in YAML/JSON configs.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, configs/pipelines.yaml:
@@ -1,4 +1,4 @@
pipelines:
- name: customers
- operator: airflow.operators.bash_operator.BashOperator
+ operator: airflow.providers.standard.operators.bash.BashOperator
command: "load.sh {{ execution_date }}"
fixed, line 3:
airflow.operators.bash_operator.BashOperator->airflow.providers.standard.operators.bash.BashOperator
DS703¶
dag-factory-keys: dag-factory keys renamed or removed.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, configs/dag_factory.yml:
@@ -1,7 +1,7 @@
default:
default_args:
owner: data-eng
- schedule_interval: "@daily"
+ schedule: "@daily"
customers:
tasks:
load:
fixed, line 4:
schedule_intervalis not accepted in Airflow 3.3; renamed toschedule.
DS704¶
config-airflow-keys: Airflow parameter names the target removed, used as keys in homegrown configs.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, configs/pipeline.json:
warn, line 1: Config key
schedule_intervalmatches an Airflow parameter that is not accepted in Airflow 3.3 (nowschedule).How to fix: If your generator passes this key straight into DAG()/an operator (e.g. **config), rename it (or run with rename_config_keys=true). If the generator maps it in code, dagshift has already fixed the Python side.
DS8xx Environment and platform¶
DS801¶
airflow-version-pin: Airflow pinned below the target in requirements, constraints, images, compose or Helm values.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, requirements.txt:
manual, line 1: Airflow is pinned to apache-airflow==2.10.5; the upgrade targets 3.3.
How to fix: Pin apache-airflow to the 3.3.x release you are moving to. Install with the constraints file of the exact 3.3.x release: https://raw.githubusercontent.com/apache/airflow/constraints-3.3.
/constraints- .txt info, line 2: apache-airflow-providers-google is pinned to 10.26.0.
How to fix: Take provider versions from the constraints file of Airflow 3.3.x; older provider releases may not support it.
DS802¶
provider-requirements: Provider packages required by migrated imports.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
DS803¶
airflowignore-syntax: .airflowignore uses regexps; Airflow 3 defaults to glob.
Applies to: upgrades from Airflow 2 to Airflow 3.
Example, Airflow 2.10 to 3.3, dags/.airflowignore:
warn, line 1: .airflowignore line
scratch_.*looks like a regular expression; Airflow 3 reads this file as glob patterns by default.How to fix: Rewrite it as a glob (e.g.
.*_test\.py->*_test.py), or set [core] dag_ignore_file_syntax = regexp.warn, line 2: .airflowignore line
.*_test\.pylooks like a regular expression; Airflow 3 reads this file as glob patterns by default.How to fix: Rewrite it as a glob (e.g.
.*_test\.py->*_test.py), or set [core] dag_ignore_file_syntax = regexp.
DS804¶
cloud-composer: Cloud Composer image below the target (no in-place upgrade from Airflow 2 to 3).
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, infra/main.tf:
resource "google_composer_environment" "prod" {
config {
software_config {
image_version = "composer-2.9.7-airflow-2.10.5"
}
}
}
info, line 4: Cloud Composer image composer-2.9.7-airflow-2.10.5 runs Airflow 2.10.5.
How to fix: Composer does not yet support upgrading an environment from Airflow 2 to 3 in place: create a second Composer 3 environment on composer-3-airflow-3.3.x-build.N and cut over. Do not just change image_version on the existing resource.
DS805¶
mwaa: Amazon MWAA environment below the target.
Applies to: any upgrade (the knowledge base decides per target what is removed or deprecated).
Example, Airflow 2.10 to 3.3, infra/mwaa.tf:
info, line 2: Amazon MWAA environment on Airflow 2.10.3.
How to fix: Check that MWAA offers Airflow 3.3 and its documented upgrade path before cutover.
DS806¶
airflow-config: airflow.cfg / webserver config overrides.
Applies to: upgrades from Airflow 2 to Airflow 3.
Example, Airflow 2.10 to 3.3, airflow.cfg:
info, line 1: airflow.cfg found.
How to fix: Run
airflow config update --fixwith Airflow 3.3 installed to rename and drop changed options.
DS807¶
dag-factory: dag-factory configs: use dag-factory 1.0's own convert/lint for layout changes.
Applies to: upgrades whose target is Airflow 3.0 or newer.
Example, Airflow 2.10 to 3.3, configs/dag_factory.yml:
default:
default_args:
owner: data-eng
customers:
schedule: "@daily"
tasks:
load:
operator: airflow.providers.standard.operators.bash.BashOperator
bash_command: load.sh
info, line 1: dag-factory config. dagshift updated import paths, template variables and renamed keys. Airflow 3 needs dag-factory 1.0+, which also changed its YAML layout.
How to fix: Run
dagfactory convertand thendagfactory lint(dag-factory 1.0 CLI) for structural changes such as tasks/task_groups as lists.