This is an automated email from the ASF dual-hosted git repository.

bugraoz pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 203acbb8ef0 Add pause and unpause commands for dags (#57685)
203acbb8ef0 is described below

commit 203acbb8ef0883efe7de06169d8a4fb6840d22ca
Author: Bugra Ozturk <[email protected]>
AuthorDate: Sun Nov 2 18:17:18 2025 +0100

    Add pause and unpause commands for dags (#57685)
    
    * Add pause and unpause commands for dags
    
    * Convert exit in failure
    
    * Update airflow-ctl/src/airflowctl/ctl/commands/dag_command.py
    
    * Refactor error message for pause/unpause operations in DAG commands
---
 airflow-ctl/docs/images/command_hashes.txt         |   2 +-
 airflow-ctl/docs/images/output_dags.svg            | 122 +++++++++---------
 airflow-ctl/src/airflowctl/api/client.py           |   1 +
 airflow-ctl/src/airflowctl/ctl/cli_config.py       |  34 +++++
 .../src/airflowctl/ctl/commands/dag_command.py     |  74 +++++++++++
 .../ctl/commands/test_config_command.py            |   2 +-
 .../airflow_ctl/ctl/commands/test_dag_command.py   | 139 +++++++++++++++++++++
 7 files changed, 315 insertions(+), 59 deletions(-)

diff --git a/airflow-ctl/docs/images/command_hashes.txt 
b/airflow-ctl/docs/images/command_hashes.txt
index 1a017304f15..6792a0234ef 100644
--- a/airflow-ctl/docs/images/command_hashes.txt
+++ b/airflow-ctl/docs/images/command_hashes.txt
@@ -4,7 +4,7 @@ auth:f396d4bce90215599dde6ad0a8f30f29
 backfill:bbce9859a2d1ce054ad22db92dea8c05
 config:cb175bedf29e8a2c2c6a2ebd13d770a7
 connections:a16225e1c7d28488d0da612752669b4b
-dags:4c2ae65c76b32efcd86b5fd0c1831a2e
+dags:287a128a71c97d2b537e09a5c7c73c09
 dagrun:f47ed2a89ed0f8c71f79dba53a3a3882
 jobs:7f8680afff230eb9940bc7fca727bd52
 pools:03fc7d948cbecf16ff8d640eb8f0ce43
diff --git a/airflow-ctl/docs/images/output_dags.svg 
b/airflow-ctl/docs/images/output_dags.svg
index 496fa8d995c..6863827b594 100644
--- a/airflow-ctl/docs/images/output_dags.svg
+++ b/airflow-ctl/docs/images/output_dags.svg
@@ -1,4 +1,4 @@
-<svg class="rich-terminal" viewBox="0 0 811 611.1999999999999" 
xmlns="http://www.w3.org/2000/svg";>
+<svg class="rich-terminal" viewBox="0 0 811 660.0" 
xmlns="http://www.w3.org/2000/svg";>
     <!-- Generated with Rich https://www.textualize.io -->
     <style>
 
@@ -19,130 +19,138 @@
         font-weight: 700;
     }
 
-    .terminal-111300623-matrix {
+    .terminal-1075761825-matrix {
         font-family: Fira Code, monospace;
         font-size: 20px;
         line-height: 24.4px;
         font-variant-east-asian: full-width;
     }
 
-    .terminal-111300623-title {
+    .terminal-1075761825-title {
         font-size: 18px;
         font-weight: bold;
         font-family: arial;
     }
 
-    .terminal-111300623-r1 { fill: #ff8700 }
-.terminal-111300623-r2 { fill: #c5c8c6 }
-.terminal-111300623-r3 { fill: #808080 }
-.terminal-111300623-r4 { fill: #68a0b3 }
+    .terminal-1075761825-r1 { fill: #ff8700 }
+.terminal-1075761825-r2 { fill: #c5c8c6 }
+.terminal-1075761825-r3 { fill: #808080 }
+.terminal-1075761825-r4 { fill: #68a0b3 }
     </style>
 
     <defs>
-    <clipPath id="terminal-111300623-clip-terminal">
-      <rect x="0" y="0" width="792.0" height="560.1999999999999" />
+    <clipPath id="terminal-1075761825-clip-terminal">
+      <rect x="0" y="0" width="792.0" height="609.0" />
     </clipPath>
-    <clipPath id="terminal-111300623-line-0">
+    <clipPath id="terminal-1075761825-line-0">
     <rect x="0" y="1.5" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-1">
+<clipPath id="terminal-1075761825-line-1">
     <rect x="0" y="25.9" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-2">
+<clipPath id="terminal-1075761825-line-2">
     <rect x="0" y="50.3" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-3">
+<clipPath id="terminal-1075761825-line-3">
     <rect x="0" y="74.7" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-4">
+<clipPath id="terminal-1075761825-line-4">
     <rect x="0" y="99.1" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-5">
+<clipPath id="terminal-1075761825-line-5">
     <rect x="0" y="123.5" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-6">
+<clipPath id="terminal-1075761825-line-6">
     <rect x="0" y="147.9" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-7">
+<clipPath id="terminal-1075761825-line-7">
     <rect x="0" y="172.3" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-8">
+<clipPath id="terminal-1075761825-line-8">
     <rect x="0" y="196.7" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-9">
+<clipPath id="terminal-1075761825-line-9">
     <rect x="0" y="221.1" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-10">
+<clipPath id="terminal-1075761825-line-10">
     <rect x="0" y="245.5" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-11">
+<clipPath id="terminal-1075761825-line-11">
     <rect x="0" y="269.9" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-12">
+<clipPath id="terminal-1075761825-line-12">
     <rect x="0" y="294.3" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-13">
+<clipPath id="terminal-1075761825-line-13">
     <rect x="0" y="318.7" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-14">
+<clipPath id="terminal-1075761825-line-14">
     <rect x="0" y="343.1" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-15">
+<clipPath id="terminal-1075761825-line-15">
     <rect x="0" y="367.5" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-16">
+<clipPath id="terminal-1075761825-line-16">
     <rect x="0" y="391.9" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-17">
+<clipPath id="terminal-1075761825-line-17">
     <rect x="0" y="416.3" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-18">
+<clipPath id="terminal-1075761825-line-18">
     <rect x="0" y="440.7" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-19">
+<clipPath id="terminal-1075761825-line-19">
     <rect x="0" y="465.1" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-20">
+<clipPath id="terminal-1075761825-line-20">
     <rect x="0" y="489.5" width="793" height="24.65"/>
             </clipPath>
-<clipPath id="terminal-111300623-line-21">
+<clipPath id="terminal-1075761825-line-21">
     <rect x="0" y="513.9" width="793" height="24.65"/>
             </clipPath>
+<clipPath id="terminal-1075761825-line-22">
+    <rect x="0" y="538.3" width="793" height="24.65"/>
+            </clipPath>
+<clipPath id="terminal-1075761825-line-23">
+    <rect x="0" y="562.7" width="793" height="24.65"/>
+            </clipPath>
     </defs>
 
-    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="809" height="609.2" rx="8"/>
+    <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" 
x="1" y="1" width="809" height="658" rx="8"/>
             <g transform="translate(26,22)">
             <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
             <circle cx="22" cy="0" r="7" fill="#febc2e"/>
             <circle cx="44" cy="0" r="7" fill="#28c840"/>
             </g>
         
-    <g transform="translate(9, 41)" 
clip-path="url(#terminal-111300623-clip-terminal)">
+    <g transform="translate(9, 41)" 
clip-path="url(#terminal-1075761825-clip-terminal)">
     
-    <g class="terminal-111300623-matrix">
-    <text class="terminal-111300623-r1" x="0" y="20" textLength="73.2" 
clip-path="url(#terminal-111300623-line-0)">Usage:</text><text 
class="terminal-111300623-r3" x="85.4" y="20" textLength="183" 
clip-path="url(#terminal-111300623-line-0)">airflowctl&#160;dags</text><text 
class="terminal-111300623-r2" x="268.4" y="20" textLength="24.4" 
clip-path="url(#terminal-111300623-line-0)">&#160;[</text><text 
class="terminal-111300623-r4" x="292.8" y="20" textLength="24.4" 
clip-path="url(#terminal [...]
-</text><text class="terminal-111300623-r2" x="793" y="44.4" textLength="12.2" 
clip-path="url(#terminal-111300623-line-1)">
-</text><text class="terminal-111300623-r2" x="0" y="68.8" textLength="280.6" 
clip-path="url(#terminal-111300623-line-2)">Perform&#160;Dags&#160;operations</text><text
 class="terminal-111300623-r2" x="793" y="68.8" textLength="12.2" 
clip-path="url(#terminal-111300623-line-2)">
-</text><text class="terminal-111300623-r2" x="793" y="93.2" textLength="12.2" 
clip-path="url(#terminal-111300623-line-3)">
-</text><text class="terminal-111300623-r1" x="0" y="117.6" textLength="256.2" 
clip-path="url(#terminal-111300623-line-4)">Positional&#160;Arguments:</text><text
 class="terminal-111300623-r2" x="793" y="117.6" textLength="12.2" 
clip-path="url(#terminal-111300623-line-4)">
-</text><text class="terminal-111300623-r4" x="24.4" y="142" textLength="85.4" 
clip-path="url(#terminal-111300623-line-5)">COMMAND</text><text 
class="terminal-111300623-r2" x="793" y="142" textLength="12.2" 
clip-path="url(#terminal-111300623-line-5)">
-</text><text class="terminal-111300623-r4" x="48.8" y="166.4" 
textLength="73.2" 
clip-path="url(#terminal-111300623-line-6)">delete</text><text 
class="terminal-111300623-r2" x="268.4" y="166.4" textLength="292.8" 
clip-path="url(#terminal-111300623-line-6)">Perform&#160;delete&#160;operation</text><text
 class="terminal-111300623-r2" x="793" y="166.4" textLength="12.2" 
clip-path="url(#terminal-111300623-line-6)">
-</text><text class="terminal-111300623-r4" x="48.8" y="190.8" 
textLength="36.6" clip-path="url(#terminal-111300623-line-7)">get</text><text 
class="terminal-111300623-r2" x="268.4" y="190.8" textLength="256.2" 
clip-path="url(#terminal-111300623-line-7)">Perform&#160;get&#160;operation</text><text
 class="terminal-111300623-r2" x="793" y="190.8" textLength="12.2" 
clip-path="url(#terminal-111300623-line-7)">
-</text><text class="terminal-111300623-r4" x="48.8" y="215.2" 
textLength="134.2" 
clip-path="url(#terminal-111300623-line-8)">get-details</text><text 
class="terminal-111300623-r2" x="268.4" y="215.2" textLength="353.8" 
clip-path="url(#terminal-111300623-line-8)">Perform&#160;get_details&#160;operation</text><text
 class="terminal-111300623-r2" x="793" y="215.2" textLength="12.2" 
clip-path="url(#terminal-111300623-line-8)">
-</text><text class="terminal-111300623-r4" x="48.8" y="239.6" 
textLength="195.2" 
clip-path="url(#terminal-111300623-line-9)">get-import-error</text><text 
class="terminal-111300623-r2" x="268.4" y="239.6" textLength="414.8" 
clip-path="url(#terminal-111300623-line-9)">Perform&#160;get_import_error&#160;operation</text><text
 class="terminal-111300623-r2" x="793" y="239.6" textLength="12.2" 
clip-path="url(#terminal-111300623-line-9)">
-</text><text class="terminal-111300623-r4" x="48.8" y="264" textLength="109.8" 
clip-path="url(#terminal-111300623-line-10)">get-stats</text><text 
class="terminal-111300623-r2" x="268.4" y="264" textLength="329.4" 
clip-path="url(#terminal-111300623-line-10)">Perform&#160;get_stats&#160;operation</text><text
 class="terminal-111300623-r2" x="793" y="264" textLength="12.2" 
clip-path="url(#terminal-111300623-line-10)">
-</text><text class="terminal-111300623-r4" x="48.8" y="288.4" 
textLength="97.6" 
clip-path="url(#terminal-111300623-line-11)">get-tags</text><text 
class="terminal-111300623-r2" x="268.4" y="288.4" textLength="317.2" 
clip-path="url(#terminal-111300623-line-11)">Perform&#160;get_tags&#160;operation</text><text
 class="terminal-111300623-r2" x="793" y="288.4" textLength="12.2" 
clip-path="url(#terminal-111300623-line-11)">
-</text><text class="terminal-111300623-r4" x="48.8" y="312.8" 
textLength="134.2" 
clip-path="url(#terminal-111300623-line-12)">get-version</text><text 
class="terminal-111300623-r2" x="268.4" y="312.8" textLength="353.8" 
clip-path="url(#terminal-111300623-line-12)">Perform&#160;get_version&#160;operation</text><text
 class="terminal-111300623-r2" x="793" y="312.8" textLength="12.2" 
clip-path="url(#terminal-111300623-line-12)">
-</text><text class="terminal-111300623-r4" x="48.8" y="337.2" 
textLength="48.8" clip-path="url(#terminal-111300623-line-13)">list</text><text 
class="terminal-111300623-r2" x="268.4" y="337.2" textLength="268.4" 
clip-path="url(#terminal-111300623-line-13)">Perform&#160;list&#160;operation</text><text
 class="terminal-111300623-r2" x="793" y="337.2" textLength="12.2" 
clip-path="url(#terminal-111300623-line-13)">
-</text><text class="terminal-111300623-r4" x="48.8" y="361.6" 
textLength="219.6" 
clip-path="url(#terminal-111300623-line-14)">list-import-errors</text><text 
class="terminal-111300623-r2" x="793" y="361.6" textLength="12.2" 
clip-path="url(#terminal-111300623-line-14)">
-</text><text class="terminal-111300623-r2" x="268.4" y="386" 
textLength="439.2" 
clip-path="url(#terminal-111300623-line-15)">Perform&#160;list_import_errors&#160;operation</text><text
 class="terminal-111300623-r2" x="793" y="386" textLength="12.2" 
clip-path="url(#terminal-111300623-line-15)">
-</text><text class="terminal-111300623-r4" x="48.8" y="410.4" 
textLength="146.4" 
clip-path="url(#terminal-111300623-line-16)">list-version</text><text 
class="terminal-111300623-r2" x="268.4" y="410.4" textLength="366" 
clip-path="url(#terminal-111300623-line-16)">Perform&#160;list_version&#160;operation</text><text
 class="terminal-111300623-r2" x="793" y="410.4" textLength="12.2" 
clip-path="url(#terminal-111300623-line-16)">
-</text><text class="terminal-111300623-r4" x="48.8" y="434.8" 
textLength="146.4" 
clip-path="url(#terminal-111300623-line-17)">list-warning</text><text 
class="terminal-111300623-r2" x="268.4" y="434.8" textLength="366" 
clip-path="url(#terminal-111300623-line-17)">Perform&#160;list_warning&#160;operation</text><text
 class="terminal-111300623-r2" x="793" y="434.8" textLength="12.2" 
clip-path="url(#terminal-111300623-line-17)">
-</text><text class="terminal-111300623-r4" x="48.8" y="459.2" 
textLength="85.4" 
clip-path="url(#terminal-111300623-line-18)">trigger</text><text 
class="terminal-111300623-r2" x="268.4" y="459.2" textLength="305" 
clip-path="url(#terminal-111300623-line-18)">Perform&#160;trigger&#160;operation</text><text
 class="terminal-111300623-r2" x="793" y="459.2" textLength="12.2" 
clip-path="url(#terminal-111300623-line-18)">
-</text><text class="terminal-111300623-r4" x="48.8" y="483.6" 
textLength="73.2" 
clip-path="url(#terminal-111300623-line-19)">update</text><text 
class="terminal-111300623-r2" x="268.4" y="483.6" textLength="292.8" 
clip-path="url(#terminal-111300623-line-19)">Perform&#160;update&#160;operation</text><text
 class="terminal-111300623-r2" x="793" y="483.6" textLength="12.2" 
clip-path="url(#terminal-111300623-line-19)">
-</text><text class="terminal-111300623-r2" x="793" y="508" textLength="12.2" 
clip-path="url(#terminal-111300623-line-20)">
-</text><text class="terminal-111300623-r1" x="0" y="532.4" textLength="97.6" 
clip-path="url(#terminal-111300623-line-21)">Options:</text><text 
class="terminal-111300623-r2" x="793" y="532.4" textLength="12.2" 
clip-path="url(#terminal-111300623-line-21)">
-</text><text class="terminal-111300623-r4" x="24.4" y="556.8" 
textLength="24.4" clip-path="url(#terminal-111300623-line-22)">-h</text><text 
class="terminal-111300623-r2" x="48.8" y="556.8" textLength="24.4" 
clip-path="url(#terminal-111300623-line-22)">,&#160;</text><text 
class="terminal-111300623-r4" x="73.2" y="556.8" textLength="73.2" 
clip-path="url(#terminal-111300623-line-22)">--help</text><text 
class="terminal-111300623-r2" x="268.4" y="556.8" textLength="378.2" 
clip-path="url(#term [...]
+    <g class="terminal-1075761825-matrix">
+    <text class="terminal-1075761825-r1" x="0" y="20" textLength="73.2" 
clip-path="url(#terminal-1075761825-line-0)">Usage:</text><text 
class="terminal-1075761825-r3" x="85.4" y="20" textLength="183" 
clip-path="url(#terminal-1075761825-line-0)">airflowctl&#160;dags</text><text 
class="terminal-1075761825-r2" x="268.4" y="20" textLength="24.4" 
clip-path="url(#terminal-1075761825-line-0)">&#160;[</text><text 
class="terminal-1075761825-r4" x="292.8" y="20" textLength="24.4" 
clip-path="url(#t [...]
+</text><text class="terminal-1075761825-r2" x="793" y="44.4" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-1)">
+</text><text class="terminal-1075761825-r2" x="0" y="68.8" textLength="280.6" 
clip-path="url(#terminal-1075761825-line-2)">Perform&#160;Dags&#160;operations</text><text
 class="terminal-1075761825-r2" x="793" y="68.8" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-2)">
+</text><text class="terminal-1075761825-r2" x="793" y="93.2" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-3)">
+</text><text class="terminal-1075761825-r1" x="0" y="117.6" textLength="256.2" 
clip-path="url(#terminal-1075761825-line-4)">Positional&#160;Arguments:</text><text
 class="terminal-1075761825-r2" x="793" y="117.6" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-4)">
+</text><text class="terminal-1075761825-r4" x="24.4" y="142" textLength="85.4" 
clip-path="url(#terminal-1075761825-line-5)">COMMAND</text><text 
class="terminal-1075761825-r2" x="793" y="142" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-5)">
+</text><text class="terminal-1075761825-r4" x="48.8" y="166.4" 
textLength="73.2" 
clip-path="url(#terminal-1075761825-line-6)">delete</text><text 
class="terminal-1075761825-r2" x="268.4" y="166.4" textLength="292.8" 
clip-path="url(#terminal-1075761825-line-6)">Perform&#160;delete&#160;operation</text><text
 class="terminal-1075761825-r2" x="793" y="166.4" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-6)">
+</text><text class="terminal-1075761825-r4" x="48.8" y="190.8" 
textLength="36.6" clip-path="url(#terminal-1075761825-line-7)">get</text><text 
class="terminal-1075761825-r2" x="268.4" y="190.8" textLength="256.2" 
clip-path="url(#terminal-1075761825-line-7)">Perform&#160;get&#160;operation</text><text
 class="terminal-1075761825-r2" x="793" y="190.8" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-7)">
+</text><text class="terminal-1075761825-r4" x="48.8" y="215.2" 
textLength="134.2" 
clip-path="url(#terminal-1075761825-line-8)">get-details</text><text 
class="terminal-1075761825-r2" x="268.4" y="215.2" textLength="353.8" 
clip-path="url(#terminal-1075761825-line-8)">Perform&#160;get_details&#160;operation</text><text
 class="terminal-1075761825-r2" x="793" y="215.2" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-8)">
+</text><text class="terminal-1075761825-r4" x="48.8" y="239.6" 
textLength="195.2" 
clip-path="url(#terminal-1075761825-line-9)">get-import-error</text><text 
class="terminal-1075761825-r2" x="268.4" y="239.6" textLength="414.8" 
clip-path="url(#terminal-1075761825-line-9)">Perform&#160;get_import_error&#160;operation</text><text
 class="terminal-1075761825-r2" x="793" y="239.6" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-9)">
+</text><text class="terminal-1075761825-r4" x="48.8" y="264" 
textLength="109.8" 
clip-path="url(#terminal-1075761825-line-10)">get-stats</text><text 
class="terminal-1075761825-r2" x="268.4" y="264" textLength="329.4" 
clip-path="url(#terminal-1075761825-line-10)">Perform&#160;get_stats&#160;operation</text><text
 class="terminal-1075761825-r2" x="793" y="264" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-10)">
+</text><text class="terminal-1075761825-r4" x="48.8" y="288.4" 
textLength="97.6" 
clip-path="url(#terminal-1075761825-line-11)">get-tags</text><text 
class="terminal-1075761825-r2" x="268.4" y="288.4" textLength="317.2" 
clip-path="url(#terminal-1075761825-line-11)">Perform&#160;get_tags&#160;operation</text><text
 class="terminal-1075761825-r2" x="793" y="288.4" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-11)">
+</text><text class="terminal-1075761825-r4" x="48.8" y="312.8" 
textLength="134.2" 
clip-path="url(#terminal-1075761825-line-12)">get-version</text><text 
class="terminal-1075761825-r2" x="268.4" y="312.8" textLength="353.8" 
clip-path="url(#terminal-1075761825-line-12)">Perform&#160;get_version&#160;operation</text><text
 class="terminal-1075761825-r2" x="793" y="312.8" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-12)">
+</text><text class="terminal-1075761825-r4" x="48.8" y="337.2" 
textLength="48.8" 
clip-path="url(#terminal-1075761825-line-13)">list</text><text 
class="terminal-1075761825-r2" x="268.4" y="337.2" textLength="268.4" 
clip-path="url(#terminal-1075761825-line-13)">Perform&#160;list&#160;operation</text><text
 class="terminal-1075761825-r2" x="793" y="337.2" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-13)">
+</text><text class="terminal-1075761825-r4" x="48.8" y="361.6" 
textLength="219.6" 
clip-path="url(#terminal-1075761825-line-14)">list-import-errors</text><text 
class="terminal-1075761825-r2" x="793" y="361.6" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-14)">
+</text><text class="terminal-1075761825-r2" x="268.4" y="386" 
textLength="439.2" 
clip-path="url(#terminal-1075761825-line-15)">Perform&#160;list_import_errors&#160;operation</text><text
 class="terminal-1075761825-r2" x="793" y="386" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-15)">
