Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

from termcolor import colored

from airflow.api_fastapi.app import create_auth_manager
from airflow.configuration import conf
from airflow.executors import executor_constants
from airflow.executors.executor_loader import ExecutorLoader
Expand Down Expand Up @@ -58,7 +59,6 @@ def entrypoint(cls, args):
def __init__(self):
self.subcommands = {}
self.output_queue = deque()
self.user_info = {}
self.ready_time = None
self.ready_delay = 3

Expand All @@ -69,6 +69,7 @@ def run(self):
logging.getLogger("").setLevel(logging.WARNING)
# Startup checks and prep
env = self.calculate_env()
self.find_user_info()
self.initialize_database()
# Set up commands to run
self.subcommands["scheduler"] = SubCommand(
Expand Down Expand Up @@ -179,24 +180,49 @@ def calculate_env(self):
else:
self.print_output("standalone", "Forcing executor to LocalExecutor")
env["AIRFLOW__CORE__EXECUTOR"] = executor_constants.LOCAL_EXECUTOR

# Make sure we're using SimpleAuthManager
simple_auth_manager_classpath = (
"airflow.api_fastapi.auth.managers.simple.simple_auth_manager.SimpleAuthManager"
)
if conf.get("core", "auth_manager") != simple_auth_manager_classpath:
self.print_output("standalone", "Forcing auth manager to SimpleAuthManager")
env["AIRFLOW__CORE__AUTH_MANAGER"] = simple_auth_manager_classpath
os.environ["AIRFLOW__CORE__AUTH_MANAGER"] = simple_auth_manager_classpath # also in this process!

return env

def find_user_info(self):
if conf.get("core", "simple_auth_manager_all_admins").lower() == "true":
# If we have no auth anyways, no need to print or do anything
return
if conf.get("core", "simple_auth_manager_users") != "admin:admin":
self.print_output(
"standalone",
"Not outputting user passwords - `[core] simple_auth_manager_users` is already set.",
)
return

am = create_auth_manager()

password_file = am.get_generated_password_file()
if os.path.exists(password_file):
self.print_output(
"standalone",
f"Password for the admin user has been previously generated in {password_file}. Not echoing it here.",
)
return

# this generates the password and prints it
am.init()

def initialize_database(self):
"""Make sure all the tables are created."""
# Set up DB tables
self.print_output("standalone", "Checking database is initialized")
db.initdb()
self.print_output("standalone", "Database ready")

# Then create a "default" admin user if necessary
from airflow.providers.fab.auth_manager.cli_commands.utils import get_application_builder

with get_application_builder() as appbuilder:
if hasattr(appbuilder.sm, "create_admin_standalone"):
user_name, password = appbuilder.sm.create_admin_standalone()
# Store what we know about the user for printing later in startup
self.user_info = {"username": user_name, "password": password}

def is_ready(self):
"""
Detect when all Airflow components are ready to serve.
Expand Down Expand Up @@ -244,11 +270,6 @@ def print_ready(self):
"""
self.print_output("standalone", "")
self.print_output("standalone", "Airflow is ready")
if self.user_info["password"]:
self.print_output(
"standalone",
f"Login with username: {self.user_info['username']} password: {self.user_info['password']}",
)
self.print_output(
"standalone",
"Airflow Standalone is for development purposes only. Do not use this in production!",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
import datetime
import itertools
import logging
import os
import random
import uuid
from collections.abc import Collection, Iterable, Mapping
from typing import TYPE_CHECKING, Any
Expand Down Expand Up @@ -740,43 +738,6 @@ def builtin_roles(self):
"""Get the builtin roles."""
return self._builtin_roles

def create_admin_standalone(self) -> tuple[str | None, str | None]:
"""Create an Admin user with a random password so that users can access airflow."""
from airflow.configuration import AIRFLOW_HOME, make_group_other_inaccessible

user_name = "admin"

# We want a streamlined first-run experience, but we do not want to
# use a preset password as people will inevitably run this on a public
# server. Thus, we make a random password and store it in AIRFLOW_HOME,
# with the reasoning that if you can read that directory, you can see
# the database credentials anyway.
password_path = os.path.join(AIRFLOW_HOME, "standalone_admin_password.txt")

user_exists = self.find_user(user_name) is not None
we_know_password = os.path.isfile(password_path)

# If the user does not exist, make a random password and make it
if not user_exists:
print(f"FlaskAppBuilder Authentication Manager: Creating {user_name} user")
if (role := self.find_role("Admin")) is None:
raise AirflowException("Unable to find role 'Admin'")
# password does not contain visually similar characters: ijlIJL1oO0
password = "".join(random.choices("abcdefghkmnpqrstuvwxyzABCDEFGHKMNPQRSTUVWXYZ23456789", k=16))
with open(password_path, "w") as file:
file.write(password)
make_group_other_inaccessible(password_path)
self.add_user(user_name, "Admin", "User", "admin@example.com", role, password)
print(f"FlaskAppBuilder Authentication Manager: Created {user_name} user")
# If the user does exist, and we know its password, read the password
elif user_exists and we_know_password:
with open(password_path) as file:
password = file.read().strip()
# Otherwise we don't know the password
else:
password = None
return user_name, password

def _init_config(self):
"""
Initialize config.
Expand Down