Skip to content

fix(iac): apply refuses an MWAA environment it cannot create instead of reporting success - #715

Merged
fas89 merged 4 commits into
mainfrom
fix/aws-mwaa-unsupported-loud
Oct 12, 2026
Merged

fas89 merged 4 commits into
mainfrom
fix/aws-mwaa-unsupported-loud

Conversation

@fas89

@fas89 fas89 commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Description

plan.json promised an MWAA environment that apply never created. providers/aws/plan/schedule.py emits an mwaa.ensure_environment action for an AWS contract with orchestration.engine: airflow|mwaa plus a schedule or triggers. iac/providers/aws.py::_emit_from_actions had no branch for it, so main.tf.json silently lacked aws_mwaa_environment, fluid apply "succeeded", and --shadow did not flag the drop.

  • An action op with no emitter branch is now loud: the typed UnsupportedBindingError, with kind mwaa-environment-not-emitted for mwaa.ensure_environment and planned-op-not-emitted for any other unknown op. It is never silently dropped, following the design note in iac/base.py.
  • The refusal names the environment and gives two ways forward: schedule with EventBridge Scheduler (orchestration.engine: none + schedule), or deploy DAGs to an existing MWAA environment.
  • --shadow reports it too.
  • --provider aws refuses an MWAA contract even when another cloud's planner ran, and a multi-cloud contract applies again.
  • A planned-but-not-emitted IAM binding (iam.bind_glue_database) is now logged instead of silent.
  • Upgrade note in CHANGELOG.md.

Creating a real aws_mwaa_environment needs VPC network_configuration (private subnets, security groups) that the contract does not carry, so it is out of scope.

Type of Change

  • Bug fix
  • Breaking change: AWS contracts with orchestration.engine: airflow|mwaa plus a schedule or triggers now fail at generate iac / apply instead of succeeding without the environment.

Documentation

Checklist

  • Tests added (tests/iac/test_iac_aws_unemitted_ops.py; test_iac_aws.py and test_iac_shadow.py updated)
  • ruff check, black (24.10.0), import-linter and the mypy strict hotspots are clean
  • Full suite green on Python 3.10–3.14 (plus perf, coverage gates and build smoke on 3.12), run as part of an integration branch with the other in-flight fixes

Testing

Real fluid CLI in fc-test:3.12 with a fake tofu shim that records the module it is handed, origin/main vs this branch:

Contract Before After
engine: airflow + schedule (generate iac) logs "Planned MWAA environment", writes main.tf.json with 3 resources and no environment, exit 0 exit 1, unsupported_binding [ERR_UNSUPPORTED_BINDING] kind: mwaa-environment-not-emitted + remediation; no main.tf.json
same, fluid apply hands tofu the 3-resource module exit 1 before tofu is invoked
engine: mwaa, uppercase AIRFLOW, named shared environment same silent drop same typed refusal
--shadow no gap reported exit 1, typed error
plain AWS contract / engine: none + schedule / triggers only unchanged unchanged (aws_scheduler_schedule emitted for none + schedule)

fas89 added 3 commits October 12, 2026 19:21
…of reporting success

An AWS contract with orchestration.engine airflow (or mwaa) and an
orchestration.schedule made the AWS planner plan mwaa.ensure_environment,
and the OpenTofu emitter's _emit_from_actions had no branch for it. The
generated main.tf.json held no aws_mwaa_environment, fluid apply reported
success, and no Airflow environment existed. OpenTofu is the only AWS
apply engine, so nothing else created it.

fluid apply and fluid generate iac now stop with unsupported_binding, kind
mwaa-environment-not-emitted, naming the environment and two ways out:
engine none keeps the schedule on EventBridge Scheduler, or push the DAG
to an MWAA environment you own with fluid schedule-sync. The OpenTofu
engine cannot create one itself: aws_mwaa_environment needs a VPC
network_configuration with private subnets, which the contract does not
carry (aws-ia/terraform-aws-mwaa requires private_subnet_ids with no
default; the CloudFormation NetworkConfiguration reference says the same).