+</text><text class="terminal-1075761825-r4" x="48.8" y="410.4" 
textLength="146.4" 
clip-path="url(#terminal-1075761825-line-16)">list-version</text><text 
class="terminal-1075761825-r2" x="268.4" y="410.4" textLength="366" 
clip-path="url(#terminal-1075761825-line-16)">Perform&#160;list_version&#160;operation</text><text
 class="terminal-1075761825-r2" x="793" y="410.4" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-16)">
+</text><text class="terminal-1075761825-r4" x="48.8" y="434.8" 
textLength="146.4" 
clip-path="url(#terminal-1075761825-line-17)">list-warning</text><text 
class="terminal-1075761825-r2" x="268.4" y="434.8" textLength="366" 
clip-path="url(#terminal-1075761825-line-17)">Perform&#160;list_warning&#160;operation</text><text
 class="terminal-1075761825-r2" x="793" y="434.8" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-17)">
+</text><text class="terminal-1075761825-r4" x="48.8" y="459.2" textLength="61" 
clip-path="url(#terminal-1075761825-line-18)">pause</text><text 
class="terminal-1075761825-r2" x="268.4" y="459.2" textLength="134.2" 
clip-path="url(#terminal-1075761825-line-18)">Pause&#160;a&#160;Dag</text><text 
class="terminal-1075761825-r2" x="793" y="459.2" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-18)">
+</text><text class="terminal-1075761825-r4" x="48.8" y="483.6" 
textLength="85.4" 
clip-path="url(#terminal-1075761825-line-19)">trigger</text><text 
class="terminal-1075761825-r2" x="268.4" y="483.6" textLength="305" 
clip-path="url(#terminal-1075761825-line-19)">Perform&#160;trigger&#160;operation</text><text
 class="terminal-1075761825-r2" x="793" y="483.6" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-19)">
+</text><text class="terminal-1075761825-r4" x="48.8" y="508" textLength="85.4" 
clip-path="url(#terminal-1075761825-line-20)">unpause</text><text 
class="terminal-1075761825-r2" x="268.4" y="508" textLength="158.6" 
clip-path="url(#terminal-1075761825-line-20)">Unpause&#160;a&#160;Dag</text><text
 class="terminal-1075761825-r2" x="793" y="508" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-20)">
+</text><text class="terminal-1075761825-r4" x="48.8" y="532.4" 
textLength="73.2" 
clip-path="url(#terminal-1075761825-line-21)">update</text><text 
class="terminal-1075761825-r2" x="268.4" y="532.4" textLength="292.8" 
clip-path="url(#terminal-1075761825-line-21)">Perform&#160;update&#160;operation</text><text
 class="terminal-1075761825-r2" x="793" y="532.4" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-21)">
+</text><text class="terminal-1075761825-r2" x="793" y="556.8" 
textLength="12.2" clip-path="url(#terminal-1075761825-line-22)">
+</text><text class="terminal-1075761825-r1" x="0" y="581.2" textLength="97.6" 
clip-path="url(#terminal-1075761825-line-23)">Options:</text><text 
class="terminal-1075761825-r2" x="793" y="581.2" textLength="12.2" 
clip-path="url(#terminal-1075761825-line-23)">
+</text><text class="terminal-1075761825-r4" x="24.4" y="605.6" 
textLength="24.4" clip-path="url(#terminal-1075761825-line-24)">-h</text><text 
class="terminal-1075761825-r2" x="48.8" y="605.6" textLength="24.4" 
clip-path="url(#terminal-1075761825-line-24)">,&#160;</text><text 
class="terminal-1075761825-r4" x="73.2" y="605.6" textLength="73.2" 
clip-path="url(#terminal-1075761825-line-24)">--help</text><text 
class="terminal-1075761825-r2" x="268.4" y="605.6" textLength="378.2" 
clip-path="ur [...]
 </text>
     </g>
     </g>
