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

ephraimanierobi pushed a commit to branch v2-8-test
in repository https://gitbox.apache.org/repos/asf/airflow.git

commit 26d5e3f4e72c349b9f041fbb11f2e4791e6da1db
Author: Jarek Potiuk <[email protected]>
AuthorDate: Tue Nov 21 10:30:50 2023 +0100

    Improve ownership fixing for Breeze (#35759)
    
    When we run airflow package preparation in docker containers and
    asset compilation, we use docker containers - similarly as breeze
    CI image, those constainers use root user in container. This means
    that the files created by those containers are owned by root on
    Linux - which means that we should change ownership of these files
    after they were generated.
    
    Used the opportunity to rewrtie "fix_ownership" part to Python
    and remove bash scripts used for it so far.
---
 .github/actions/build-ci-images/action.yml         |  4 --
 .github/actions/build-prod-images/action.yml       |  4 --
 .github/actions/post_tests_failure/action.yml      |  3 -
 .github/actions/post_tests_success/action.yml      |  3 -
 .github/workflows/build-images.yml                 |  3 -
 .github/workflows/ci.yml                           | 53 --------------
 .github/workflows/release_dockerhub_image.yml      |  3 -
 Dockerfile.ci                                      |  2 -
 ...-root-ownership-after-exiting-docker-command.md | 60 ++++++++++++++++
 .../airflow_breeze/commands/developer_commands.py  |  7 ++
 .../commands/release_management_commands.py        | 21 ++++++
 .../airflow_breeze/commands/testing_commands.py    |  5 ++
 .../airflow_breeze/utils/docker_command_utils.py   | 17 +++--
 dev/breeze/src/airflow_breeze/utils/parallel.py    |  6 ++
 dev/breeze/src/airflow_breeze/utils/run_utils.py   |  2 +-
 scripts/docker/entrypoint_ci.sh                    |  2 -
 scripts/in_container/_in_container_utils.sh        | 40 -----------
 scripts/in_container/run_fix_ownership.py          | 84 ++++++++++++++++++++++
 scripts/in_container/run_fix_ownership.sh          | 21 ------
 .../in_container/run_prepare_airflow_packages.py   | 12 ++--
 20 files changed, 201 insertions(+), 151 deletions(-)

diff --git a/.github/actions/build-ci-images/action.yml 
b/.github/actions/build-ci-images/action.yml
index d8c0216bb4..f950c53b4e 100644
--- a/.github/actions/build-ci-images/action.yml
+++ b/.github/actions/build-ci-images/action.yml
@@ -45,7 +45,3 @@ runs:
         name: source-constraints
         path: ./files/constraints-*/constraints-*.txt
         retention-days: 7
-    - name: "Fix ownership"
-      shell: bash
-      run: breeze ci fix-ownership
-      if: always()
diff --git a/.github/actions/build-prod-images/action.yml 
b/.github/actions/build-prod-images/action.yml
index 7572153169..5fdbb795c4 100644
--- a/.github/actions/build-prod-images/action.yml
+++ b/.github/actions/build-prod-images/action.yml
@@ -72,7 +72,3 @@ runs:
       env:
         COMMIT_SHA: ${{ github.sha }}
       if: ${{ inputs.build-provider-packages != 'true' }}
-    - name: "Fix ownership"
-      shell: bash
-      run: breeze ci fix-ownership
-      if: always()
diff --git a/.github/actions/post_tests_failure/action.yml 
b/.github/actions/post_tests_failure/action.yml
index 96e43bbe0e..5b51db97ed 100644
--- a/.github/actions/post_tests_failure/action.yml
+++ b/.github/actions/post_tests_failure/action.yml
@@ -39,6 +39,3 @@ runs:
         name: container-logs-${{env.JOB_ID}}
         path: "./files/other_logs*"
         retention-days: 7
-    - name: "Fix ownership"
-      shell: bash
-      run: breeze ci fix-ownership
diff --git a/.github/actions/post_tests_success/action.yml 
b/.github/actions/post_tests_success/action.yml
index 1325c5dde3..ac1e4fb8d2 100644
--- a/.github/actions/post_tests_success/action.yml
+++ b/.github/actions/post_tests_success/action.yml
@@ -40,6 +40,3 @@ runs:
         name: coverage-${{env.JOB_ID}}
         flags: 
python-${{env.PYTHON_MAJOR_MINOR_VERSION}},${{env.BACKEND}}-${{env.BACKEND_VERSION}}
         directory: "./files/coverage-reposts/"
-    - name: "Fix ownership"
-      shell: bash
-      run: breeze ci fix-ownership
diff --git a/.github/workflows/build-images.yml 
b/.github/workflows/build-images.yml
index 82498ebde0..b29d49de17 100644
--- a/.github/workflows/build-images.yml
+++ b/.github/workflows/build-images.yml
@@ -362,6 +362,3 @@ jobs:
       - name: "Stop ARM instance"
         run: ./scripts/ci/images/ci_stop_arm_instance.sh
         if: always()
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: always()
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index da4dbfde53..45e0a9fca0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -260,11 +260,6 @@ jobs:
         if: >
           matrix.platform == 'linux/amd64' && 
needs.build-info.outputs.canary-run == 'true'
           && needs.build-info.outputs.default-branch == 'main'
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: >
-          always() && needs.build-info.outputs.canary-run == 'true'
-          && needs.build-info.outputs.default-branch == 'main'
   # Check that after earlier cache push, breeze command will build quickly
   check-that-image-builds-quickly:
     timeout-minutes: 5
@@ -291,9 +286,6 @@ jobs:
         uses: ./.github/actions/breeze
       - name: "Check that image builds quickly"
         run: breeze shell --max-time 120
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: always()
 
   build-ci-images:
     timeout-minutes: 80
@@ -385,9 +377,6 @@ jobs:
           name: constraints
           path: ./files/constraints-*/constraints-*.txt
           retention-days: 7
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: always()
 
   build-prod-images:
     timeout-minutes: 80
@@ -603,9 +592,6 @@ jobs:
         env:
           PYTHON_VERSIONS: ${{ 
needs.build-info.outputs.python-versions-list-as-string }}
           DEBUG_RESOURCES: ${{needs.build-info.outputs.debug-resources}}
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: always()
 
   static-checks:
     timeout-minutes: 45
@@ -645,9 +631,6 @@ jobs:
           SKIP_GROUP_OUTPUT: "true"
           DEFAULT_BRANCH: ${{ needs.build-info.outputs.default-branch }}
           RUFF_FORMAT: "github"
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: always()
 
   # Those checks are run if no image needs to be built for checks. This is for 
simple changes that
   # Do not touch any of the python code or any of the important files that 
might require building
@@ -701,9 +684,6 @@ jobs:
           SKIP_IMAGE_PRE_COMMITS: "true"
           SKIP: ${{ needs.build-info.outputs.skip-pre-commits }}
           COLUMNS: "250"
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: always()
 
   build-docs:
     timeout-minutes: 60
@@ -768,9 +748,6 @@ jobs:
       - name: "Upload documentation to AWS S3"
         if: needs.build-info.outputs.canary-run == 'true' && 
needs.build-info.outputs.default-branch == 'main'
         run: aws s3 sync --delete ./files/documentation 
s3://apache-airflow-docs
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: always()
 
   spellcheck-docs:
     timeout-minutes: 60
@@ -802,9 +779,6 @@ jobs:
       - name: "Spellcheck docs"
         run: >
           breeze build-docs ${{ needs.build-info.outputs.docs-list-as-string 
}} --spellcheck-only
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: always()
 
   prepare-test-provider-packages-wheel:
     timeout-minutes: 80
@@ -857,9 +831,6 @@ jobs:
         if: needs.build-info.outputs.affected-providers-list-as-string != ''
         env:
           SKIP_CONSTRAINTS: "${{ 
needs.build-info.outputs.upgrade-to-newer-dependencies }}"
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: always()
 
   provider-airflow-compatibility-check:
     timeout-minutes: 80
