This is an automated email from the ASF dual-hosted git repository.
husseinawala 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 d8f647cdef [#38284] Update Confirmation Logic for Config Changes on
Sensitive Environments Like Production (#38299)
d8f647cdef is described below
commit d8f647cdef5aa526cbf3732aa47e03817b476f83
Author: Nguyễn Anh Bình <[email protected]>
AuthorDate: Wed Mar 20 21:42:26 2024 +0700
[#38284] Update Confirmation Logic for Config Changes on Sensitive
Environments Like Production (#38299)
* - [#38284] Update Confirmation Logic for Dag status Changes on Sensitive
Environments Like Production
* - Update pause/unpause confirm messages
* - Not version added yet
* - Fix static check
* - Ruff check
* Revert "- Fix static check"
This reverts commit cccfeb757ab1d6f8ac35a6730ae6cc1c7a9a2edc.
* - Fix ruff check
---
airflow/config_templates/config.yml | 9 +++++++++
airflow/www/app.py | 4 ++++
airflow/www/extensions/init_appbuilder.py | 13 +++++++++++++
airflow/www/static/js/dag.js | 10 ++++++++++
airflow/www/templates/airflow/dag.html | 12 +++++++++++-
5 files changed, 47 insertions(+), 1 deletion(-)
diff --git a/airflow/config_templates/config.yml
b/airflow/config_templates/config.yml
index 2fc72cbb2d..0a1b292fdc 100644
--- a/airflow/config_templates/config.yml
+++ b/airflow/config_templates/config.yml
@@ -1943,6 +1943,15 @@ webserver:
type: float
example: ~
default: "1.0"
+ require_confirmation_dag_change:
+ description: |
+ Require confirmation when changing a DAG in the web UI. This is to
prevent accidental changes
+ to a DAG that may be running on sensitive environments like production.
+ When set to True, confirmation dialog will be shown when a user tries
to Pause/Unpause, Trigger a DAG
+ version_added: ~
+ type: boolean
+ example: ~
+ default: "False"
email:
description: |
Configuration email backend and whether to
diff --git a/airflow/www/app.py b/airflow/www/app.py
index 749efe8912..17a4c681dd 100644
--- a/airflow/www/app.py
+++ b/airflow/www/app.py
@@ -86,6 +86,9 @@ def create_app(config=None, testing=False):
flask_app.config["SQLALCHEMY_DATABASE_URI"] = conf.get("database",
"SQL_ALCHEMY_CONN")
instance_name = conf.get(section="webserver", key="instance_name",
fallback="Airflow")
+ require_confirmation_dag_change = conf.getboolean(
+ section="webserver", key="require_confirmation_dag_change",
fallback=False
+ )
instance_name_has_markup = conf.getboolean(
section="webserver", key="instance_name_has_markup", fallback=False
)
@@ -93,6 +96,7 @@ def create_app(config=None, testing=False):
instance_name = Markup(instance_name).striptags()
flask_app.config["APP_NAME"] = instance_name
+ flask_app.config["REQUIRE_CONFIRMATION_DAG_CHANGE"] =
require_confirmation_dag_change
url = make_url(flask_app.config["SQLALCHEMY_DATABASE_URI"])
if url.drivername == "sqlite" and url.database and not
url.database.startswith("/"):
diff --git a/airflow/www/extensions/init_appbuilder.py
b/airflow/www/extensions/init_appbuilder.py
index ed972af271..7bb71ba980 100644
--- a/airflow/www/extensions/init_appbuilder.py
+++ b/airflow/www/extensions/init_appbuilder.py
@@ -293,6 +293,19 @@ class AirflowAppBuilder:
"""
return self.get_app.config["APP_NAME"]
+ @property
+ def require_confirmation_dag_change(self):
+ """Get the value of the require_confirmation_dag_change configuration.
+
+ The logic is:
+ - return True, in page dag.html, when user trigger/pause the dag from
UI.
+ Once confirmation box will be shown before triggering the dag.
+ - Default value is False.
+
+ :return: Boolean
+ """
+ return self.get_app.config["REQUIRE_CONFIRMATION_DAG_CHANGE"]
+
@property
def app_theme(self):
"""
diff --git a/airflow/www/static/js/dag.js b/airflow/www/static/js/dag.js
index 39a26f28e6..b70587a70c 100644
--- a/airflow/www/static/js/dag.js
+++ b/airflow/www/static/js/dag.js
@@ -116,6 +116,16 @@ $("#pause_resume").on("change", function onChange() {
const $input = $(this);
const id = $input.data("dag-id");
const isPaused = $input.is(":checked");
+ const requireConfirmation = $input.data("require-confirmation");
+ if (requireConfirmation) {
+ const confirmation = window.confirm(
+ `Are you sure you want to ${isPaused ? "resume" : "pause"} this DAG?`
+ );
+ if (!confirmation) {
+ $input.prop("checked", !isPaused);
+ return;
+ }
+ }
const url = `${pausedUrl}?is_paused=${isPaused}&dag_id=${encodeURIComponent(
id
)}`;
diff --git a/airflow/www/templates/airflow/dag.html
b/airflow/www/templates/airflow/dag.html
index 6f5a187f61..a09fa7ffa6 100644
--- a/airflow/www/templates/airflow/dag.html
+++ b/airflow/www/templates/airflow/dag.html
@@ -123,6 +123,7 @@
{% endif %}
<label class="switch-label{{' disabled' if not can_edit_dag else ''
}} js-tooltip" title="{{ switch_tooltip }}">
<input class="switch-input" id="pause_resume" data-dag-id="{{
dag.dag_id }}"
+ data-require-confirmation="{{
appbuilder.require_confirmation_dag_change }}"
type="checkbox"{{ " checked" if not dag_is_paused else "" }}
{{ " disabled" if not can_edit_dag else "" }}>
<span class="switch" aria-hidden="true"></span>
@@ -261,7 +262,8 @@
{% else %}
<a href="{{ url_for('Airflow.trigger', dag_id=dag.dag_id,
origin=url_for(request.endpoint, dag_id=dag.dag_id, **request.args)) }}"
{% endif %}
- onclick="return triggerDag(this, '{{ dag.dag_id }}')"
title="Trigger DAG"
+ onclick="return {{ 'triggerDag' if not
appbuilder.require_confirmation_dag_change else 'confirmTriggerDag' }}(this,
'{{ dag.dag_id }}')"
+ title="Trigger DAG"
aria-label="Trigger DAG"
class="btn btn-default btn-icon-only{{ ' disabled' if not
dag.can_trigger }} trigger-dropdown-btn">
<span class="material-icons" aria-hidden="true">play_arrow</span>
@@ -301,5 +303,13 @@
postAsForm(link.href, {});
return false;
}
+
+ function confirmTriggerDag(link, dagId) {
+ if (confirm(`Are you sure you want to trigger '${dagId}' now?`)) {
+ postAsForm(link.href, {});
+ }
+ return false;
+ }
+
</script>
{% endblock %}