diff --git a/airflow-ctl/src/airflowctl/api/client.py 
b/airflow-ctl/src/airflowctl/api/client.py
index 355304ae37a..9e138ffc87b 100644
--- a/airflow-ctl/src/airflowctl/api/client.py
+++ b/airflow-ctl/src/airflowctl/api/client.py
@@ -73,6 +73,7 @@ __all__ = [
     "provide_api_client",
     "NEW_API_CLIENT",
     "ClientKind",
+    "ServerResponseError",
 ]
 
 PS = ParamSpec("PS")
diff --git a/airflow-ctl/src/airflowctl/ctl/cli_config.py 
b/airflow-ctl/src/airflowctl/ctl/cli_config.py
index b4b283c658a..da548530eb0 100644
--- a/airflow-ctl/src/airflowctl/ctl/cli_config.py
+++ b/airflow-ctl/src/airflowctl/ctl/cli_config.py
@@ -252,6 +252,14 @@ ARG_AUTH_PASSWORD = Arg(
     nargs="?",
 )
 
+# Dag Commands Args
+ARG_DAG_ID = Arg(
+    flags=("--dag-id",),
+    type=str,
+    dest="dag_id",
+    help="The DAG ID of the DAG to pause or unpause",
+)
+
 # Variable Commands Args
 ARG_VARIABLE_ACTION_ON_EXISTING_KEY = Arg(
     flags=("-a", "--action-on-existing-key"),
@@ -804,6 +812,27 @@ CONNECTION_COMMANDS = (
     ),
 )
 