@@ -925,9 +896,6 @@ jobs:
         if: >
           needs.build-info.outputs.affected-providers-list-as-string != '' &&
           needs.build-info.outputs.affected-providers-list-as-string != 
'openlineage'
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: always()
 
   prepare-install-provider-packages-sdist:
     timeout-minutes: 80
@@ -974,9 +942,6 @@ jobs:
           breeze release-management install-provider-packages
           --package-format sdist --use-airflow-version sdist --run-in-parallel
         if: needs.build-info.outputs.affected-providers-list-as-string != ''
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: always()
 
 
   test-airflow-release-commands:
@@ -1012,9 +977,6 @@ jobs:
       - name: "Check Airflow release process command"
         run: |
           breeze release-management start-release --release-candidate 2.4.3rc1 
--previous-release 2.4.2 -a y
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: always()
 
   tests-helm:
     timeout-minutes: 80
@@ -1717,9 +1679,6 @@ jobs:
         env:
           PYTHON_VERSIONS: ${{ 
needs.build-info.outputs.python-versions-list-as-string }}
           DEBUG_RESOURCES: ${{needs.build-info.outputs.debug-resources}}
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: always()
 
 
   test-docker-compose-quick-start:
@@ -1746,9 +1705,6 @@ jobs:
           pull-image-type: 'PROD'
       - name: "Test docker-compose quick start"
         run: breeze testing docker-compose-tests
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: always()
 
   tests-kubernetes:
     timeout-minutes: 240
