✨ feat(infra): add enable_provisioner option to skip the provisioner Lambda - #443
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #443 +/- ##
==========================================
+ Coverage 92.91% 92.93% +0.02%
==========================================
Files 37 37
Lines 8324 8353 +29
==========================================
+ Hits 7734 7763 +29
Misses 590 590
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. |
…Lambda StackOptions gained enable_provisioner (default: True), mirroring enable_aggregator. It maps to the existing EnableProvisioner CloudFormation parameter, which had conditions in the template but was never wired to a client-side option. - StackOptions.enable_provisioner + to_parameters() mapping - RepositoryBuilder.enable_provisioner() - Repository._ensure_infrastructure_internal() gates deploy_provisioner_code() - CLI: --enable-provisioner/--no-provisioner on deploy --no-iam now auto-disables the provisioner the same way it auto-disables the aggregator. DeployProvisionerLambda requires DeployIAM, so the function was never created under --no-iam, yet the CLI still tried to push code to it and exited 1. Sync counterparts regenerated.
The builder path pushed code to the aggregator and provisioner Lambdas whenever their enable_* flags were set, but CloudFormation only creates those functions when a role exists for them. Under create_iam=False the functions are never created, so deploy_lambda_code() / deploy_provisioner_code() failed with ResourceNotFoundException — the same bug the CLI just fixed for --no-iam, reached through Repository.builder().create_iam(False). StackOptions gains deploys_aggregator_lambda and deploys_provisioner_lambda, which mirror the template's DeployAggregatorLambda / DeployProvisionerLambda conditions (the aggregator's external-role escape hatch included), and _ensure_infrastructure_internal now gates on those. Also give `limits plan|apply|diff` a real error when the provisioner is not deployed. That state is now reachable by design via --no-provisioner, and it surfaced as a raw botocore ResourceNotFoundException traceback. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014nPStTtHkW7zqj3X3wGpVg
sodre
force-pushed
the
feat/provisioner-toggle
branch
from
September 9, 2026 22:18
5bb3dd3 to
80ce5f3
Compare
sodre
marked this pull request as ready for review
September 9, 2026 22:19
Contributor
There was a problem hiding this comment.
⚠️ Performance Alert ⚠️
Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.50.
| Benchmark suite | Current: 3af38d7 | Previous: 9485bcb | Ratio |
|---|---|---|---|
tests/benchmark/test_localstack.py::TestLocalStackBenchmarks::test_acquire_release_localstack |
18.103334101206823 iter/sec (stddev: 0.03938246977539952) |
28.2083270139138 iter/sec (stddev: 0.007097106796083319) |
1.56 |
tests/benchmark/test_localstack.py::TestCascadeOptimizationBenchmarks::test_cascade_with_batchgetitem_optimization |
10.594873975464141 iter/sec (stddev: 0.10143684040737734) |
29.740897984517193 iter/sec (stddev: 0.004872259368349679) |
2.81 |
tests/benchmark/test_localstack.py::TestCascadeOptimizationBenchmarks::test_cascade_multiple_resources |
9.95450375118606 iter/sec (stddev: 0.07499362296151371) |
28.42705895758373 iter/sec (stddev: 0.004165152020627401) |
2.86 |
tests/benchmark/test_localstack.py::TestLocalStackCascadeSpeculativeComparison::test_cascade_speculative_cache_cold_localstack |
15.13881447788611 iter/sec (stddev: 0.06199454924695345) |
28.30620601547383 iter/sec (stddev: 0.002496931856171016) |
1.87 |
tests/benchmark/test_localstack.py::TestLocalStackCascadeSpeculativeComparison::test_cascade_speculative_cache_warm_localstack |
18.036485534712767 iter/sec (stddev: 0.058482648534910656) |
31.87679296055283 iter/sec (stddev: 0.003964527014694207) |
1.77 |
This comment was automatically generated by workflow using github-action-benchmark.
`status` reported the aggregator but said nothing about the provisioner, which `--no-provisioner` now makes a real choice worth seeing. The template's ProvisionerFunctionName output is conditioned on DeployProvisionerLambda, so its presence is exactly whether CloudFormation created the function — and `status` already calls describe_stacks for the role ARNs, so this costs no extra API call. Unreadable stack outputs report Unknown rather than claiming Disabled, so an externally managed stack that omits the output is not misreported. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014nPStTtHkW7zqj3X3wGpVg
7 tasks
…tream `status` inferred the aggregator from the table's StreamSpecification, but that is unconditional in the template — the stream is on whether or not the aggregator was deployed — so every stack reported `Aggregator: Enabled`. Caught on real AWS: a stack deployed with --no-aggregator still claimed the aggregator was enabled. AggregatorFunctionName is conditioned on DeployAggregatorLambda, exactly like ProvisionerFunctionName, so both now come from the same describe_stacks call and share the Enabled/Disabled/Unknown rendering. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014nPStTtHkW7zqj3X3wGpVg
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
StackOptionsgainsenable_provisioner(defaultTrue), mirroringenable_aggregator. The CloudFormation template already had anEnableProvisionerparameter with conditions guarding the provisioner Lambda, its role, and its log group — but nothing on the client side ever set it, so the provisioner was always deployed.StackOptions.enable_provisioner+to_parameters()mapping toEnableProvisionerStackManager/SyncStackManagerparameter name mappingRepositoryBuilder.enable_provisioner()(and sync counterpart)--enable-provisioner/--no-provisionerondeploy, plus aProvisioner: enabled|disabledline in the deploy summarySync counterparts regenerated via
hatch run generate-sync.Bug fix: don't push Lambda code to functions CloudFormation never created
CloudFormation only creates the aggregator and provisioner functions when a role exists for them, but both deploy paths pushed code based on the
enable_*flags alone. Under--no-iam/.create_iam(False)the functions are absent and the code push failed withResourceNotFoundException, exiting 1 on an otherwise successful deploy.--no-iamnow auto-disables the provisioner the same way it auto-disables the aggregatorStackOptionsgainsdeploys_aggregator_lambda/deploys_provisioner_lambda, mirroring the template'sDeployAggregatorLambda/DeployProvisionerLambdaconditions (aggregator's external-role escape hatch included), andRepository._ensure_infrastructure_internal()gates the code deploys on those — closing the same hole on theRepository.builder()path--no-provisioner--no-aggregator--no-iam--no-iam+--aggregator-role-arnVisibility and error handling
Now that the provisioner is a real deployment choice, the CLI reports and explains its absence:
zae-limiter statusgains aProvisioner: Enabled|Disabled|Unknownline in the Infrastructure section, next to the existingAggregator:line. The template'sProvisionerFunctionNameoutput is conditioned onDeployProvisionerLambda, so its presence in the stack outputs is exactly whether CloudFormation created the function — andstatusalready callsdescribe_stacksfor the role ARNs, so this costs no extra API call. When the stack outputs cannot be read at all it reportsUnknownrather thanDisabled, so an externally managed stack (Terraform/CDK) that omits the output is not misreported.Aggregator:line inferred state from the DynamoDB table'sStreamSpecification, which the template enables unconditionally — so every stack reportedAggregator: Enabled, including one deployed with--no-aggregator.AggregatorFunctionNameis conditioned onDeployAggregatorLambdaexactly likeProvisionerFunctionName, so both states now come from the samedescribe_stackscall and share one Enabled/Disabled/Unknown renderer. The stream-spec inference is gone.zae-limiter limits plan|apply|diffnow exits 1 with an explanation when the provisioner Lambda is missing, instead of surfacing a raw botocoreResourceNotFoundExceptiontraceback.Test plan
tests/unit/test_models.py—to_parameters()emitsEnableProvisioner=false;deploys_*_lambdaproperties for default /--no-iam/ external-role / disabled configstests/unit/test_repository_builder.py—_ensure_infrastructure_internal()skips the disabled provisioner, skips both Lambdas without IAM, and still deploys the aggregator with an external roletests/unit/test_cli.py::test_deploy_with_no_provisioner_flag—--no-provisionerskips provisioner code deploymenttests/unit/test_limits_cli.py::test_invoke_provisioner_missing_function_exits_cleanly— clean exit 1, no tracebacktests/unit/test_cli.py—statusreportsEnabled/Disabled/Unknownfor both the aggregator and the provisioner (thestatusdocstring sample output, which feedsdocs/cli.mdvia mkdocs-click, was updated to match)tests/unit/test_cli.py::test_status_aggregator_disabled_despite_table_stream— pins the regression: outputs present with noAggregatorFunctionName→Disableduv run pytest tests/unit/ -q→ 2954 passedruff check,ruff format,mypy src/zae_limiter→ cleandiff-covervsorigin/main→ 100% patch coverage, 38 lines, 0 missingzae-limiter deploy --name x --no-iamcompletes with exit 0 (previously exited 1)zae-limiter deploy --name x --no-provisionercreates no{stack}-limits-provisionerfunctionBoth manual items were verified against real AWS (account 733153035800, us-east-1) with real CloudFormation stacks, all since deleted:
{stack}-limits-provisionerexists;statusprintedProvisioner: Enabled--no-provisionerstatusprintedProvisioner: Disabled;limits planprinted the new clean error (Error: Lambda function '...-limits-provisioner' not found. The limits provisioner is not deployed for this stack...) rather than a botocore traceback--no-iamCaveat on the
--no-iamcase: it reaches exit 0 only when--permission-boundaryand--role-name-formatare also supplied. With either omitted the stack rolls back before the CLI ever reaches the provisioner step, due to a pre-existing bug onmainthat this PR neither touches nor causes:PermissionBoundaryArnandRoleNameFormatinsrc/zae_limiter/infra/cfn_template.yamlare exported unconditionally while both parameters default to'', and CloudFormation rejects empty export values (Cannot export output PermissionBoundaryArn. Exported values must not be empty or whitespace-only.). Introduced in 80a1a34 and now tracked in #445. The--no-iamfix in this PR is verified working.🤖 Generated with Claude Code
https://claude.ai/code/session_014nPStTtHkW7zqj3X3wGpVg