+DAG_COMMANDS = (
+    ActionCommand(
+        name="pause",
+        help="Pause a Dag",
+        func=lazy_load_command("airflowctl.ctl.commands.dag_command.pause"),
+        args=(
+            ARG_DAG_ID,
+            ARG_OUTPUT,
+        ),
+    ),
+    ActionCommand(
+        name="unpause",
+        help="Unpause a Dag",
+        func=lazy_load_command("airflowctl.ctl.commands.dag_command.unpause"),
+        args=(
+            ARG_DAG_ID,
+            ARG_OUTPUT,
+        ),
+    ),
+)
+
 POOL_COMMANDS = (
     ActionCommand(
         name="import",
@@ -854,6 +883,11 @@ core_commands: list[CLICommand] = [
         help="Manage Airflow connections",
         subcommands=CONNECTION_COMMANDS,
     ),
+    GroupCommand(
+        name="dags",
+        help="Manage Airflow Dags",
+        subcommands=DAG_COMMANDS,
+    ),
     GroupCommand(
         name="pools",
         help="Manage Airflow pools",
diff --git a/airflow-ctl/src/airflowctl/ctl/commands/dag_command.py 
b/airflow-ctl/src/airflowctl/ctl/commands/dag_command.py
new file mode 100644
index 00000000000..9b43be47eb2
--- /dev/null
+++ b/airflow-ctl/src/airflowctl/ctl/commands/dag_command.py
@@ -0,0 +1,74 @@
+# 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.
+
+from __future__ import annotations
+
+import sys
+from typing import Literal
+
+import rich
+
+from airflowctl.api.client import NEW_API_CLIENT, ClientKind, 
ServerResponseError, provide_api_client
+from airflowctl.api.datamodels.generated import DAGPatchBody
+from airflowctl.ctl.console_formatting import AirflowConsole
+
+
+def update_dag_state(
+    dag_id: str,
+    operation: Literal["pause", "unpause"],
+    api_client,
+    output: str,
+):
+    """Update DAG state (pause/unpause)."""
+    try:
+        response = api_client.dags.update(
+            dag_id=dag_id, dag_body=DAGPatchBody(is_paused=operation == 
"pause")
+        )
+    except ServerResponseError as e:
+        rich.print(f"[red]Error while trying to {operation} Dag {dag_id}: 
{e}[/red]")
+        sys.exit(1)
+
+    response_dict = response.model_dump()
+    rich.print(f"[green]Dag {operation} successful {dag_id}[/green]")
+    rich.print("[green]Further Dag details:[/green]")
+    AirflowConsole().print_as(
+        data=[response_dict],
+        output=output,
+    )
+    return response_dict
+
+
+@provide_api_client(kind=ClientKind.CLI)
+def pause(args, api_client=NEW_API_CLIENT) -> None:
+    """Pause a DAG."""
+    return update_dag_state(
+        dag_id=args.dag_id,
+        operation="pause",
+        api_client=api_client,
+        output=args.output,
+    )
+
+
+@provide_api_client(kind=ClientKind.CLI)
+def unpause(args, api_client=NEW_API_CLIENT) -> None:
+    """Unpause a DAG."""
+    return update_dag_state(
+        dag_id=args.dag_id,
+        operation="unpause",
+        api_client=api_client,
+        output=args.output,
+    )
diff --git a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_config_command.py 
b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_config_command.py
index cd5af73605c..f87a9851a52 100644
--- a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_config_command.py
+++ b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_config_command.py
@@ -26,7 +26,7 @@ from airflowctl.ctl.commands import config_command
 from airflowctl.ctl.commands.config_command import ConfigChange, 
ConfigParameter
 
 
-class TestCliConfigLint:
+class TestCliConfigCommands:
     parser = cli_parser.get_parser()
 
     @patch("rich.print")
diff --git a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_dag_command.py 
b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_dag_command.py
new file mode 100644
index 00000000000..98fb318024b
--- /dev/null
+++ b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_dag_command.py
@@ -0,0 +1,139 @@
+# 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.
+from __future__ import annotations
+
+import datetime
+
+import pytest
+
+from airflowctl.api.client import ClientKind
+from airflowctl.api.datamodels.generated import DAGResponse
+from airflowctl.ctl import cli_parser
+from airflowctl.ctl.commands import dag_command
+
+
+class TestDagCommands:
+    parser = cli_parser.get_parser()
+    dag_id = "test_dag"
+    dag_display_name = "dag_display_name"
+    dag_response_paused = DAGResponse(
+        dag_id=dag_id,
+        dag_display_name=dag_display_name,
+        is_paused=False,
+        last_parsed_time=datetime.datetime(2024, 12, 31, 23, 59, 59),
+        last_expired=datetime.datetime(2025, 1, 1, 0, 0, 0),
+        fileloc="fileloc",
+        relative_fileloc="relative_fileloc",
+        description="description",
+        timetable_summary="timetable_summary",
+        timetable_description="timetable_description",
+        tags=[],
+        max_active_tasks=1,
+        max_active_runs=1,
+        max_consecutive_failed_dag_runs=1,
+        has_task_concurrency_limits=True,
+        has_import_errors=True,
+        next_dagrun_logical_date=datetime.datetime(2025, 1, 1, 0, 0, 0),
+        next_dagrun_data_interval_start=datetime.datetime(2025, 1, 1, 0, 0, 0),
+        next_dagrun_data_interval_end=datetime.datetime(2025, 1, 1, 0, 0, 0),
+        next_dagrun_run_after=datetime.datetime(2025, 1, 1, 0, 0, 0),
+        owners=["apache-airflow"],
+        file_token="file_token",
+        bundle_name="bundle_name",
+        is_stale=False,
+    )
+
+    dag_response_unpaused = DAGResponse(
+        dag_id=dag_id,
+        dag_display_name=dag_display_name,
+        is_paused=True,
+        last_parsed_time=datetime.datetime(2024, 12, 31, 23, 59, 59),
+        last_expired=datetime.datetime(2025, 1, 1, 0, 0, 0),
+        fileloc="fileloc",
+        relative_fileloc="relative_fileloc",
+        description="description",
+        timetable_summary="timetable_summary",
+        timetable_description="timetable_description",
+        tags=[],
+        max_active_tasks=1,
+        max_active_runs=1,
+        max_consecutive_failed_dag_runs=1,
+        has_task_concurrency_limits=True,
+        has_import_errors=True,
+        next_dagrun_logical_date=datetime.datetime(2025, 1, 1, 0, 0, 0),
+        next_dagrun_data_interval_start=datetime.datetime(2025, 1, 1, 0, 0, 0),
+        next_dagrun_data_interval_end=datetime.datetime(2025, 1, 1, 0, 0, 0),
+        next_dagrun_run_after=datetime.datetime(2025, 1, 1, 0, 0, 0),
+        owners=["apache-airflow"],
+        file_token="file_token",
+        bundle_name="bundle_name",
+        is_stale=False,
+    )
+
+    def test_pause_dag(self, api_client_maker, monkeypatch):
+        api_client = api_client_maker(
+            path=f"/api/v2/dags/{self.dag_id}",
+            response_json=self.dag_response_paused.model_dump(mode="json"),
+            expected_http_status_code=200,
+            kind=ClientKind.CLI,
+        )
+        assert self.dag_response_paused.is_paused is False
+        dag_response_dict = dag_command.pause(
+            self.parser.parse_args(["dags", "pause", "--dag-id", self.dag_id]),
+            api_client=api_client,
+        )
+        assert dag_response_dict["is_paused"] is False
+
+    def test_pause_fail(self, api_client_maker, monkeypatch):
+        api_client = api_client_maker(
+            path=f"/api/v2/dags/{self.dag_id}",
+            response_json={"detail": "DAG not found"},
+            expected_http_status_code=404,
+            kind=ClientKind.CLI,
+        )
+        with pytest.raises(SystemExit):
+            dag_command.pause(
+                self.parser.parse_args(["dags", "pause", "--dag-id", 
self.dag_id]),
+                api_client=api_client,
+            )
+
+    def test_unpause_dag(self, api_client_maker, monkeypatch):
+        api_client = api_client_maker(
+            path=f"/api/v2/dags/{self.dag_id}",
+            response_json=self.dag_response_unpaused.model_dump(mode="json"),
+            expected_http_status_code=200,
+            kind=ClientKind.CLI,
+        )
+        assert self.dag_response_unpaused.is_paused is True
+        dag_response_dict = dag_command.unpause(
+            self.parser.parse_args(["dags", "unpause", "--dag-id", 
self.dag_id]),
+            api_client=api_client,
+        )
+        assert dag_response_dict["is_paused"] is True
+
+    def test_unpause_fail(self, api_client_maker, monkeypatch):
+        api_client = api_client_maker(
+            path=f"/api/v2/dags/{self.dag_id}",
+            response_json={"detail": "DAG not found"},
+            expected_http_status_code=404,
+            kind=ClientKind.CLI,
+        )
+        with pytest.raises(SystemExit):
+            dag_command.unpause(
+                self.parser.parse_args(["dags", "unpause", "--dag-id", 
self.dag_id]),
+                api_client=api_client,
+            )


Reply via email to