@@ -1816,9 +1772,6 @@ jobs:
       - name: "Delete clusters just in case they are left"
         run: breeze k8s delete-cluster --all
         if: always()
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: always()
 
   update-constraints:
     permissions:
@@ -1974,9 +1927,6 @@ jobs:
       - name: "Stop ARM instance"
         run: ./scripts/ci/images/ci_stop_arm_instance.sh
         if: always() && matrix.platform == 'linux/arm64'
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: always()
 
   # This is only a check if ARM images are successfully building when 
committer runs PR from
   # Apache repository. This is needed in case you want to fix failing cache 
job in "canary" run
@@ -2033,6 +1983,3 @@ jobs:
       - name: "Stop ARM instance"
         run: ./scripts/ci/images/ci_stop_arm_instance.sh
         if: always()
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: always()
diff --git a/.github/workflows/release_dockerhub_image.yml 
b/.github/workflows/release_dockerhub_image.yml
index 02d7d6fcc0..6889539387 100644
--- a/.github/workflows/release_dockerhub_image.yml
+++ b/.github/workflows/release_dockerhub_image.yml
@@ -150,6 +150,3 @@ jobs:
       - name: "Docker logout"
         run: docker logout
         if: always()
-      - name: "Fix ownership"
-        run: breeze ci fix-ownership
-        if: always()
diff --git a/Dockerfile.ci b/Dockerfile.ci
index 45a1166070..81a9d424a0 100644
--- a/Dockerfile.ci
+++ b/Dockerfile.ci
@@ -998,8 +998,6 @@ if [[ ${SKIP_ENVIRONMENT_INITIALIZATION=} != "true" ]]; then
     touch /usr/lib/google-cloud-sdk/bin/gcloud
     ln -s -f /usr/bin/gcloud /usr/lib/google-cloud-sdk/bin/gcloud
 
-    in_container_fix_ownership
-
     if [[ ${SKIP_SSH_SETUP="false"} == "false" ]]; then
         # Set up ssh keys
         echo 'yes' | ssh-keygen -t rsa -C [email protected] -m PEM -P 