Every other planned op without an emitter is accounted for too. An op no
list knows is refused by name (kind planned-op-not-emitted), the way dbt
raises MissingMaterializationError ("No materialization ... was found for
adapter", dbt-adapters exceptions/compilation.py) rather than skipping
the model. Data-plane ops emit() derives from exposes[] and build steps
pass quietly; known planner ops nothing provisions yet (IAM bindings from
metadata.policies, Athena views, Redshift schemas and tables,
execution.trigger schedules) and orchestration.tasks steps are named in
a warning. --shadow now maps an environment op to a logical resource, so
a dropped one would show as a native-only gap.
… MWAA contract is refused even when the planner fails

The previous commit refused any planned op the AWS emitter did not know.
native_actions plans with the first expose's provider, so on a contract
whose first expose is on GCP or Snowflake, `--provider aws` (what the
ambiguous-provider error tells the user to run) fed bq.* / sf.* ops to the
AWS emitter and failed the AWS half with planned-op-not-emitted. An unknown
op is now named in a warning (op and id rendered with repr, so a control
character from a task's op never reaches the terminal) instead of refused.

What kept the refusal honest is now a test: every "op" literal under
fluid_build/providers/aws, and a mode="replace" plan, must be emitted
(_ACTION_EMITTERS, which replaces the if/elif chain) or classified. It found
aws.s3.copy_prefix, the pre-replace backup the rollback writer records,
which is now owned elsewhere. dbt-core asserts adapter materialization
coverage the same way.

native_actions returns [] when the planner raises (a Glue table name its
validator refuses, a cyclic orchestration.tasks), and the emitter then saw
no mwaa.ensure_environment and wrote the data plane, exit 0. An empty plan
for a contract that asks for MWAA (the schedule planner's own test: a
schedule and engine airflow or mwaa, lower-cased, unstripped) is refused
the same way.

The remediation named `schedule-sync --environment-name <env>`, which the
MWAA dispatch never reads: it syncs to --destination. It now gives the
full generate-schedule / schedule-sync pair with --destination filled from
orchestration.mwaa.dagS3Path, and tells a contract that names its
environment to push to it rather than create one.
…loud's planner ran

A multi-cloud contract whose first expose is on GCP or Snowflake, with
orchestration.engine airflow and a schedule, still wrote an AWS module with
no MWAA environment and exited 0 under `fluid generate iac --provider aws`
(and `fluid apply --provider aws`). native_actions plans with the first
expose's provider, so the AWS planner that plans mwaa.ensure_environment
never ran; the emitter only refused when the action list was empty or held
that op, and the foreign ops made it non-empty. FLUID_PROVIDER=local hit the
same gap with LocalProvider's ops.

The refusal now follows the contract: when it asks for MWAA (the same
predicate the AWS schedule planner applies) and no planned action is the
environment op, the emitter refuses with unsupported_binding, kind
mwaa-environment-not-emitted, whichever planner produced the actions.
Regression tests drive generate_iac with --provider aws on both multi-cloud
shapes and the emitter with GCP, Snowflake and local ops.
@github-actions github-actions Bot added the tests Test coverage or test infrastructure changes label Oct 12, 2026
The refusal printed `fluid generate schedule --output dags` and then
`fluid schedule-sync --dags-dir dags`. schedule-sync's default (product
delete scope) mirrors each top-level directory of --dags-dir and refuses
loose files, so the printed pair failed with
schedule_sync_dags_dir_not_product_scoped. The DAG is now generated into
dags/<contract id>/, and a contract id that is not a valid product
directory name prints a <product-id> placeholder instead. A test runs the
printed output path through schedule-sync's own scope check.
@fas89
fas89 merged commit 9482607 into main Oct 12, 2026
41 checks passed
@fas89
fas89 deleted the fix/aws-mwaa-unsupported-loud branch October 12, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tests Test coverage or test infrastructure changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant