Skip to content
Open
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
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Enforce LF line endings for all text files on all platforms.
# This ensures shell scripts and other files work correctly inside Linux containers
# regardless of the host OS (Windows, macOS, Linux).
* text=auto eol=lf
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ coverage.xml

# Intellij
*.idea*

# Generated by run.ps1 for Docker Compose variable substitution on Windows
images/airflow/**/.env
80 changes: 80 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ well. _Notice, however, that we do not plan to support previous Airflow versions

## Using the Airflow Image

### Linux / macOS

To experiment with the image using a vanilla Docker setup, follow these steps:

0. _(Prerequisites)_ Ensure you have:
Expand Down Expand Up @@ -46,6 +48,84 @@ python3 create_venvs.py --target <development | production> --version 3.0.6

Airflow should be up and running now. You can access the web server on your localhost on port 8080.

---

### Windows (PowerShell 5.1)

#### Prerequisites

- Windows 10/11 with PowerShell 5.1 (built-in — no installation needed)
- Python 3.11 or later — install from [python.org](https://www.python.org/downloads/) (check "Add Python to PATH" during install)
- [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/) set to **Linux containers** mode
- Right-click the Docker Desktop tray icon → "Switch to Linux containers" if needed
- AWS CLI (optional — only required if you want CloudWatch log group creation)

#### One-time setup

1. Clone this repository.

2. Allow PowerShell to run local scripts (run once as your user):
```powershell
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
```

3. Create the Python virtual environments from the repo root:
```powershell
# Create venvs for all Airflow versions
python create_venvs.py --target development

# Or for a specific version only
python create_venvs.py --target development --version <version>
```

#### Running

4. Navigate to an Airflow version directory and run:
```powershell
cd images\airflow\<version>
.\run.ps1
```

This will build the Docker images and start the full Airflow stack. On first run, the image build can take 10–20 minutes.

- To test a `requirements.txt` without running Airflow:
```powershell
.\run.ps1 -Command test-requirements
```
- To test a `startup.sh` without running Airflow:
```powershell
.\run.ps1 -Command test-startup-script
```

#### AWS Credentials

For local development without a real AWS account, `run.ps1` defaults to dummy values — ElasticMQ (the local SQS mock) does not validate credentials. To use real AWS services (e.g. CloudWatch logging), update the `$AccountId`, `$EnvName`, and `$env:AWS_*` values at the top of `run.ps1`.

#### Logging in

Once the stack is up, open `http://localhost:8080`. The default credentials are printed in the webserver container logs on startup.

#### Adding DAGs

Drop DAG files into `images\airflow\<version>\dags\`. They are live-mounted into the container — no restart needed. The scheduler picks them up within a minute or two.

#### Stopping

```powershell
docker compose down
```

#### Troubleshooting

| Problem | Fix |
|---|---|
| `cannot be loaded because running scripts is disabled` | Run `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser` |
| `Docker is not in Linux containers mode` | Right-click Docker Desktop tray icon → Switch to Linux containers |
| `python` not found | Install Python 3.11+ from python.org with "Add to PATH" checked |
| `Unable to locate credentials` | Ensure `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` are non-empty in `run.ps1` |
| Login fails at `http://localhost:8080` | Check the webserver container logs for the credentials printed on startup |
| DAG not appearing | Check the scheduler container logs or verify the file exists in the `dags\` folder |

### Authentication from version 3.0.1 onward
For environments created using this repository starting with version 3.0.1, we default to using `SimpleAuthManager`,
which is also the default auth manager in Airflow 3.0.0+. By default, `SIMPLE_AUTH_MANAGER_ALL_ADMINS` is set to true,
Expand Down
10 changes: 8 additions & 2 deletions create_venvs.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def create_venv(path: Path, development_build: bool, recreate: bool = False):

if not venv_path.exists():
print(f"> Creating virtualenv in directory: {venv_path}")
venv.create(venv_path, with_pip=True, symlinks=True)
venv.create(venv_path, with_pip=True, symlinks=(sys.platform != "win32"))
else:
print(f"> Virtualenv already exists in {venv_path}")

Expand Down Expand Up @@ -109,8 +109,14 @@ def pip_install(venv_dir: Path, *args: str):
:param venv_dir: The path to the venv directory.
:param venv_dir: The path to the requirements.txt file.
"""
# On Windows the venv layout uses Scripts\python.exe; on Unix it uses bin/python
if sys.platform == "win32":
python_bin = os.path.join(venv_dir, "Scripts", "python.exe")
else:
python_bin = os.path.join(venv_dir, "bin", "python")

subprocess.run(
[os.path.join(venv_dir, "bin", "python"), "-m", "pip", "install", *args],
[python_bin, "-m", "pip", "install", *args],
check=True,
)

Expand Down
146 changes: 146 additions & 0 deletions images/airflow/2.10.1/build.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#Requires -Version 5.1
<#
.SYNOPSIS
Builds the Amazon MWAA Airflow 2.10.1 Docker images on Windows.

.DESCRIPTION
PowerShell equivalent of build.sh for Windows (PowerShell 5.1+).
Activates the repo virtual environment, generates Dockerfiles from
Jinja2 templates, and builds the base image plus all derivative images.
Optionally extracts the Bill of Materials from each built image.

PREREQUISITES
-------------
- Docker Desktop for Windows with Linux containers mode enabled
- Repo virtual environment created at <repo-root>/.venv:
python create_venvs.py --target development
- PowerShell execution policy allowing local scripts:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

.PARAMETER ContainerRuntime
Container runtime to use: docker (default), podman, or finch.

.EXAMPLE
.\build.ps1
.\build.ps1 -ContainerRuntime podman
#>

param(
[string]$ContainerRuntime = ""
)

$ErrorActionPreference = 'Stop'
Set-Location -Path $PSScriptRoot

# ---------------------------------------------------------------------------
# Container runtime — detect if not provided
# ---------------------------------------------------------------------------
if (-not $ContainerRuntime) {
if (Get-Command -Name finch -ErrorAction SilentlyContinue) {
$ContainerRuntime = "finch"
} elseif (Get-Command -Name podman -ErrorAction SilentlyContinue) {
$ContainerRuntime = "podman"
} else {
$ContainerRuntime = "docker"
}
}

Write-Host "Using $ContainerRuntime runtime in build.ps1"

# ---------------------------------------------------------------------------
# Prerequisite validation
# ---------------------------------------------------------------------------
if (-not (Get-Command -Name $ContainerRuntime -ErrorAction SilentlyContinue)) {
throw "$ContainerRuntime is not installed or not in PATH."
}

& $ContainerRuntime info 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
throw "$ContainerRuntime is not running. Please start Docker Desktop and try again."
}

$osType = & $ContainerRuntime info --format '{{.OSType}}' 2>&1
if ($LASTEXITCODE -ne 0 -or $osType -ne 'linux') {
throw "$ContainerRuntime is not in Linux containers mode. Right-click the Docker Desktop tray icon and choose 'Switch to Linux containers'."
}

# ---------------------------------------------------------------------------
# Virtual environment activation
# ---------------------------------------------------------------------------
$venvActivate = [System.IO.Path]::GetFullPath(
[System.IO.Path]::Combine($PSScriptRoot, "..\..\..\.venv\Scripts\Activate.ps1")
)
if (-not (Test-Path -Path $venvActivate)) {
throw "Virtual environment not found at '$venvActivate'. Run from the repo root: python create_venvs.py --target development"
}
. $venvActivate

# ---------------------------------------------------------------------------
# Generate Dockerfiles from Jinja2 templates
# ---------------------------------------------------------------------------
python ..\generate-dockerfiles.py
if ($LASTEXITCODE -ne 0) { throw "generate-dockerfiles.py failed." }

deactivate

# ---------------------------------------------------------------------------
# BOM configuration
# ---------------------------------------------------------------------------
$GenerateBom = if ($env:GENERATE_BILL_OF_MATERIALS) { $env:GENERATE_BILL_OF_MATERIALS } else { "False" }
$BomDockerPath = "/BillOfMaterials"
$BomLocalPath = ".\BillOfMaterials"

Write-Host "GENERATE_BILL_OF_MATERIALS is set to $GenerateBom"

if ($GenerateBom -eq "True") {
if (Test-Path -Path $BomLocalPath) {
Remove-Item -Path $BomLocalPath -Recurse -Force
}
New-Item -ItemType Directory -Path $BomLocalPath -Force | Out-Null
}

# ---------------------------------------------------------------------------
# Build base image
# ---------------------------------------------------------------------------
& $ContainerRuntime build -f .\Dockerfiles\Dockerfile.base -t amazon-mwaa-docker-images/airflow:2.10.1-base .\
if ($LASTEXITCODE -ne 0) { throw "Failed to build base image." }

# ---------------------------------------------------------------------------
# Build derivative images
# ---------------------------------------------------------------------------
foreach ($dev in @("True", "False")) {
foreach ($buildType in @("standard", "explorer", "explorer-privileged")) {
$dockerfileName = "Dockerfile"
$tagName = "2.10.1"

if ($buildType -ne "standard") {
$dockerfileName = "$dockerfileName-$buildType"
$tagName = "$tagName-$buildType"
}

if ($dev -eq "True") {
$dockerfileName = "$dockerfileName-dev"
$tagName = "$tagName-dev"
}

$imageName = "amazon-mwaa-docker-images/airflow:$tagName"

& $ContainerRuntime build -f ".\Dockerfiles\$dockerfileName" -t $imageName .\
if ($LASTEXITCODE -ne 0) { throw "Failed to build image '$imageName'." }

if ($GenerateBom -eq "True") {
& $ContainerRuntime container rm bom_temp_container 2>&1 | Out-Null

& $ContainerRuntime container create --name bom_temp_container $imageName
if ($LASTEXITCODE -ne 0) { throw "Failed to create temp container for BOM extraction." }

& $ContainerRuntime cp "bom_temp_container:$BomDockerPath" $BomLocalPath
if ($LASTEXITCODE -ne 0) { throw "Failed to copy BOM from container." }

Rename-Item -Path "$BomLocalPath\BillOfMaterials" -NewName $tagName

& $ContainerRuntime rm bom_temp_container
if ($LASTEXITCODE -ne 0) { throw "Failed to remove temp container." }
}
}
}
Loading