'' -f ~/.ssh/id_rsa \
diff --git 
a/dev/breeze/doc/adr/0014-fix-root-ownership-after-exiting-docker-command.md 
b/dev/breeze/doc/adr/0014-fix-root-ownership-after-exiting-docker-command.md
new file mode 100644
index 0000000000..5ca0c4681a
--- /dev/null
+++ b/dev/breeze/doc/adr/0014-fix-root-ownership-after-exiting-docker-command.md
@@ -0,0 +1,60 @@
+<!--
+ 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.
+ -->
+
+<!-- START doctoc generated TOC please keep comment here to allow auto update 
-->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+**Table of Contents**  *generated with 
[DocToc](https://github.com/thlorenz/doctoc)*
+
+- [14. Fix root ownership after exiting docker 
command](#14-fix-root-ownership-after-exiting-docker-command)
+  - [Status](#status)
+  - [Context](#context)
+  - [Decision](#decision)
+  - [Consequences](#consequences)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+# 14. Fix root ownership after exiting docker command
+
+Date: 2023-11-20
+
+## Status
+
+Accepted
+
+Builds on [6. Using root user and fixing ownership 
for-ci-container](0006-using-root-user-and-fixing-ownership-for-ci-container.md)
+
+## Context
+
+As discussed in [6. Using root user and fixing ownership 
for-ci-container](0006-using-root-user-and-fixing-ownership-for-ci-container.md)
+we run Breeze CI container as root user. We used to use TRAP to fix the 
ownership of files created in
+the container by root user. However, this is not fool-proof and using TRAP has 
its caveats (for example
+it can be overridden by another TRAP command). Also TRAP might be invalidated 
by signal handling
+or abrupt killing the docker container. Running the cleanup command after 
docker command completed
+seems to do be much more robust solution.
+
+## Decision
+
+Instead of running the command as TRAP when exiting we simply attempt to clean 
the ownership whenever
+we exit the container using `breeze`, `breeze shell`, `tests` or `breeze 
start-airflow` commands that run
+commands in the CI image.
+
+## Consequences
+
+Users running Breeze on Linux will have less problems with root owned files 
and we can also remove
+dedicated `ci fix-ownership` command in CI.
diff --git a/dev/breeze/src/airflow_breeze/commands/developer_commands.py 
b/dev/breeze/src/airflow_breeze/commands/developer_commands.py
index 7f4b38d27b..a8c257a2df 100644
--- a/dev/breeze/src/airflow_breeze/commands/developer_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/developer_commands.py
@@ -84,6 +84,7 @@ from airflow_breeze.utils.console import get_console
 from airflow_breeze.utils.custom_param_types import BetterChoice
 from airflow_breeze.utils.docker_command_utils import (
     check_docker_resources,
+    fix_ownership_using_docker,
     get_env_variables_for_docker_commands,
     get_extra_docker_flags,
     perform_environment_checks,
@@ -259,6 +260,7 @@ def shell(
         run_db_tests_only=run_db_tests_only,
         skip_db_tests=skip_db_tests,
     )
+    fix_ownership_using_docker()
     sys.exit(result.returncode)
 
 
@@ -382,6 +384,7 @@ def start_airflow(
         standalone_dag_processor=standalone_dag_processor,
         database_isolation=database_isolation,
     )
+    fix_ownership_using_docker()
     sys.exit(result.returncode)
 
 
@@ -426,6 +429,7 @@ def build_docs(
     Build documents.
     """
     perform_environment_checks()
+    fix_ownership_using_docker()
     cleanup_python_generated_files()
     params = BuildCiParams(
         github_repository=github_repository, 
python=DEFAULT_PYTHON_MAJOR_MINOR_VERSION, builder=builder
@@ -460,6 +464,7 @@ def build_docs(
         *doc_builder.args_doc_builder,
     ]
     process = run_command(cmd, text=True, env=env, check=False)
+    fix_ownership_using_docker()
     if process.returncode == 0:
         get_console().print(
             "[info]Start the webserver in breeze and view the built docs at 
http://localhost:28080/docs/[/]";
@@ -639,6 +644,7 @@ def static_checks(
         text=True,
         env=env,
     )
+    fix_ownership_using_docker()
     if static_checks_result.returncode != 0:
         if os.environ.get("CI"):
             get_console().print("\n[error]This error means that you have to 
fix the issues listed above:[/]")
@@ -756,6 +762,7 @@ def enter_shell(**kwargs) -> RunCommandResult:
 
     """
     perform_environment_checks()
+    fix_ownership_using_docker()
     cleanup_python_generated_files()
     if read_from_cache_file("suppress_asciiart") is None:
         get_console().print(ASCIIART, style=ASCIIART_STYLE)
diff --git 
a/dev/breeze/src/airflow_breeze/commands/release_management_commands.py 
b/dev/breeze/src/airflow_breeze/commands/release_management_commands.py
index c7a29b5aa2..732475d5df 100644
--- a/dev/breeze/src/airflow_breeze/commands/release_management_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/release_management_commands.py
@@ -96,6 +96,7 @@ from airflow_breeze.utils.console import MessageType, Output, 
get_console
 from airflow_breeze.utils.custom_param_types import BetterChoice
 from airflow_breeze.utils.docker_command_utils import (
     check_remote_ghcr_io_commands,
+    fix_ownership_using_docker,
     get_env_variables_for_docker_commands,
     get_extra_docker_flags,
     perform_environment_checks,
@@ -220,6 +221,7 @@ NODE_BUILD_IMAGE_TAG = "node:21.2.0-bookworm-slim"
 
 def _compile_assets_in_docker():
     clean_www_assets()
+    get_console().print("[info]Compiling assets in docker container\n")
     result = run_command(
         [
             "docker",
@@ -242,8 +244,14 @@ def _compile_assets_in_docker():
         get_console().print("[error]Error compiling assets[/]")
         get_console().print(result.stdout)
         get_console().print(result.stderr)
+        fix_ownership_using_docker()
         sys.exit(result.returncode)
 
+    get_console().print("[success]compiled assets in docker container\n")
+    get_console().print("[info]Fixing ownership of compiled assets\n")
+    fix_ownership_using_docker()
+    get_console().print("[success]Fixing ownership of compiled assets\n")
+
 
 @release_management.command(
     name="prepare-airflow-package",
@@ -267,6 +275,7 @@ def prepare_airflow_packages(
     use_container_for_assets_compilation: bool,
 ):
     perform_environment_checks()
+    fix_ownership_using_docker()
     cleanup_python_generated_files()
     get_console().print("[info]Compiling assets\n")
     from sys import platform
@@ -303,6 +312,9 @@ def prepare_airflow_packages(
         check=True,
     )
     get_console().print("[success]Successfully prepared Airflow package!\n\n")
+    get_console().print("\n[info]Cleaning ownership of generated files\n")
+    fix_ownership_using_docker()
+    get_console().print("\n[success]Cleaned ownership of generated files\n")
 
 
 def provider_action_summary(description: str, message_type: MessageType, 
packages: list[str]):
@@ -371,6 +383,8 @@ def prepare_provider_documentation(
         update_release_notes,
     )
 
+    perform_environment_checks()
+    fix_ownership_using_docker()
     cleanup_python_generated_files()
     if not provider_packages:
         provider_packages = get_available_packages()
@@ -516,6 +530,7 @@ def prepare_provider_packages(
     provider_packages: tuple[str, ...],
 ):
     perform_environment_checks()
+    fix_ownership_using_docker()
     cleanup_python_generated_files()
     packages_list = get_packages_list_to_act_on(package_list_file, 
provider_packages)
     if not skip_tag_check:
@@ -601,6 +616,7 @@ def run_generate_constraints(
         output=output,
         output_outside_the_group=True,
     )
+    fix_ownership_using_docker()
     return (
         generate_constraints_result.returncode,
         f"Constraints 
{shell_params.airflow_constraints_mode}:{shell_params.python}",
@@ -687,6 +703,7 @@ def generate_constraints(
 ):
     perform_environment_checks()
     check_remote_ghcr_io_commands()
+    fix_ownership_using_docker()
     cleanup_python_generated_files()
     if debug and run_in_parallel:
         get_console().print("\n[error]Cannot run --debug and --run-in-parallel 
at the same time[/]\n")
@@ -854,6 +871,7 @@ def install_provider_packages(
     include_success_outputs: bool,
 ):
     perform_environment_checks()
+    fix_ownership_using_docker()
     cleanup_python_generated_files()
     shell_params = ShellParams(
         mount_sources=MOUNT_SELECTED,
@@ -944,6 +962,7 @@ def install_provider_packages(
             debug=debug,
             output_outside_the_group=True,
         )
+        fix_ownership_using_docker()
         sys.exit(result_command.returncode)
 
 
@@ -977,6 +996,7 @@ def verify_provider_packages(
         get_console().print("Forcing use_packages_from_dist as installing 
selected_providers is set")
         use_packages_from_dist = True
     perform_environment_checks()
+    fix_ownership_using_docker()
     cleanup_python_generated_files()
     shell_params = ShellParams(
         mount_sources=MOUNT_SELECTED,
@@ -1000,6 +1020,7 @@ def verify_provider_packages(
         debug=debug,
         output_outside_the_group=True,
     )
+    fix_ownership_using_docker()
     sys.exit(result_command.returncode)
 
 
diff --git a/dev/breeze/src/airflow_breeze/commands/testing_commands.py 
b/dev/breeze/src/airflow_breeze/commands/testing_commands.py
index 66ffb593c3..fdd712087b 100644
--- a/dev/breeze/src/airflow_breeze/commands/testing_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/testing_commands.py
@@ -70,6 +70,7 @@ from airflow_breeze.utils.common_options import (
 from airflow_breeze.utils.console import Output, get_console
 from airflow_breeze.utils.custom_param_types import BetterChoice
 from airflow_breeze.utils.docker_command_utils import (
+    fix_ownership_using_docker,
     get_env_variables_for_docker_commands,
     perform_environment_checks,
     remove_docker_networks,
@@ -592,6 +593,7 @@ def _run_test_command(
         parallel_test_types_list=test_list,
     )
     rebuild_or_pull_ci_image_if_needed(command_params=exec_shell_params)
+    fix_ownership_using_docker()
     cleanup_python_generated_files()
     perform_environment_checks()
     if run_in_parallel:
@@ -695,6 +697,7 @@ def integration_tests(
         github_repository=github_repository,
         enable_coverage=enable_coverage,
     )
+    fix_ownership_using_docker()
     cleanup_python_generated_files()
     perform_environment_checks()
     returncode, _ = _run_test(
@@ -752,6 +755,7 @@ def helm_tests(
     if helm_test_package != "all":
         env_variables["HELM_TEST_PACKAGE"] = helm_test_package
     perform_environment_checks()
+    fix_ownership_using_docker()
     cleanup_python_generated_files()
     pytest_args = generate_args_for_pytest(
         test_type="Helm",
@@ -769,4 +773,5 @@ def helm_tests(
     )
     cmd = ["docker", "compose", "run", "--service-ports", "--rm", "airflow", 
*pytest_args, *extra_pytest_args]
     result = run_command(cmd, env=env_variables, check=False, 
output_outside_the_group=True)
+    fix_ownership_using_docker()
     sys.exit(result.returncode)
diff --git a/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py 
b/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py
index e936773162..36c976b2fe 100644
--- a/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py
+++ b/dev/breeze/src/airflow_breeze/utils/docker_command_utils.py
@@ -41,8 +41,10 @@ except ImportError:
 from airflow_breeze.branch_defaults import AIRFLOW_BRANCH
 from airflow_breeze.global_constants import (
     ALLOWED_CELERY_BROKERS,
+    ALLOWED_DEBIAN_VERSIONS,
     ALLOWED_PACKAGE_FORMATS,
     APACHE_AIRFLOW_GITHUB_REPOSITORY,
+    DEFAULT_PYTHON_MAJOR_MINOR_VERSION,
     FLOWER_HOST_PORT,
     MIN_DOCKER_COMPOSE_VERSION,
     MIN_DOCKER_VERSION,
@@ -763,8 +765,15 @@ LABEL description="test warmup image"
         )
 
 
+OWNERSHIP_CLEANUP_DOCKER_TAG = (
+    
f"python:{DEFAULT_PYTHON_MAJOR_MINOR_VERSION}-slim-{ALLOWED_DEBIAN_VERSIONS[0]}"
+)
+
+
 def fix_ownership_using_docker():
-    perform_environment_checks()
+    if get_host_os() != "linux":
+        # no need to even attempt fixing ownership on MacOS/Windows
+        return
     shell_params = find_available_ci_image(
         github_repository=APACHE_AIRFLOW_GITHUB_REPOSITORY,
     )
@@ -775,10 +784,8 @@ def fix_ownership_using_docker():
         "run",
         "-t",
         *extra_docker_flags,
-        "--pull",
-        "never",
-        shell_params.airflow_image_name_with_tag,
-        "/opt/airflow/scripts/in_container/run_fix_ownership.sh",
+        OWNERSHIP_CLEANUP_DOCKER_TAG,
+        "/opt/airflow/scripts/in_container/run_fix_ownership.py",
     ]
     run_command(cmd, text=True, env=env, check=False)
 
diff --git a/dev/breeze/src/airflow_breeze/utils/parallel.py 
b/dev/breeze/src/airflow_breeze/utils/parallel.py
index ad7e197d1c..eca700a18a 100644
--- a/dev/breeze/src/airflow_breeze/utils/parallel.py
+++ b/dev/breeze/src/airflow_breeze/utils/parallel.py
@@ -443,9 +443,15 @@ def check_async_run_results(
     try:
         if errors:
             get_console().print("\n[error]There were errors when running some 
tasks. Quitting.[/]\n")
+            from airflow_breeze.utils.docker_command_utils import 
fix_ownership_using_docker
+
+            fix_ownership_using_docker()
             sys.exit(1)
         else:
             get_console().print(f"\n[success]{success}[/]\n")
+            from airflow_breeze.utils.docker_command_utils import 
fix_ownership_using_docker
+
+            fix_ownership_using_docker()
     finally:
         if not skip_cleanup:
             for output in outputs:
diff --git a/dev/breeze/src/airflow_breeze/utils/run_utils.py 
b/dev/breeze/src/airflow_breeze/utils/run_utils.py
index f9415394b6..2553e75ce0 100644
--- a/dev/breeze/src/airflow_breeze/utils/run_utils.py
+++ b/dev/breeze/src/airflow_breeze/utils/run_utils.py
@@ -457,7 +457,7 @@ def clean_www_assets():
     WWW_ASSET_HASH_FILE.unlink(missing_ok=True)
     shutil.rmtree(WWW_NODE_MODULES_DIR, ignore_errors=True)
     shutil.rmtree(WWW_STATIC_DIST_DIR, ignore_errors=True)
-    get_console().print("[info]Cleaned www assets[/]")
+    get_console().print("[success]Cleaned www assets[/]")
 
 
 def run_compile_www_assets(
diff --git a/scripts/docker/entrypoint_ci.sh b/scripts/docker/entrypoint_ci.sh
index 1de27480d1..d16134b482 100755
--- a/scripts/docker/entrypoint_ci.sh
+++ b/scripts/docker/entrypoint_ci.sh
@@ -296,8 +296,6 @@ if [[ ${SKIP_ENVIRONMENT_INITIALIZATION=} != "true" ]]; then
     touch /usr/lib/google-cloud-sdk/bin/gcloud
     ln -s -f /usr/bin/gcloud /usr/lib/google-cloud-sdk/bin/gcloud
 
-    in_container_fix_ownership
-
     if [[ ${SKIP_SSH_SETUP="false"} == "false" ]]; then
         # Set up ssh keys
         echo 'yes' | ssh-keygen -t rsa -C [email protected] -m PEM -P 
'' -f ~/.ssh/id_rsa \
diff --git a/scripts/in_container/_in_container_utils.sh 
b/scripts/in_container/_in_container_utils.sh
index b3606973c4..a3e09e0f1e 100644
--- a/scripts/in_container/_in_container_utils.sh
+++ b/scripts/in_container/_in_container_utils.sh
@@ -56,46 +56,6 @@ function in_container_script_start() {
     fi
 }
 
-#
-# Fixes ownership of files generated in container - if they are owned by root, 
they will be owned by
-# The host user. Only needed if the host is Linux - on Mac, ownership of files 
is automatically
-# changed to the Host user via osxfs filesystem
-#
-function in_container_fix_ownership() {
-    if [[ ${HOST_OS:=} == "linux" ]]; then
-        if [[ ${DOCKER_IS_ROOTLESS=} == "true" ]]; then
-             echo "${COLOR_YELLOW}Skip fixing ownership of generated files: 
Docker is rootless${COLOR_RESET}"
-             return
-        fi
-        DIRECTORIES_TO_FIX=(
-            "/dist"
-            "/files"
-            "${AIRFLOW_SOURCES}/logs"
-            "${AIRFLOW_SOURCES}/docs"
-            "${AIRFLOW_SOURCES}/dags"
-            "${AIRFLOW_SOURCES}/airflow/"
-            "${AIRFLOW_SOURCES}/constraints/"
-            "${AIRFLOW_SOURCES}/images/"
-            "${AIRFLOW_SOURCES}/.mypy_cache/"
-            "${AIRFLOW_SOURCES}/dev/"
-        )
-        count_matching=$(find "${DIRECTORIES_TO_FIX[@]}" -mindepth 1 -user 
root -printf . 2>/dev/null | wc -m || true)
-        if [[ ${count_matching=} != "0" && ${count_matching=} != "" ]]; then
-            echo
-            echo "${COLOR_BLUE}Fixing ownership of ${count_matching} root 
owned files on ${HOST_OS}${COLOR_RESET}"
-            echo
-            find "${DIRECTORIES_TO_FIX[@]}" -mindepth 1 -user root -print0 2> 
/dev/null |
-                xargs --null chown "${HOST_USER_ID}:${HOST_GROUP_ID}" 
--no-dereference || true >/dev/null 2>&1
-            echo "${COLOR_BLUE}Fixed ownership of generated 
files${COLOR_RESET}."
-            echo
-        fi
-     else
-        echo
-        echo "${COLOR_YELLOW}Skip fixing ownership of generated files as Host 
OS is ${HOST_OS}${COLOR_RESET}"
-        echo
-    fi
-}
-
 function in_container_go_to_airflow_sources() {
     pushd "${AIRFLOW_SOURCES}" >/dev/null 2>&1 || exit 1
 }
diff --git a/scripts/in_container/run_fix_ownership.py 
b/scripts/in_container/run_fix_ownership.py
new file mode 100755
index 0000000000..68fc3a9904
--- /dev/null
+++ b/scripts/in_container/run_fix_ownership.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python3
+#
+# 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 os
+import pwd
+import sys
+from pathlib import Path
+
+HOST_OS = os.environ.get("HOST_OS", "")
+DOCKER_IS_ROOTLESS = os.environ.get("DOCKER_IS_ROOTLESS", "false") == "true"
+
+
+def change_ownership_of_files(path: Path) -> None:
+    host_user_id = os.environ.get("HOST_USER_ID", "")
+    host_group_id = os.environ.get("HOST_GROUP_ID", "")
+    if host_user_id == "" or host_group_id == "":
+        print(
+            f"ERROR: HOST_USER_ID or HOST_GROUP_ID environment variables "
+            f"are not set: {host_user_id}:{host_group_id}"
+        )
+        sys.exit(1)
+    if not host_user_id.isnumeric() or not host_group_id.isnumeric():
+        print(
+            f"ERROR: HOST_USER_ID or HOST_GROUP_ID environment variables "
+            f"should be numeric: {host_user_id}:{host_group_id}"
+        )
+        sys.exit(1)
+    count_files = 0
+    root_uid = pwd.getpwnam("root").pw_uid
+    print("Attempting to see if there are files that need to be changed to 
host user ownership")
+    for file in path.rglob("*"):
+        try:
+            if file.is_symlink() and file.lstat().st_uid == root_uid:
+                # Change ownership of symlink itself (by default stat/chown 
follow the symlinks)
+                os.chown(file, int(host_user_id), int(host_group_id), 
follow_symlinks=False)
+                count_files += 1
+                if os.environ.get("VERBOSE_COMMANDS", "false") == "true":
+                    print(f"Changed ownership of symlink {file}")
+            if file.stat().st_uid == root_uid:
+                # And here change ownership of the file (or if it is a symlink 
- the file it points to)
+                os.chown(file, int(host_user_id), int(host_group_id))
+                count_files += 1
+                if os.environ.get("VERBOSE_COMMANDS", "false") == "true":
+                    print(f"Changed ownership of {file.resolve()}")
+        except FileNotFoundError:
+            # This is OK - file might have been deleted in the meantime or 
linked in Host from
+            # another place
+            if os.environ.get("VERBOSE_COMMANDS", "false") == "true":
+                print(f"Could not change ownership of {file}")
+    if count_files:
+        print(f"Changed ownership of {count_files} files back to 
{host_user_id}:{host_group_id}.")
+    else:
+        print("No files needed to change ownership")
+
+
+if __name__ == "__main__":
+    if HOST_OS == "":
+        print("ERROR: HOST_OS environment variable is not set")
+        sys.exit(1)
+    if HOST_OS == "Linux":
+        print("Since host OS is not Linux, we don't need to fix ownership.")
+        sys.exit(0)
+    if DOCKER_IS_ROOTLESS:
+        print("Since Docker is in rootless mode , we don't need to fix 
ownership even on Linux.")
+        sys.exit(0)
+
+    change_ownership_of_files(Path("/opt/airflow"))
diff --git a/scripts/in_container/run_fix_ownership.sh 
b/scripts/in_container/run_fix_ownership.sh
deleted file mode 100755
index d9e98ff168..0000000000
--- a/scripts/in_container/run_fix_ownership.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env bash
-# 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.
-# shellcheck source=scripts/in_container/_in_container_utils.sh
-. "$( dirname "${BASH_SOURCE[0]}" )/_in_container_utils.sh"
-
-in_container_fix_ownership
diff --git a/scripts/in_container/run_prepare_airflow_packages.py 
b/scripts/in_container/run_prepare_airflow_packages.py
index c1d2fb8aa5..4b319a722c 100755
--- a/scripts/in_container/run_prepare_airflow_packages.py
+++ b/scripts/in_container/run_prepare_airflow_packages.py
@@ -44,8 +44,6 @@ def process_summary(success_message: str, error_message: str, 
completed_process:
 AIRFLOW_SOURCES_ROOT = Path(__file__).parents[2].resolve()
 WWW_DIRECTORY = AIRFLOW_SOURCES_ROOT / "airflow" / "www"
 
-rich.print("[green]Installed packages\n")
-
 rich.print("[bright_blue]Cleaning build directories\n")
 
 for egg_info_file in AIRFLOW_SOURCES_ROOT.glob("*egg-info*"):
@@ -53,7 +51,7 @@ for egg_info_file in AIRFLOW_SOURCES_ROOT.glob("*egg-info*"):
 
 rmtree(AIRFLOW_SOURCES_ROOT / "build", ignore_errors=True)
 
-rich.print("[bright_blue]Cleaned build directories\n")
+rich.print("[green]Cleaned build directories\n\n")
 
 version_suffix = os.environ.get("VERSION_SUFFIX_FOR_PYPI", "")
 package_format = os.environ.get("PACKAGE_FORMAT", "wheel")
@@ -76,7 +74,7 @@ subprocess.run(
     check=True,
 )
 
-rich.print(f"[bright_blue]Marked {AIRFLOW_SOURCES_ROOT} as safe directory for 
git commands.\n")
+rich.print(f"[green]Marked {AIRFLOW_SOURCES_ROOT} as safe directory for git 
commands.\n")
 
 rich.print("[bright_blue]Checking airflow version\n")
 
@@ -84,7 +82,7 @@ airflow_version = subprocess.check_output(
     [sys.executable, "setup.py", "--version"], text=True, 
cwd=AIRFLOW_SOURCES_ROOT
 ).strip()
 
-rich.print(f"[bright_blue]Airflow version: {airflow_version}\n")
+rich.print(f"[green]Airflow version: {airflow_version}\n")
 
 RELEASED_VERSION_MATCHER = re.compile(r"^\d+\.\d+\.\d+$")
 
@@ -92,7 +90,7 @@ command = [sys.executable, "setup.py"]
 
 if version_suffix:
     if RELEASED_VERSION_MATCHER.match(airflow_version):
-        rich.print(f"[bright_blue]Adding {version_suffix} suffix to the 
{airflow_version}")
+        rich.print(f"[warning]Adding {version_suffix} suffix to the 
{airflow_version}")
         command.extend(["egg_info", "--tag-build", version_suffix])
     elif not airflow_version.endswith(version_suffix):
         rich.print(f"[red]Version {airflow_version} does not end with 
{version_suffix}. Using !")
@@ -112,7 +110,7 @@ process_summary("Airflow packages built successfully", 
"Error building Airflow p
 if os.environ.get("GITHUB_ACTIONS", "") != "":
     print("::endgroup::")
 
-rich.print("[bright_blue]Packages built successfully:\n")
+rich.print("[green]Packages built successfully:\n")
 for file in (AIRFLOW_SOURCES_ROOT / "dist").glob("apache*"):
     rich.print(file.name)
 rich.print()


Reply via email to