diff --git a/.agents/references/primitive/creation.md b/.agents/references/primitive/creation.md new file mode 100644 index 0000000..8ca20ed --- /dev/null +++ b/.agents/references/primitive/creation.md @@ -0,0 +1,40 @@ +# Primitive Module Creation and Cleanup + +Use this reference when converting the skeleton into a primitive module or reviewing a conversion. + +## Creation Flow + +1. Confirm the provider and one target resource type. Review the provider documentation and a similar primitive for that provider. +2. For AWS, consult the official service API reference when practical. Translate documented ranges, enums, formats, and cross-field constraints into variable validation and descriptions. If no suitable public API reference is found after a reasonable search, proceed using provider documentation rather than retrying indefinitely. +3. Implement `versions.tf`, `variables.tf`, `main.tf`, and `outputs.tf` using the primitive standards. +4. Build `examples/complete/` with its Terraform files, an accurate README, resource naming, and any deployable prerequisite resources. +5. Before writing cloud-backed tests, run the available example validation flow: formatting and linting, init, validate, plan, apply, and destroy. Resolve failures before continuing when credentials and environment access permit. +6. Add Terratest coverage, then run the Go quality checks in the testing reference before cloud-backed test execution. +7. Build root `README.md` from `TEMPLATED_README.md`, replace the module-specific title and overview, add usage, retain the development boilerplate, and populate terraform-docs. +8. Run the cleanup and completion checks below. + +## Root and Example Documentation + +- Do not write root `README.md` from scratch. Start from `TEMPLATED_README.md` and preserve Module Development, Pre-Requisites, Pre-Commit Hooks, Local Validation, Review and Merge Process, and Automatic Updates. +- Do not remove `TEMPLATED_README.md` until all of its required sections have been incorporated into the root README. +- The generated terraform-docs block in the root README must not be empty. Generate it when available; otherwise supply accurate inputs and outputs tables. +- The handwritten usage block in `examples/complete/README.md` must match `examples/complete/main.tf`, including resources, policies, dependencies, variables, inputs, and outputs. Update it whenever the example changes. + +## Skeleton Cleanup + +Before completion: + +- Delete `examples/with_cake/` when it is part of the skeleton. +- Update the Go module path and every test import from `launch-terraform-template` to the new module repository. +- Replace template comments in `tests/testimpl/`, including empty template settings comments. +- Search all Markdown and Go files for `TODO:` and template references. Remove or replace every placeholder, including the hooks documentation placeholder. +- Search for stale resource names, package names, copied snippets, imports, and generated provider files that should not be committed. +- Run `go mod tidy` after renaming imports or adding SDK dependencies. + +## Completion Checklist + +- Root variables have explicit types, descriptions, required validation, and coherent optional objects. +- Root outputs exist in the provider schema, have descriptions, and match the intended composition interface. +- The complete example passes every root variable through, exposes test-consumed outputs, and uses the secure configuration. +- Root and example documentation are synchronized and complete. +- No skeleton resources, TODOs, template names, or stale imports remain. diff --git a/.agents/references/primitive/release-history.md b/.agents/references/primitive/release-history.md new file mode 100644 index 0000000..3f23436 --- /dev/null +++ b/.agents/references/primitive/release-history.md @@ -0,0 +1,67 @@ +# Primitive Module Guidance Release History + +This file preserves the learnings from the previous monolithic primitive module guide. + +## 2.1 + +- Skeleton updates now add the `.agents/`, `.claude/`, and `.cursor/` guidance trees. Reviewers of automatic update PRs should expect this additive template change. + +## 2.0 + +- Split the legacy GitHub agent guide into an agent-agnostic skill and focused reference files. The legacy file is a compatibility stub; operational standards, creation and cleanup guidance, test guidance, and historical rationale now live separately. + +## 1.13 + +- AWS primitive modules should consult official AWS API references where practical and translate documented ranges, enums, formats, and cross-field constraints into Terraform validations and variable descriptions. + +## 1.12 + +- Root `README.md` should be built from `TEMPLATED_README.md`; preserve skeleton boilerplate and only replace module-specific sections. + +## 1.11 + +- Optional object variables need cross-field validation for paired fields, conditional requirements, and contradictory sentinel values. + +## 1.10 + +- Add mechanical checks for loose test assertions and README/example drift. When `main.tf` changes, revisit the example README and tests. + +## 1.9 + +- Regula/OPA references were removed from the skeleton. Use the current unified Terraform check workflow and current Makefile targets. + +## 1.8 + +- Avoid Terraform reserved variable names. Verify output attributes against provider schema. Keep example outputs aligned with tests. Use account-scoped unique names where cloud resources require uniqueness. + +## 1.7 + +- Treat specific-value assertions, differentiated functional/readonly tests, security verification, README accuracy, and template cleanup as high-severity requirements. + +## 1.6 + +- Remove skeleton placeholders, generate terraform-docs output, require output descriptions, validate bounded inputs, and keep examples complete. + +## 1.5 + +- Examples should use security-first defaults and cleanup should search broadly for skeleton/template references. + +## 1.4 + +- Legacy GitHub agent files required frontmatter first to be recognized. + +## 1.3 + +- Agent guidance moved into `.github/agents/` in the older layout and gained a skeleton cleanup checklist. + +## 1.2 + +- Resource naming module usage was corrected: use `for_each = var.resource_names_map`, `class_env`, required `cloud_resource_type` and `maximum_length`, and output format references such as `module.resource_names["key"].format`. + +## 1.1 + +- Terratest should verify real resource state through provider APIs, not only Terraform outputs. + +## 1.0 + +- Initial primitive module guidance. diff --git a/.agents/references/primitive/standards.md b/.agents/references/primitive/standards.md new file mode 100644 index 0000000..df4f773 --- /dev/null +++ b/.agents/references/primitive/standards.md @@ -0,0 +1,101 @@ +# Primitive Module Standards + +Primitive modules wrap one cloud resource type and keep the interface reusable. They are comprehensive production wrappers, not minimal examples or opinionated architectures. + +## Architecture + +- Include one primary cloud resource type. +- Do not add business logic or multi-resource architecture behavior. +- Use a descriptive Terraform resource name based on the resource type; do not use `this`. +- Expose every non-deprecated, non-computed argument that a typical production deployment would configure. Optional attributes normally default to `null` so Terraform omits them unless chosen. +- Export useful resource attributes individually. Do not output the complete resource object. +- Keep secure defaults in the example, especially encryption and private access patterns where the provider supports them. + +## Required Structure + +```text +examples/complete/ + main.tf + variables.tf + outputs.tf + versions.tf + README.md +tests/ + post_deploy_functional/ + post_deploy_functional_readonly/ + testimpl/ +main.tf +variables.tf +outputs.tf +versions.tf +README.md +TEMPLATED_README.md +Makefile +go.mod +go.sum +``` + +## Provider Notes + +- Azure resources commonly use explicit `location`, `resource_group_name`, and nested configuration blocks. +- AWS resources commonly use `data.aws_region.current`, tags for grouping, and separate resources for versioning, policies, encryption, or logging when the provider models them separately. +- GCP resources commonly distinguish project, location, labels, and IAM binding patterns. + +## Variables + +- Give every variable an explicit type and useful description. +- Use `snake_case`, provider argument names where practical, and concise group headings for related inputs. +- Do not use Terraform-reserved names such as `source`, `target`, `version`, `count`, `for`, or `provider`. +- Required infrastructure inputs have no default. Optional flags should default to `false` or the safer option. Tags or labels should default to an empty map. +- Use `object()` with `optional()` attributes for structured optional configuration. +- Add validation for provider-enforced numeric bounds, enums, formats, mutually exclusive inputs, and cross-field requirements. +- Nullable validation expressions must avoid evaluating null values, for example with a conditional expression. Use `try()` for nested optional object attributes. +- Optional object descriptions must explain conditional field requirements and prohibited combinations. + +For every optional object, validate all of the following where applicable: + +- Individual enum, range, and format constraints. +- Fields that must be provided together. +- Fields required when another field is set or active. +- Fields prohibited by an off or disabled sentinel value. + +## Resources and Outputs + +- Map variables directly to resource arguments. Use dynamic blocks for optional nested blocks when the provider supports them. +- Avoid lifecycle blocks and data sources unless they are necessary for the resource contract. +- AWS often models configuration as separate resources rather than nested blocks; follow the provider schema. +- Place tags or labels at the end of resource blocks where the local provider convention supports it. +- Every output needs a short description and must reference an attribute verified in the provider schema. +- Use generic output names such as `id`, `name`, `arn`, `url`, or `fqdn`, without resource-type prefixes. +- When `id` is the same value as another output, say so in the `id` description. +- Do not mark outputs sensitive by default; callers handle sensitivity in their own interface. + +## Version Constraints + +- Set a Terraform version and provider constraints that avoid untested major upgrades. +- Pin providers to an appropriate compatible minor range, following the current skeleton and provider-specific precedent. +- Keep provider configuration out of the root module; examples own provider configuration. + +## AWS API Reference Check + +For AWS primitive modules, consult the official AWS service API reference when practical. Derive validation ranges, enum values, formats, and cross-field constraints from the API reference and provider schema. If no suitable public API reference is found after a reasonable search, skip the step and do not retry indefinitely. + +## Example Requirements + +- The example must pass through every root module variable. +- Mutually exclusive root variables should both be represented with coherent defaults. +- The example should demonstrate the secure pattern for the resource type. +- `examples/complete/README.md` usage must exactly match `examples/complete/main.tf`. +- Example outputs must expose every value used by tests. +- Use the Launch resource naming module correctly: `for_each = var.resource_names_map`, `class_env`, numeric `instance_env` and `instance_resource`, and each entry's `name` and `max_length` for `cloud_resource_type` and `maximum_length`. +- Read naming outputs by map key, for example `module.resource_names[""].standard`. Account-scoped names need a random suffix when concurrent or sequential tests could collide. +- Use the provider's regional convention for the naming module. AWS and GCP patterns may need hyphens removed from region names. + +## Common Anti-Patterns + +- Wrapping more than one primary resource type. +- Using `assert.NotEmpty` where a specific expected value is known. +- Copying functional tests into readonly tests unchanged. +- Leaving an empty terraform-docs block. +- Writing root `README.md` from scratch and dropping skeleton boilerplate. +- Leaving `TEMPLATED_README.md` content unincorporated. diff --git a/.agents/references/primitive/testing.md b/.agents/references/primitive/testing.md new file mode 100644 index 0000000..072d4e2 --- /dev/null +++ b/.agents/references/primitive/testing.md @@ -0,0 +1,62 @@ +# Primitive Module Testing + +Primitive tests must prove the Terraform interface and the cloud resource behavior. + +## Test Shape + +- `post_deploy_functional` performs write or behavior-changing operations where the resource type supports them. +- `post_deploy_functional_readonly` performs read-only verification only. +- The two test packages must call different implementation functions. +- Shared setup and provider clients belong in `tests/testimpl/`. +- Use `t.Parallel()` where the test framework and provisioned resources support concurrent execution. +- Functional tests deploy and clean up the complete example. Readonly tests assume deployed infrastructure. + +## Assertions + +- Assert specific expected values from Terraform inputs, computed naming outputs, provider API responses, or known example configuration. +- Avoid `assert.NotEmpty` and `require.NotEmpty` when the value is knowable. +- Use `require` for attributes that must exist before deeper assertions. +- Verify security settings through the cloud provider API when the module configures encryption, policies, public access controls, identity, or networking. +- For a required security API attribute, use `require.True(t, ok, ...)` before assertions rather than an `if ok` branch that silently skips a missing setting. +- The limited valid uses of `NotEmpty` are checking a collection before indexing it or verifying a required environment variable. Configuration and API-returned values should have known expected values. + +## Readonly Tests + +Readonly tests must not create, update, invoke mutating operations, publish messages, write objects, or alter state. + +## Readonly Test Runner + +The readonly package (`tests/post_deploy_functional_readonly`) must: + +- Call `lib.RunNonDestructiveTest`, not `lib.RunSetupTestTeardown`. The setup/teardown runner turns a readonly suite into a full apply, test, and destroy flow. +- Pass a `tests/testimpl` function whose name begins with `TestComposable`, such as `TestComposableCompleteReadOnly`. `lcaf-component-terratest` fails the test at runtime when the name does not meet this requirement. + +CI excludes the readonly binary from its test command, so both requirements must be correct by construction. + +## Functional Tests + +Functional tests should exercise the resource behavior, not just Terraform outputs. Examples include writing and reading data, invoking a function, publishing a message, checking access policies, or using the relevant provider SDK operation. + +## Go Quality + +- Keep provider SDK helpers small and purpose-focused. +- Run `go mod tidy` after adding SDK dependencies. +- Before cloud-backed test runs, run the available Go linter, `go get -u ./...`, `go mod tidy`, and `go build ./...`; resolve failures before running the wider test flow. +- Build or run targeted Go tests before cloud-backed runs when possible. +- Keep example outputs and Go expected values synchronized. + +## Provider State Verification + +- Verify both Terraform outputs and real cloud state. Prefer Terratest provider helpers, then use the provider SDK where no helper covers the resource. +- Read cloud credentials and region or project context from the environment. Fail clearly when required configuration is missing. +- Compare API values to Terraform outputs or known example values, including security configuration such as encryption, TLS, access policy, private networking, or identity settings. + +## Test Review Checklist + +- No configuration assertion uses `assert.NotEmpty` or `require.NotEmpty` when a specific value is available. +- Functional and readonly entrypoints call different `tests/testimpl/` functions and are not copies of each other. +- The readonly entrypoint uses `lib.RunNonDestructiveTest` and a `TestComposable*` implementation function. +- Functional coverage includes a safe write or behavior operation when the resource supports one. +- Readonly coverage performs no writes, invocation, publishing, resource creation, updates, or state changes. +- Security-critical provider attributes are required and compared to expected values. +- Test-consumed Terraform outputs exist in `examples/complete/outputs.tf`. diff --git a/.agents/references/reference-architecture/release-history.md b/.agents/references/reference-architecture/release-history.md new file mode 100644 index 0000000..a9f49c0 --- /dev/null +++ b/.agents/references/reference-architecture/release-history.md @@ -0,0 +1,51 @@ +# Reference Architecture Guidance Release History + +This file preserves the learnings from the previous monolithic reference architecture guide. + +## 3.1 + +- Skeleton updates now add the `.agents/`, `.claude/`, and `.cursor/` guidance trees. Reviewers of automatic update PRs should expect this additive template change. + +## 2.0 + +- Readonly tests must use non-destructive helpers. Add a skeleton transformation verification gate. Include Lambda source directories when examples need deployable source. Avoid duplicate CloudWatch log groups with `terraform-aws-modules/lambda/aws`. Use registry module source formats. Keep output prefixes consistent. Prefer `create_*` feature flags. Strengthen the ban on loose non-empty assertions. + +## 1.9 + +- Complete examples must not redundantly compose `resource_names`. Do not commit `providers.tf` in examples. Use `RunNonDestructiveTest` for readonly tests. Check community module compatibility before choosing versions. Remove stale skeleton names from go.mod, imports, and test functions. Avoid duplicate IAM permissions. + +## 1.8 + +- Strengthen specific-value assertions, differentiate functional and readonly tests, mandate KMS/encryption verification where configured, and require README tables to match actual code. + +## 1.7 + +- Keep cloud-provider guidance balanced. Add Azure networking and security patterns alongside AWS examples. + +## 1.6 + +- Provider SDK verification examples were added for AWS services and read-only versus destructive test flows. + +## 1.5 + +- SDK verification is mandatory for meaningful tests. IAM should use least privilege and avoid duplicated policy attachments. Community module versions need compatibility checks. + +## 1.4 + +- Legacy GitHub agent files required frontmatter first to be recognized. + +## 1.3 + +- Agent guidance moved into `.github/agents/` in the older layout and gained a skeleton cleanup checklist. + +## 1.2 + +- Resource naming module usage was corrected: use `for_each = var.resource_names_map`, `class_env`, required `cloud_resource_type` and `maximum_length`, output format references, and no obsolete `resource_names_strategy` variable. + +## 1.1 + +- Terratest should verify real resource state through provider APIs, and reference architecture tests should cover optional features enabled by examples. + +## 1.0 + +- Initial reference architecture guidance. diff --git a/.agents/references/reference-architecture/standards.md b/.agents/references/reference-architecture/standards.md new file mode 100644 index 0000000..f1ef87d --- /dev/null +++ b/.agents/references/reference-architecture/standards.md @@ -0,0 +1,72 @@ +# Reference Architecture Standards + +Reference architecture modules compose multiple primitives and selected community modules into complete, opinionated infrastructure patterns. + +## Architecture + +- Compose multiple resources or modules into a coherent pattern. +- Use Launch primitive modules from `terraform.registry.launch.nttdata.com` when available. +- Use mature public registry modules for complex AWS patterns when they reduce implementation risk. +- Use registry source addresses, not `git::` module URLs. +- Use bounded version constraints for internal and community modules. +- Keep feature flags named `create_*` for optional resources. + +## Required Structure + +```text +examples/complete/ + main.tf + variables.tf + outputs.tf + versions.tf + test.tfvars + README.md +tests/ +main.tf +variables.tf +outputs.tf +versions.tf +locals.tf +README.md +Makefile +go.mod +go.sum +``` + +## Composition + +- Start with the Launch resource naming module. +- Azure architectures normally create or consume a resource group and pass `location`. +- AWS architectures normally use `data.aws_region.current` and tags. +- Do not ask consumers to provide names for resources the architecture owns. +- Do not duplicate naming modules inside examples when the root module already handles naming. + +## Variables + +- Include naming context variables required by the resource naming module. +- Use high-level service variables rather than exposing every nested primitive argument directly. +- Validate mutually exclusive fields such as `name` and `name_prefix`. +- Complex object variables need validation for required field groups and contradictory combinations. +- Optional features should have explicit `create_*` booleans and object inputs with safe defaults. + +## Outputs + +- Expose aggregate identifiers, names, ARNs/IDs, endpoints, and optional feature state. +- Prefix outputs consistently by resource or feature. +- Avoid exposing raw nested module internals unless they are part of the intended public contract. + +## Example Requirements + +- `examples/complete` is the reference implementation and should enable meaningful optional features. +- Do not commit `examples/complete/providers.tf`; the build system generates `provider.tf`. +- Include required runtime source assets for examples, such as Lambda source directories. +- Keep example README usage, variables, and outputs synchronized with actual example files. + +## Common Anti-Patterns + +- Replacing composition with one giant raw resource implementation. +- Using `git::` module sources. +- Duplicating provider blocks in examples. +- Composing resource naming in both root and example. +- Creating duplicate IAM permissions or duplicate CloudWatch log groups when community modules can already manage them. +- Using inconsistent output prefixes. diff --git a/.agents/references/reference-architecture/testing.md b/.agents/references/reference-architecture/testing.md new file mode 100644 index 0000000..20a7543 --- /dev/null +++ b/.agents/references/reference-architecture/testing.md @@ -0,0 +1,43 @@ +# Reference Architecture Testing + +Reference architecture tests must verify the composed infrastructure, not just Terraform outputs. + +## Test Scope + +- Validate each major composed resource through the provider API where practical. +- Verify optional features that the complete example enables. +- Check security controls such as encryption, private networking, IAM policies, logging, and monitoring. +- Verify resource naming outputs against expected naming formats. + +## Destructive and Readonly Tests + +- Destructive or functional tests may create and exercise resources. +- Readonly tests must not mutate state. +- Do not copy the destructive test body into the readonly test unchanged. + +## Readonly Test Runner + +The readonly package (`tests/post_deploy_functional_readonly`) must: + +- Call `lib.RunNonDestructiveTest`, not `lib.RunSetupTestTeardown`. The setup/teardown runner turns a readonly suite into a full apply, test, and destroy flow. +- Pass a `tests/testimpl` function whose name begins with `TestComposable`, such as `TestComposableCompleteReadOnly`. `lcaf-component-terratest` fails the test at runtime when the name does not meet this requirement. + +CI excludes the readonly binary from its test command, so both requirements must be correct by construction. + +## Assertions + +- Prefer `assert.Equal`, `assert.Contains`, and provider-specific state checks with known expected values. +- Avoid `assert.NotEmpty` where the expected value can be derived from `test.tfvars`, module outputs, or provider API response fields. +- Use `require` for required API response fields before asserting nested values. + +## Provider-Specific Checks + +- AWS Lambda architectures should verify Lambda configuration, runtime, handler, environment variables, CloudWatch log behavior, IAM role/policies, KMS encryption, and source package behavior where applicable. +- Azure PostgreSQL-style architectures should verify server configuration, networking, private DNS, firewall/public access settings, identity, and encryption where applicable. +- For other providers and services, derive equivalent checks from the provider SDK and service API. + +## Go Quality + +- Keep provider SDK clients and test helpers in reusable test implementation files. +- Run `go mod tidy` after adding or changing SDK dependencies. +- Build tests before cloud-backed runs when credentials are not available. diff --git a/.agents/references/shared/terraform-module-standards.md b/.agents/references/shared/terraform-module-standards.md new file mode 100644 index 0000000..41d8575 --- /dev/null +++ b/.agents/references/shared/terraform-module-standards.md @@ -0,0 +1,55 @@ +# Shared Terraform Module Standards + +These standards apply to both primitive and reference architecture modules. + +## Providers + +- Supported providers are Azure (`azurerm`), AWS (`aws`), and Google Cloud (`google`). +- Keep provider-specific guidance labeled by provider. +- Do not add provider-specific examples to shared rules unless each provider is represented or the example is explicitly scoped. + +## Naming + +- Repository names use `tf--module_primitive-` for primitives. +- Repository names use `tf--module_reference-` for reference architectures. +- Terraform variables use `snake_case`. +- Avoid Terraform reserved names for variables, including `source`, `target`, `version`, `count`, `for`, and `provider`. +- Use the Launch resource naming module for generated cloud resource names. +- `resource_names_map` is used with `for_each`; read outputs with `module.resource_names[""].`. + +## Terraform Interface + +- Every variable needs an explicit type and useful description. +- Add validation for bounded numeric values, enums, formats, mutually exclusive fields, and cross-field requirements. +- Optional object variables must validate coherent combinations, not just individual field values. +- Outputs should expose useful resource attributes individually and describe what each output represents. +- If an output `id` equals another attribute, say so in the description. +- Provider and module version constraints should be bounded enough to avoid untested major upgrades. + +## Documentation + +- Keep the root `README.md` derived from `TEMPLATED_README.md`. +- Replace module title and overview, add accurate usage, run terraform-docs, and preserve Module Development, Local Validation, Review & Merge Process, and Automatic Updates sections. +- `examples/complete/README.md` must match `examples/complete/main.tf`, variables, and outputs. +- Do not leave empty terraform-docs blocks. + +## Validation + +- Prefer `mise run ` when a suitable task exists. +- Otherwise use `mise exec -- ` for Terraform, Go, pre-commit, and related tools. +- Run formatting and linting before broader test flows. +- For examples, validate initialization, Terraform validation, and plan where credentials and backend constraints allow. +- For Go tests, run `go mod tidy`, build or targeted tests, and broader Terratest only when the required cloud access is available. +- Keep the skeleton's `configure`, `check`, `lint`, `test`, and `docs` Makefile targets. Use those targets when they fit the task instead of recreating their underlying workflow. +- `provider.tf` in an example is generated by the Makefile. Do not edit or commit it unless the current template explicitly makes it a source file. + +## Cleanup + +Before completion, search for: + +- `TODO` +- skeleton repository names +- template resource names +- stale test imports or package names +- copied example snippets that no longer match code +- generated provider files that should not be committed diff --git a/.agents/skills/primitive-module/SKILL.md b/.agents/skills/primitive-module/SKILL.md new file mode 100644 index 0000000..03a21e0 --- /dev/null +++ b/.agents/skills/primitive-module/SKILL.md @@ -0,0 +1,39 @@ +--- +name: primitive-module +description: Create, clean up, validate, test, or document a Launch Terraform primitive module. +--- + +# Primitive Module Workflow + +Use this skill for repositories named `tf--module_primitive-` or tasks that explicitly ask for a primitive module. + +## Read First + +1. Read `AGENTS.md`. +2. Read `.agents/references/shared/terraform-module-standards.md`. +3. Read `.agents/references/primitive/standards.md`. +4. Read `.agents/references/primitive/creation.md` when creating a module from this skeleton or completing template cleanup. +5. Read `.agents/references/primitive/testing.md` before writing or changing tests. +6. Read `.agents/references/primitive/release-history.md` only when changing this guidance or investigating why a rule exists. + +## Workflow + +1. Confirm the target cloud provider and single resource type. +2. Inspect the existing repository structure and current Terraform files. +3. Remove skeleton remnants and rename template resources, tests, imports, package names, and README content. +4. Implement one primitive resource interface with explicit variable types, descriptions, validations, resource outputs, and provider constraints. +5. Build `examples/complete/` as the canonical secure usage example. +6. Keep `README.md` derived from `TEMPLATED_README.md`; replace the module-specific sections and preserve skeleton development boilerplate. +7. Add or update Terratest code so assertions verify specific expected values and security settings through provider APIs when applicable. +8. Run focused validation, then broader checks such as formatting, linting, Terraform init/validate/plan for the example, README generation, and Go test build or Terratest where practical. + +## Completion Gate + +- The module contains one primary resource type and no architecture-level business logic. +- Example variables pass through all root variables. +- Example README matches `examples/complete/main.tf`. +- Root README retains the non-module-specific sections from `TEMPLATED_README.md` and has a populated terraform-docs block. +- Functional and readonly tests are meaningfully different. +- Tests avoid `assert.NotEmpty` or `require.NotEmpty` when a specific expected value is known. +- Security features configured by the module are verified through provider APIs where practical. +- No TODO placeholders, skeleton resources, stale imports, or template names remain. diff --git a/.agents/skills/reference-architecture/SKILL.md b/.agents/skills/reference-architecture/SKILL.md new file mode 100644 index 0000000..1875114 --- /dev/null +++ b/.agents/skills/reference-architecture/SKILL.md @@ -0,0 +1,37 @@ +--- +name: reference-architecture +description: Create, clean up, validate, test, or document a Launch Terraform reference architecture module. +--- + +# Reference Architecture Workflow + +Use this skill for repositories named `tf--module_reference-` or tasks that explicitly ask for a reference architecture. + +## Read First + +1. Read `AGENTS.md`. +2. Read `.agents/references/shared/terraform-module-standards.md`. +3. Read `.agents/references/reference-architecture/standards.md`. +4. Read `.agents/references/reference-architecture/testing.md` before writing or changing tests. +5. Read `.agents/references/reference-architecture/release-history.md` only when changing this guidance or investigating why a rule exists. + +## Workflow + +1. Confirm the target cloud provider, architecture purpose, and expected optional features. +2. Inspect existing module files and examples before changing code. +3. Remove skeleton remnants and rename template resources, tests, imports, package names, and README content. +4. Compose internal primitive modules and selected mature community modules using registry source addresses and bounded version constraints. +5. Use the resource naming module for all generated names. Do not ask consumers to provide names that the architecture should own. +6. Model optional features with explicit `create_*` flags and coherent object validation. +7. Build `examples/complete/` as the canonical secure architecture example, including required source assets such as Lambda source directories where applicable. +8. Add or update Terratest coverage that validates the composed resources through provider APIs, differentiates destructive and readonly flows, and checks optional features enabled by the example. +9. Run focused validation, then broader checks such as formatting, linting, Terraform init/validate/plan for the example, README generation, and Go test build or Terratest where practical. + +## Completion Gate + +- The module composes a complete pattern rather than wrapping a single resource. +- Internal modules use Launch registry addresses; community modules use public registry addresses, not `git::` URLs. +- Example code does not duplicate provider configuration generated by the build system. +- Outputs have consistent prefixes and expose useful aggregate state. +- Readonly tests use non-destructive test helpers. +- No skeleton resources, stale imports, TODO placeholders, or template names remain. diff --git a/.claude/rules/agent-routing.md b/.claude/rules/agent-routing.md new file mode 100644 index 0000000..1cfb8d2 --- /dev/null +++ b/.claude/rules/agent-routing.md @@ -0,0 +1,9 @@ +# Agent Routing + +Use `AGENTS.md` as the shared baseline. For task workflows, prefer the files under `.agents/skills/` rather than expanding always-loaded Claude instructions. + +- Primitive module work: `.agents/skills/primitive-module/SKILL.md` +- Reference architecture work: `.agents/skills/reference-architecture/SKILL.md` +- Shared standards: `.agents/references/shared/terraform-module-standards.md` + +Do not read every reference file up front. Load the specific reference files routed by the selected workflow. diff --git a/.claude/rules/terraform-files.md b/.claude/rules/terraform-files.md new file mode 100644 index 0000000..09a1980 --- /dev/null +++ b/.claude/rules/terraform-files.md @@ -0,0 +1,15 @@ +--- +paths: + - "**/*.tf" + - "examples/**/*.tf" + - "tests/**/*.go" + - "README.md" + - "TEMPLATED_README.md" +--- + +# Terraform Module Files + +- Keep module type boundaries clear: primitives wrap one resource type; reference architectures compose complete patterns. +- Keep examples synchronized with root variables, outputs, and README usage snippets. +- Prefer provider API constraints, Terraform validation blocks, and specific test assertions over loose or non-empty checks. +- Remove skeleton placeholders and stale template references before completion. diff --git a/.copier-answers.yml b/.copier-answers.yml new file mode 100644 index 0000000..da645a3 --- /dev/null +++ b/.copier-answers.yml @@ -0,0 +1,5 @@ +# NEVER EDIT MANUALLY +# https://copier.readthedocs.io/en/stable/updating/#never-change-the-answers-file-manually + +_commit: 0.7.1 +_src_path: gh:launchbynttdata/launch-terraform-skeleton diff --git a/.cursor/rules/launch-agent-routing.mdc b/.cursor/rules/launch-agent-routing.mdc new file mode 100644 index 0000000..736f8f0 --- /dev/null +++ b/.cursor/rules/launch-agent-routing.mdc @@ -0,0 +1,11 @@ +--- +alwaysApply: true +--- + +# Launch Agent Routing + +- Use `AGENTS.md` as the shared baseline for this repository. +- For primitive module creation or cleanup, follow `.agents/skills/primitive-module/SKILL.md`. +- For reference architecture creation or cleanup, follow `.agents/skills/reference-architecture/SKILL.md`. +- Read files under `.agents/references/` only when a selected workflow routes you there. +- Keep generated module changes small, explicit, and validated. diff --git a/.cursor/rules/primitive-module-workflow.mdc b/.cursor/rules/primitive-module-workflow.mdc new file mode 100644 index 0000000..11b086e --- /dev/null +++ b/.cursor/rules/primitive-module-workflow.mdc @@ -0,0 +1,18 @@ +--- +description: "Terraform primitive module creation, cleanup, validation, testing, and documentation workflow" +alwaysApply: false +--- + +# Primitive Module Workflow + +Use this rule when creating or modifying a `tf--module_primitive-` repository. + +Follow `.agents/skills/primitive-module/SKILL.md`. + +Read these references only as needed: + +- `.agents/references/shared/terraform-module-standards.md` +- `.agents/references/primitive/standards.md` +- `.agents/references/primitive/creation.md` +- `.agents/references/primitive/testing.md` +- `.agents/references/primitive/release-history.md` diff --git a/.cursor/rules/reference-architecture-workflow.mdc b/.cursor/rules/reference-architecture-workflow.mdc new file mode 100644 index 0000000..99b64e6 --- /dev/null +++ b/.cursor/rules/reference-architecture-workflow.mdc @@ -0,0 +1,17 @@ +--- +description: "Terraform reference architecture creation, cleanup, validation, testing, and documentation workflow" +alwaysApply: false +--- + +# Reference Architecture Workflow + +Use this rule when creating or modifying a `tf--module_reference-` repository. + +Follow `.agents/skills/reference-architecture/SKILL.md`. + +Read these references only as needed: + +- `.agents/references/shared/terraform-module-standards.md` +- `.agents/references/reference-architecture/standards.md` +- `.agents/references/reference-architecture/testing.md` +- `.agents/references/reference-architecture/release-history.md` diff --git a/.github/agents/primitive-module-creator.agent.md b/.github/agents/primitive-module-creator.agent.md new file mode 100644 index 0000000..831d065 --- /dev/null +++ b/.github/agents/primitive-module-creator.agent.md @@ -0,0 +1,24 @@ +--- +name: Terraform Primitive Module Creator +description: Compatibility stub for agents looking for the legacy primitive module creator guide. +--- + + + +# Terraform Primitive Module Creator + +The primitive module guide has moved to the agent-agnostic instruction layout. + +Use these files instead: + +- Baseline operating rules: [AGENTS.md](../../AGENTS.md) +- Reusable workflow: [.agents/skills/primitive-module/SKILL.md](../../.agents/skills/primitive-module/SKILL.md) +- Shared Terraform standards: [.agents/references/shared/terraform-module-standards.md](../../.agents/references/shared/terraform-module-standards.md) +- Primitive interface standards: [.agents/references/primitive/standards.md](../../.agents/references/primitive/standards.md) +- Creation, cleanup, and documentation guidance: [.agents/references/primitive/creation.md](../../.agents/references/primitive/creation.md) +- Terratest guidance: [.agents/references/primitive/testing.md](../../.agents/references/primitive/testing.md) +- Condensed release history and preserved learnings: [.agents/references/primitive/release-history.md](../../.agents/references/primitive/release-history.md) +- Cursor project rule: [.cursor/rules/primitive-module-workflow.mdc](../../.cursor/rules/primitive-module-workflow.mdc) +- Claude routing rules: [.claude/rules/agent-routing.md](../../.claude/rules/agent-routing.md) and [.claude/rules/terraform-files.md](../../.claude/rules/terraform-files.md) + +This file remains only for compatibility with tools or prompts that still inspect `.github/agents/`. diff --git a/.github/agents/reference-architecture-creator.agent.md b/.github/agents/reference-architecture-creator.agent.md new file mode 100644 index 0000000..d7259a2 --- /dev/null +++ b/.github/agents/reference-architecture-creator.agent.md @@ -0,0 +1,23 @@ +--- +name: Terraform Reference Architecture Creator +description: Compatibility stub for agents looking for the legacy reference architecture creator guide. +--- + + + +# Terraform Reference Architecture Creator + +The reference architecture guide has moved to the agent-agnostic instruction layout. + +Use these files instead: + +- Baseline operating rules: [AGENTS.md](../../AGENTS.md) +- Codex-style workflow skill: [.agents/skills/reference-architecture/SKILL.md](../../.agents/skills/reference-architecture/SKILL.md) +- Cursor project rule: [.cursor/rules/reference-architecture-workflow.mdc](../../.cursor/rules/reference-architecture-workflow.mdc) +- Claude routing rules: [.claude/rules/agent-routing.md](../../.claude/rules/agent-routing.md) +- Shared Terraform standards: [.agents/references/shared/terraform-module-standards.md](../../.agents/references/shared/terraform-module-standards.md) +- Reference architecture standards: [.agents/references/reference-architecture/standards.md](../../.agents/references/reference-architecture/standards.md) +- Reference architecture testing guidance: [.agents/references/reference-architecture/testing.md](../../.agents/references/reference-architecture/testing.md) +- Condensed release history: [.agents/references/reference-architecture/release-history.md](../../.agents/references/reference-architecture/release-history.md) + +This file remains only for compatibility with tools or prompts that still inspect `.github/agents/`. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 33f0854..18d7ff1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,32 +1,34 @@ version: 2 - multi-ecosystem-groups: - module: + tests: + commit-message: + prefix: "chore" + include: scope schedule: - interval: "weekly" + interval: "daily" workflows: + commit-message: + prefix: "chore" + include: scope schedule: - interval: "weekly" - + interval: "daily" updates: - package-ecosystem: "github-actions" directory: "/" multi-ecosystem-group: "workflows" - patterns: ["*"] - + patterns: [ "*" ] + exclude-paths: + - .github/workflows/pull-request-automation.yml + - .github/workflows/pull-request-check-terraform.yml + - .github/workflows/pull-request-dependabot-automerge.yml + - .github/workflows/pull-request-label.yml + - .github/workflows/pull-request-precommit-checks.yml + - .github/workflows/rebuild-cache-on-merge.yml + - .github/workflows/release-publish.yml + - .github/workflows/update-from-skeleton.yml - package-ecosystem: "gomod" directory: "/" - multi-ecosystem-group: "module" - patterns: ["*"] - - - package-ecosystem: "terraform" - directory: "/" - multi-ecosystem-group: "module" - patterns: ["*"] - ignore: - - dependency-name: "hashicorp/*" - update-types: ["version-update:semver-major"] - - dependency-name: "microsoft/*" - update-types: ["version-update:semver-major"] - - dependency-name: "azure/*" - update-types: ["version-update:semver-major"] + multi-ecosystem-group: "tests" + patterns: [ "*" ] + cooldown: + default-days: 14 diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 6ab7d45..6fe7e59 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -13,37 +13,53 @@ template: | categories: - title: ":warning: Breaking Changes" labels: - - "major" + - "breaking change" - title: "๐Ÿš€ Features" labels: - - "minor" + - "feature" - title: "๐Ÿ”ง Fixes" collapse-after: 3 labels: - - "patch" - -autolabeler: - - label: "major" - branch: - - '/(patch|bug|fix|feature|feat|chore)!\/.+/' - - label: "minor" - branch: - - '/(feature|feat)\/.+/' - - label: "patch" - branch: - - '/(patch|bug|fix|chore)\/.+/' + - "fix" + - title: "๐Ÿ“ Documentation" + collapse-after: 3 + labels: + - "documentation" + - title: "๐Ÿงช Test" + collapse-after: 3 + labels: + - "test" + - title: "โš™๏ธ CI/CD" + collapse-after: 3 + labels: + - "CI/CD" + - title: "โ™ป๏ธ Refactor" + collapse-after: 3 + labels: + - "refactor" + - title: "โšก Performance" + collapse-after: 3 + labels: + - "performance" + - title: "๐Ÿงน Chore" + collapse-after: 3 + labels: + - "chore" + - title: "โช Revert" + collapse-after: 3 + labels: + - "revert" change-template: "- $TITLE @$AUTHOR (#$NUMBER)" version-resolver: major: labels: - - "major" + - "breaking change" minor: labels: - - "minor" - patch: - labels: - - "patch" - - "dependencies" + - "feature" default: patch + +exclude-labels: + - "skip-changelog" diff --git a/.github/scripts/validate-readonly-test.sh b/.github/scripts/validate-readonly-test.sh new file mode 100755 index 0000000..d6039a1 --- /dev/null +++ b/.github/scripts/validate-readonly-test.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# +# Validates the post-deploy readonly test wiring. +# +# CI never executes the readonly test binary -- `make test` excludes it +# (go list ./tests/... | grep -v post_deploy_functional_readonly) because +# post-deploy tests need live infrastructure. That leaves two defects +# invisible to CI until they hit a real test run: +# +# 1. Wrong runner: the readonly suite calls lib.RunSetupTestTeardown +# (apply -> test -> destroy) instead of lib.RunNonDestructiveTest, so +# the "read-only" suite is not actually read-only. +# 2. Wrong name: the testimpl function passed to RunNonDestructiveTest does +# not start with TestComposable. lcaf-component-terratest's +# demandAllTests2RunAreComposableOnes calls t.FailNow() in that case, so +# the readonly suite hard-fails the moment it runs. +# +# This hook catches both statically, at commit time. To stay robust to source +# formatting it strips // line comments and flattens newlines before matching, +# so multiline calls and identifiers mentioned in comments are handled. See +# launchbynttdata/launch-workflows#92. + +set -euo pipefail + +# Fixed path -- matches the hook's files: filter in .pre-commit-config.yaml and +# the Makefile's GO_TEST_READONLY_DIRECTORY default. (The directory is not +# configurable here on purpose: the pre-commit files: filter can't read env +# vars, so honoring an override in the script alone would be misleading.) +readonly_dir="tests/post_deploy_functional_readonly" + +status=0 + +# Nothing to validate if the module has no readonly suite. +[ -d "$readonly_dir" ] || exit 0 + +go_files="$(find "$readonly_dir" -name '*.go' -type f 2>/dev/null || true)" +[ -n "$go_files" ] || exit 0 + +# Comment-stripped, newline-flattened view of the readonly Go sources, so the +# checks below are robust to multiline calls and to identifiers appearing in +# comments. (Strips // line comments; block comments are not handled.) +flat="$(cat $go_files | sed -e 's://.*$::' | tr '\n' ' ')" + +# 1. Wrong runner: the destructive runner must not be *called* in the readonly suite. +if printf '%s' "$flat" | grep -Eq 'RunSetupTestTeardown[[:space:]]*\('; then + echo "ERROR: $readonly_dir calls lib.RunSetupTestTeardown." >&2 + echo " The readonly suite must use lib.RunNonDestructiveTest -- it must not apply/destroy." >&2 + status=1 +fi + +# The readonly suite must call the non-destructive runner. +if ! printf '%s' "$flat" | grep -Eq 'RunNonDestructiveTest[[:space:]]*\('; then + echo "ERROR: $readonly_dir does not call lib.RunNonDestructiveTest." >&2 + echo " The readonly suite must drive its assertions through that runner." >&2 + status=1 +fi + +# 2. Wrong name: every function passed to RunNonDestructiveTest must start with +# TestComposable (the lcaf runtime requirement that CI cannot see). The last +# argument is the testimpl function; tolerate a gofmt trailing comma and an +# optional package qualifier. +names="$(printf '%s' "$flat" \ + | grep -oE 'RunNonDestructiveTest[[:space:]]*\([^)]*\)' \ + | sed -E -e 's/^[^(]*\(//' -e 's/\)$//' -e 's/[[:space:]]//g' -e 's/,+$//' \ + | awk -F',' '{print $NF}' \ + | awk -F'.' '{print $NF}' || true)" + +while IFS= read -r fn; do + [ -n "$fn" ] || continue + case "$fn" in + TestComposable*) : ;; + *) + echo "ERROR: function '$fn' is passed to RunNonDestructiveTest in $readonly_dir but does not start with 'TestComposable'." >&2 + echo " lcaf-component-terratest requires a TestComposable* function; CI does not catch this." >&2 + status=1 + ;; + esac +done <&2 + echo "Readonly test validation failed. Background: launchbynttdata/launch-workflows#92" >&2 +fi + +exit "$status" diff --git a/.github/workflows/pull-request-automation.yml b/.github/workflows/pull-request-automation.yml new file mode 100644 index 0000000..f457e2d --- /dev/null +++ b/.github/workflows/pull-request-automation.yml @@ -0,0 +1,14 @@ +name: Automated Approvals + +on: + pull_request: + types: [ opened, reopened, synchronize ] + +jobs: + automated-approvals: + name: Automated Approvals + permissions: + pull-requests: read + contents: read + uses: launchbynttdata/launch-workflows/.github/workflows/reusable-pr-automated-approvals.yml@edddecdce4590f94f30d0fdfdf991d8a692fe8dd + secrets: inherit # pragma: allowlist secret diff --git a/.github/workflows/pull-request-check-terraform.yml b/.github/workflows/pull-request-check-terraform.yml new file mode 100644 index 0000000..1b6d29f --- /dev/null +++ b/.github/workflows/pull-request-check-terraform.yml @@ -0,0 +1,20 @@ +name: "Check Terraform" + +on: + pull_request: + types: [ opened, reopened, synchronize, ready_for_review ] + branches: [ main ] + +jobs: + check: + name: "Reusable Workflow" + permissions: + contents: read + pull-requests: read + id-token: write + statuses: write + uses: launchbynttdata/launch-workflows/.github/workflows/reusable-terraform-check.yml@edddecdce4590f94f30d0fdfdf991d8a692fe8dd + with: + auth_method: ${{ startsWith(github.event.repository.name, 'tf-aws') && 'aws' || + startsWith(github.event.repository.name, 'tf-az') && 'azure' || '' }} + secrets: inherit # pragma: allowlist secret diff --git a/.github/workflows/pull-request-dependabot-automerge.yml b/.github/workflows/pull-request-dependabot-automerge.yml new file mode 100644 index 0000000..4bf4643 --- /dev/null +++ b/.github/workflows/pull-request-dependabot-automerge.yml @@ -0,0 +1,14 @@ +name: Dependabot Auto-Merge + +on: + pull_request: + types: [ opened, reopened, synchronize ] + +jobs: + dependabot-automerge: + name: Dependabot Auto-Merge + permissions: + contents: write + pull-requests: write + uses: launchbynttdata/launch-workflows/.github/workflows/reusable-pr-dependabot-automerge.yml@edddecdce4590f94f30d0fdfdf991d8a692fe8dd + secrets: inherit # pragma: allowlist secret diff --git a/.github/workflows/pull-request-label.yml b/.github/workflows/pull-request-label.yml index 6a2936b..038b727 100644 --- a/.github/workflows/pull-request-label.yml +++ b/.github/workflows/pull-request-label.yml @@ -1,15 +1,14 @@ -name: Label Pull Request +name: Validate PR Title on: pull_request: - types: [opened, reopened, synchronize] + types: [ opened, reopened, edited, ready_for_review, synchronize ] jobs: - check: - name: "Label Pull Request" + validate-title: + name: Validate PR Title permissions: contents: read - issues: write pull-requests: write - uses: launchbynttdata/launch-workflows/.github/workflows/reusable-pr-label-by-branch.yml@0.14.2 - secrets: inherit # pragma: allowlist secret + statuses: write + uses: launchbynttdata/launch-workflows/.github/workflows/reusable-pr-conventional-commit-title.yml@edddecdce4590f94f30d0fdfdf991d8a692fe8dd diff --git a/.github/workflows/pull-request-precommit-checks.yml b/.github/workflows/pull-request-precommit-checks.yml new file mode 100644 index 0000000..1d6924f --- /dev/null +++ b/.github/workflows/pull-request-precommit-checks.yml @@ -0,0 +1,78 @@ +name: Pre-Commit Checks + +on: + pull_request: + types: [ opened, reopened, synchronize, ready_for_review ] + branches: [ main ] + +jobs: + pre-commit: + name: Pre-Commit Checks + permissions: + pull-requests: read + contents: read + statuses: write + runs-on: ubuntu-latest + steps: + - id: checkout + name: Checkout code + uses: actions/checkout@v6 + + - id: setup-python + name: Set up Python 3.14 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 + with: + python-version: 3.14 + + - id: install-tools + name: Install Tools with uv + run: | + uv tool install pre-commit --with pre-commit-uv + + # If we've cached the pre-commit environment, restore it based on the hash of the pre-commit config. + - id: cache + name: Restore cached pre-commit environment + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae + with: + path: ~/.cache/pre-commit + key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} + + - id: setup-asdf + name: Setup ASDF + uses: asdf-vm/actions/setup@b7bcd026f18772e44fe1026d729e1611cc435d47 + + - id: restore-asdf-tools + name: Restore ASDF tools from cache + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae + with: + key: ${{ runner.os }}-asdf-${{ hashFiles('.tool-versions') }} + restore-keys: | + ${{ runner.os }}-asdf- + path: | + ~/.asdf/plugins + ~/.asdf/installs + + - id: install-asdf + name: Install ASDF tools on cache-miss + if: ${{ steps.restore-asdf-tools.outputs.cache-hit != 'true' }} + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 + + - id: asdf-reshim + name: Reshim installed ASDF tools + run: asdf reshim + + - id: pre-commit + name: Run pre-commit checks + run: uv run pre-commit run --all-files --show-diff-on-failure + + - id: set-status-check + name: Set Status Check + if: always() + uses: launchbynttdata/launch-workflows/.github/actions/update-status-check@edddecdce4590f94f30d0fdfdf991d8a692fe8dd + with: + check_name: "Pre-Commit Checks" + status: ${{ steps.pre-commit.outcome == 'success' && 'success' || + steps.pre-commit.outcome == 'failure' && 'failure' || 'error' }} + description: "Pre-Commit Checks ${{steps.pre-commit.outcome }}" + target_url: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ + github.run_id }}" diff --git a/.github/workflows/pull-request-terraform-check-aws.yml b/.github/workflows/pull-request-terraform-check-aws.yml deleted file mode 100644 index 109766b..0000000 --- a/.github/workflows/pull-request-terraform-check-aws.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Check AWS Terraform Code - -on: - pull_request: - types: [opened, reopened, synchronize, ready_for_review] - branches: [main] - -permissions: - id-token: write - contents: read - -jobs: - check: - name: "Check AWS Terraform Code" - permissions: - contents: read - id-token: write - uses: launchbynttdata/launch-workflows/.github/workflows/reusable-terraform-check-aws.yml@0.14.2 - with: - assume_role_arn: ${{ vars.TERRAFORM_CHECK_AWS_ASSUME_ROLE_ARN }} - region: ${{ vars.TERRAFORM_CHECK_AWS_REGION }} - secrets: inherit # pragma: allowlist secret diff --git a/.github/workflows/rebuild-cache-on-merge.yml b/.github/workflows/rebuild-cache-on-merge.yml new file mode 100644 index 0000000..bffa84f --- /dev/null +++ b/.github/workflows/rebuild-cache-on-merge.yml @@ -0,0 +1,56 @@ +name: Rebuild Caches on Merge + +on: + push: + branches: + - main + paths: + - ".pre-commit-config.yaml" + - ".tool-versions" + workflow_dispatch: + +jobs: + pre-commit: + name: Rebuild Caches + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - id: checkout + name: Checkout + uses: actions/checkout@v6 + + - id: setup-python + name: Set up Python 3.14 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 + with: + python-version: 3.14 + + - id: install-tools + name: Install Tools with uv + run: | + uv tool install pre-commit --with pre-commit-uv + + - id: asdf-install + name: Setup tools using asdf + uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 + + - id: pre-commit + name: Run pre-commit checks + run: uv run pre-commit run --all-files + + - id: save-pre-commit-cache + name: Save pre-commit cache + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae + with: + path: ~/.cache/pre-commit + key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} + + - id: save-asdf-cache + name: Save asdf cache + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae + with: + path: | + ~/.asdf/installs + ~/.asdf/plugins + key: ${{ runner.os }}-asdf-${{ hashFiles('.tool-versions') }} diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index 8f7d50d..3cef49e 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -14,5 +14,5 @@ jobs: permissions: contents: write pull-requests: write - uses: launchbynttdata/launch-workflows/.github/workflows/reusable-release-on-merge.yml@0.14.2 + uses: launchbynttdata/launch-workflows/.github/workflows/reusable-release-on-merge.yml@edddecdce4590f94f30d0fdfdf991d8a692fe8dd secrets: inherit # pragma: allowlist secret diff --git a/.github/workflows/update-from-skeleton.yml b/.github/workflows/update-from-skeleton.yml new file mode 100644 index 0000000..b6ce8be --- /dev/null +++ b/.github/workflows/update-from-skeleton.yml @@ -0,0 +1,24 @@ +name: Update Repository from Skeleton + +on: + schedule: + - cron: "0 0 * * 0" # Every Sunday at midnight + workflow_dispatch: + inputs: + recopy: + description: "Perform a full recopy instead of an incremental update (overwrites + all templated files), and re-runs any tasks defined in copier.yml. The + resulting PR will require manual review and merging." + type: boolean + default: false + +jobs: + update-from-skeleton: + name: Update from Skeleton + permissions: + contents: write + pull-requests: write + uses: launchbynttdata/launch-workflows/.github/workflows/reusable-update-from-skeleton.yml@edddecdce4590f94f30d0fdfdf991d8a692fe8dd + with: + recopy: ${{ github.event.inputs.recopy == 'true' }} + secrets: inherit # pragma: allowlist secret diff --git a/.gitignore b/.gitignore index 4be3fa8..9e17ca7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ +.DS_Store +Thumbs.db + terraform.* .repo/ components/ .semverbot.toml -.tflint.hcl -.golangci.yaml .idea !examples/*.tfvars @@ -15,7 +16,7 @@ components/ **/.test-data/* # Local .terraform directories -**/.terraform +**/.terraform/* # Local .terragrunt directories **/.terragrunt/* @@ -56,16 +57,6 @@ terraform.rc # Files from common modules azure_env.sh .releaserc.json -.tflint.hcl - -# Pre-commit hook -.pre-commit-config.yaml # VS Code .vscode/ - -# Lambda build files -**/build -**/builds -**/*.egg-info -**/.DS_Store diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..4da23dc --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,5 @@ +version: "2" +run: + # Timeout for analysis, e.g. 30s, 5m. + timeout: 10m + allow-parallel-runners: true diff --git a/.lcafenv b/.lcafenv deleted file mode 100644 index 363338c..0000000 --- a/.lcafenv +++ /dev/null @@ -1,23 +0,0 @@ -# Use this file to preset variables used by the Makefile. -# This file will be included when make is run. The variables below will -# take precedence over what is defined in Makefile when they are set. This -# allows overriding certain settings without modifying the Makefile. - -# REPO_MANIFESTS_URL="https://github.com/launchbynttdata/launch-common-automation-framework.git" -# REPO_BRANCH="refs/tags/1.0.0" -# REPO_MANIFEST="manifests/terraform_modules/seed/manifest.xml" -# REPO_URL="https://github.com/launchbynttdata/git-repo.git" -# REPO_REV="main" -# GITBASE="https://github.com/launchbynttdata/" -# GITREV="main" -# IS_PIPELINE="false" -# IS_AUTHENTICATED="false" -# JOB_NAME="job" -# JOB_EMAIL="job@job.job" -# PLATFORM_VER= -# CONTAINER_VER= -# PIPELINES_VER= -# WEBHOOK_VER= -# PYTHON_VER= -# TERRAGRUNT_VER= -# TERRAFORM_VER= diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..d48c0d6 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,44 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: trailing-whitespace + - id: check-case-conflict + - id: check-executables-have-shebangs + - id: check-json + - id: check-merge-conflict + args: + - --assume-in-merge + - id: check-shebang-scripts-are-executable + - id: check-yaml + args: + - --allow-multiple-documents + - id: end-of-file-fixer + - id: mixed-line-ending + args: + - --fix=auto + - repo: https://github.com/antonbabenko/pre-commit-terraform + rev: v1.99.0 + hooks: + - id: terraform_fmt + - id: terraform_validate + - id: terraform_docs + args: + - --hook-config=--path-to-file=README.md + - --hook-config=--add-to-existing-file=true + - --hook-config=--create-file-if-not-exist=true + - --args=--hide providers + - repo: https://github.com/Yelp/detect-secrets + rev: v1.5.0 + hooks: + - id: detect-secrets + args: ["--baseline", ".secrets.baseline"] + exclude: package.lock.json + - repo: local + hooks: + - id: validate-readonly-test + name: Validate readonly test wiring (RunNonDestructiveTest + TestComposable*) + entry: .github/scripts/validate-readonly-test.sh + language: script + files: ^tests/post_deploy_functional_readonly/.*\.go$ + pass_filenames: false diff --git a/.tflint.hcl b/.tflint.hcl new file mode 100644 index 0000000..35ef36f --- /dev/null +++ b/.tflint.hcl @@ -0,0 +1,13 @@ +config { + plugin_dir = "~/.tflint.d/plugins" + + call_module_type = "all" + force = false + disabled_by_default = false + + ignore_module = { + } + + varfile = [] + variables = [] +} diff --git a/.tool-versions b/.tool-versions index 867b51e..aa78ebd 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,9 +1,8 @@ conftest 0.56.0 -golang 1.24.2 -golangci-lint 2.10.1 -pre-commit 4.2.0 -regula 3.2.1 # https://github.com/launchbynttdata/asdf-regula -terraform 1.12.2 +golang 1.26.1 +golangci-lint 2.11.3 +pre-commit 4.6.0 +terraform 1.10.4 terraform-docs 0.20.0 terragrunt 0.77.22 tflint 0.57.0 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..d79b9d8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,36 @@ +# Launch Terraform Module Agent Guide + +This repository follows the Launch Terraform module standards. Keep this file small: it is the shared baseline for coding agents and should not contain workflow playbooks or long reference material. + +## Working Agreement + +- Read the current files before making changes. Do not assume the generated template still matches this repository. +- Make the smallest change that satisfies the requested outcome. +- Keep primitive modules and reference architecture modules distinct. +- Prefer clear Terraform, explicit variable types, useful descriptions, and validation for constrained inputs. +- Treat examples and tests as part of the public contract. Update them with implementation changes. +- Do not preserve skeleton placeholders, TODOs, or copied template names in completed modules. +- Do not introduce provider-specific guidance into shared rules unless it is clearly labeled by provider. +- Use `mise` for local tool execution when available. Prefer `mise run ` for configured tasks and `mise exec -- ` otherwise. +- Use SSH-based Git remotes or `gh` for GitHub repository operations. If SSH or `gh` is not working, stop and resolve that rather than silently switching to HTTPS Git remotes. +- GitHub API access through `gh api` or `gh api graphql` is acceptable when repository metadata is needed. + +## Module Types + +- Primitive modules wrap one cloud resource type and expose a reusable, low-opinion interface. +- Reference architecture modules compose primitives and selected mature community modules into opinionated infrastructure patterns. +- If the requested work does not clearly identify the module type, inspect the repository name and module structure before proceeding. + +## Task Routing + +- For primitive module creation or cleanup, use `.agents/skills/primitive-module/SKILL.md`. +- For reference architecture creation or cleanup, use `.agents/skills/reference-architecture/SKILL.md`. +- For shared Terraform standards, read `.agents/references/shared/terraform-module-standards.md`. +- For long examples, historical rationale, and provider-specific notes, read only the reference files routed by the selected skill. +- Do not load every file in `.agents/references/` by default. + +## Validation Expectations + +- Run the narrowest useful validation first, then broaden when the change affects shared behavior. +- Before considering module creation complete, validate formatting, linting, Terraform initialization/validation for examples, README generation, and Terratest readiness where practical. +- If full cloud-backed tests cannot be run, state what was validated and what remains unproven. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2f3720b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,9 @@ +@AGENTS.md + +# Claude Code Notes + +Use `AGENTS.md` as the shared operating contract. Claude-specific routing lives in `.claude/rules/`. + +When a task matches primitive module creation, reference architecture creation, or module cleanup, follow the corresponding workflow under `.agents/skills/` and read only the referenced material needed for that task. + +Keep `CLAUDE.md` concise. Put task-specific guidance in `.claude/rules/` or `.agents/skills/` rather than expanding this file. diff --git a/CODEOWNERS b/CODEOWNERS index d5d106b..3397f05 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @launchbynttdata/terraform-administrators +* @launchbynttdata/terraform-administrators diff --git a/Makefile b/Makefile index 8f87fc3..8ab409c 100644 --- a/Makefile +++ b/Makefile @@ -10,82 +10,213 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Include custom values from .lcafenv. Repository root is assumed to be the working directory. -# Including overriding values in this file is preferred over modifying the contents below. -LCAF_ENV_FILE = .lcafenv --include $(LCAF_ENV_FILE) - -# Source repository for repo manifests -REPO_MANIFESTS_URL ?= https://github.com/launchbynttdata/launch-common-automation-framework.git -# Branch of source repository for repo manifests. Other tags not currently supported. -REPO_BRANCH ?= refs/tags/1.8.1 -# Path to seed manifest in repository referenced in REPO_MANIFESTS_URL -REPO_MANIFEST ?= manifests/terraform_modules/seed/manifest.xml - -# Settings to pull in Nexient version of (google) repo utility that supports environment substitution: -REPO_URL ?= https://github.com/launchbynttdata/git-repo.git -# Branch of the repository referenced by REPO_URL to use -REPO_REV ?= main -export REPO_REV REPO_URL - -# Example variable to substituted after init, but before sync in repo manifests. -GITBASE ?= https://github.com/launchbynttdata/ -GITREV ?= main -export GITBASE GITREV - -# Set to true in a pipeline context -IS_PIPELINE ?= false - -IS_AUTHENTICATED ?= false +SHELL := /bin/bash JOB_NAME ?= job JOB_EMAIL ?= job@job.job -COMPONENTS_DIR = components --include $(COMPONENTS_DIR)/Makefile - -MODULE_DIR ?= ${COMPONENTS_DIR}/module - PYTHON3_INSTALLED = $(shell which python3 > /dev/null 2>&1; echo $$?) MISE_INSTALLED = $(shell which mise > /dev/null 2>&1; echo $$?) ASDF_INSTALLED = $(shell which asdf > /dev/null 2>&1; echo $$?) -REPO_INSTALLED = $(shell which repo > /dev/null 2>&1; echo $$?) GIT_USER_SET = $(shell git config --get user.name > /dev/null 2>&1; echo $$?) GIT_EMAIL_SET = $(shell git config --get user.email > /dev/null 2>&1; echo $$?) -.PHONY: configure-git-hooks -configure-git-hooks: configure-dependencies -ifeq ($(PYTHON3_INSTALLED), 0) - pre-commit install -else - $(error Missing python3, which is required for pre-commit. Install python3 and rerun.) -endif +# ------------------------------------------------------------------------------ +# Binaries +# ------------------------------------------------------------------------------ + +GO ?= go +GOLANGCI_LINT ?= golangci-lint +TEE ?= tee +GREP ?= grep +FIND ?= find +TERRAFORM ?= terraform +RM ?= rm -rf +TFLINT ?= tflint + +# ------------------------------------------------------------------------------ +# Variables โ€” Golang +# ------------------------------------------------------------------------------ + +GO_TEST_DIRECTORIES ?= tests +GO_TEST_TIMEOUT ?= 2h +GO_TEST_READONLY_DIRECTORY ?= post_deploy_functional_readonly +TEST_RUN_ONLY_READONLY = #intentionally empty +TEST_RUN_EXCLUDE_READONLY = -v +GOLANGCI_LINT_CONFIG ?= .golangci.yaml +DISABLE_MAKE_CHECK_LINT ?= false +CURRENT_DIR = $(notdir $(shell pwd)) -ifeq ($(IS_PIPELINE),true) -.PHONY: git-config -git-config: - @set -ex; \ - git config --global user.name "$(JOB_NAME)"; \ - git config --global user.email "$(JOB_EMAIL)"; \ - git config --global color.ui false +# If ARM_SUBSCRIPTION_ID is not already set by the environment, we'll try to use the `az` command +# to retrieve it. If `az` isn't found or not logged in, we'll silently continue here and catch +# that case elsewhere. +export ARM_SUBSCRIPTION_ID ?= $(shell command -v az >/dev/null 2>&1 && az account show 2>/dev/null | jq -r .id) -configure: git-config +# ------------------------------------------------------------------------------ +# Variables โ€” Terraform module +# ------------------------------------------------------------------------------ + +ALL_TF_MODULES = $(shell $(call list_terraform_modules)) +ALL_EXAMPLES = $(shell $(call list_examples)) +TFLINT_CONFIG ?= .tflint.hcl +VAR_FILE ?= test.tfvars +AWS_PROFILE ?= +AWS_REGION ?= us-east-2 +GITHUB_OWNER ?= $(or $(GITHUB_REPOSITORY_OWNER),launchbynttdata) + +ifneq ($(strip $(AWS_PROFILE)),) +export AWS_PROFILE endif -ifeq ($(IS_AUTHENTICATED),true) -.PHONY: git-auth -git-auth: - $(call config,Bearer $(GIT_TOKEN)) +ifneq ($(strip $(AWS_PROFILE)),) +ifneq ($(shell printf '%s' '$(AWS_PROFILE)' | grep -Eq '^[A-Za-z0-9_.-]+$$'; echo $$?),0) +$(error AWS_PROFILE contains unsupported characters) +endif +endif + +# ------------------------------------------------------------------------------ +# Functions โ€” Golang +# ------------------------------------------------------------------------------ + +# Checks for Go files in the GO_TEST_DIRECTORIES. If they exist, runs the default configuration for golangci-lint +# https://golangci-lint.run/usage/quick-start/ +define go_lint + $(FIND) $(1)/ -name '*.go' | $(GREP) -q '\.go' || exit 0; $(GOLANGCI_LINT) run -c $(GOLANGCI_LINT_CONFIG) -v ./$(1)/...; -define config - @set -ex; \ - git config --global http.extraheader "AUTHORIZATION: $(1)"; \ - git config --global http.https://gerrit.googlesource.com/git-repo/.extraheader ''; \ - git config --global http.version HTTP/1.1; endef -configure: git-auth -endif +# Check for Go files. If they exist, run tests. Either runs only readonly tests(default) or tests except readonly ones +define go_test + $(FIND) $(1)/ -name '*.go' |$(GREP) $(2) $(GO_TEST_READONLY_DIRECTORY) | $(GREP) -q '\.go' || exit 0; $(GO) test -v -count=1 -timeout=$(GO_TEST_TIMEOUT) $$($(GO) list ./$(1)/...|$(GREP) $(2) $(GO_TEST_READONLY_DIRECTORY)) ; + +endef + +# ------------------------------------------------------------------------------ +# Functions โ€” Terraform module +# ------------------------------------------------------------------------------ + +define check_terraform_fmt + echo && echo "Formatting Terraform files ..."; + $(TERRAFORM) fmt -recursive; + +endef + +define clean_terraform_module + $(RM) -fr $(1)/.terraform* $(1)/terraform.*; + $(RM) -f $(1)/provider.tf +endef + +define init_terraform_module + echo && echo "Initializing $(1) ..."; + $(TERRAFORM) -chdir=$(1) init -backend=false -input=false; + +endef + +define list_terraform_modules + $(FIND) . -path "*/.terraform" -prune -o -name "main.tf" -not -path '*pipeline*' -not -path '*examples*' -exec dirname {} \;; + +endef + +define list_examples + $(FIND) ./examples -path "*/.terraform" -prune -o -name "main.tf" -not -path '*pipeline*' -exec dirname {} \; 2>/dev/null +endef + +# ------------------------------------------------------------------------------ +# Provider templates +# ------------------------------------------------------------------------------ +# When running `make lint` or `make test`, provider configuration files are +# auto-generated for each example directory based on the providers declared in +# the root module's `terraform providers` output. The define blocks below +# contain the default content written to each example's provider.tf for the +# matching registry provider. Examples that declare additional providers not +# present in the root module will not receive blocks for those providers. +# +# To override auto-generation entirely, set the PROVIDER_TEMPLATE environment +# variable to the path of a custom provider.tf file: +# +# $ PROVIDER_TEMPLATE=my-provider.tf make test +# +# When set, that file is copied verbatim into every example directory instead of +# running the per-provider detection logic below. +# ------------------------------------------------------------------------------ + +PROVIDER_TEMPLATE ?= + +define aws_provider_base +provider \"aws\" {\n region = \"$(AWS_REGION)\"\n$(if $(strip $(AWS_PROFILE)), profile = \"$(AWS_PROFILE)\"\n)}\n +endef + +define aws_provider_global +\nprovider \"aws\" {\n alias = \"global\"\n region = \"us-east-1\"\n$(if $(strip $(AWS_PROFILE)), profile = \"$(AWS_PROFILE)\"\n)}\n +endef + +define aws_provider +$(call aws_provider_base)$(if $(INCLUDE_AWS_GLOBAL),$(call aws_provider_global)) +endef + +define azurerm_provider +provider \"azurerm\" {\n skip_provider_registration = true\n features {\n resource_group {\n prevent_deletion_if_contains_resources = false\n }\n }\n}\n +endef + +define azapi_provider +provider \"azapi\" {\n use_cli = true\n use_msi = false\n}\n +endef + +define azuredevops_provider +provider \"azuredevops\" {}\n +endef + +define github_provider +provider \"github\" {\n owner = \"$(GITHUB_OWNER)\"\n}\n +endef + +define provider_file_path +$(1)/provider.tf +endef + +define add_provider_details + $(if $(findstring hashicorp/aws,$(2)),grep -qs "aws" $(1) || bash -c 'echo -e "$(call aws_provider)"' >> $(1),) + $(if $(findstring azure/azapi,$(2)),grep -qs "azapi" $(1) || bash -c 'echo -e "$(call azapi_provider)"' >> $(1),) + $(if $(findstring microsoft/azuredevops,$(2)),grep -qs "azuredevops" $(1) || bash -c 'echo -e "$(call azuredevops_provider)"' >> $(1),) + $(if $(findstring hashicorp/azurerm,$(2)),grep -qs "azurerm" $(1) || bash -c 'echo -e "$(call azurerm_provider)"' >> $(1),) + $(if $(findstring integrations/github,$(2)),grep -qs "github" $(1) || bash -c 'echo -e "$(call github_provider)"' >> $(1),) +endef + +define create_example_providers + $(eval PROVIDER_FILE_PATH:=$(call provider_file_path,$(1))) + $(eval INCLUDE_AWS_GLOBAL:=$(if $(filter .,$(1)),$(findstring aws.global,$(shell grep -se "\\s*provider\\s*=" *.tf || true)),)) + $(foreach PROVIDER,$(shell terraform providers | sed -re 's/.+\[(.+\/.+\/.+)\].+/\1/g' | grep registry | sort | uniq),$(call add_provider_details,$(PROVIDER_FILE_PATH),$(PROVIDER))) +endef + +define plan_terraform_module + echo && echo "Planning $(1) ..."; + if [ -f "$(1)/$(VAR_FILE)" ]; then \ + echo "Using $(VAR_FILE) for variable inputs"; \ + $(TERRAFORM) -chdir=$(1) plan -input=false -out=terraform.tfplan -var-file $(VAR_FILE); \ + else \ + echo "$(VAR_FILE) doesn't exist, falling back to default (terraform.tfvars)"; \ + $(TERRAFORM) -chdir=$(1) plan -input=false -out=terraform.tfplan; \ + fi; + echo && echo "Creating JSON plan output for $(1) ..."; + cd $(1) && $(TERRAFORM) show -json ./terraform.tfplan > ./terraform.tfplan.json; + +endef + +define tflint_terraform_module + echo && echo "Linting $(1) ..."; + (cd $(1) && TF_LOG=info $(TFLINT) -c $(PWD)/$(TFLINT_CONFIG)) || exit 1; + +endef + +define validate_terraform_module + echo && echo "Validating $(1) ..."; + $(TERRAFORM) -chdir=$(1) validate || exit 1; + +endef + +# ============================================================================== +# Targets โ€” Setup / Configuration +# ============================================================================== .PHONY: configure-dependencies configure-dependencies: @@ -99,34 +230,168 @@ else $(error Missing supported dependency manager. Install asdf-vm (https://asdf-vm.com/) or mise (https://mise.jdx.dev/) and rerun) endif +.PHONY: configure-git-hooks +configure-git-hooks: configure-dependencies +ifeq ($(PYTHON3_INSTALLED), 0) + pre-commit install +else + $(error Missing python3, which is required for pre-commit. Install python3 and rerun.) +endif + .PHONY: configure configure: configure-git-hooks ifneq ($(and $(GIT_USER_SET), $(GIT_EMAIL_SET)), 0) $(error Git identities are not set! Set your user.name and user.email using 'git config' and rerun) endif -ifeq ($(REPO_INSTALLED), 0) - echo n | repo --color=never init --no-repo-verify \ - -u "$(REPO_MANIFESTS_URL)" \ - -b "$(REPO_BRANCH)" \ - -m "$(REPO_MANIFEST)" - repo envsubst - repo sync -else - $(error Missing Repo, which is required for platform sync. Install Repo (https://gerrit.googlesource.com/git-repo) and rerun.) -endif -# The first line finds and removes all the directories pulled in by repo -# The second line finds and removes all the broken symlinks from removing things -# https://stackoverflow.com/questions/42828021/removing-files-with-rm-using-find-and-xargs .PHONY: clean clean: - -repo list | awk '{ print $1; }' | cut -d '/' -f1 | uniq | xargs rm -rf - find . -type l ! -exec test -e {} \; -print | xargs rm -rf - -.PHONY: init-clean -init-clean: - rm -rf .git - git init --initial-branch=main -ifneq (,$(wildcard ./TEMPLATED_README.md)) - mv TEMPLATED_README.md README.MD + $(MAKE) tfmodule/clean + +# ============================================================================== +# Targets โ€” Check / Lint / Test (base double-colon rules) +# ============================================================================== + +.PHONY: check +check: + $(MAKE) lint + $(MAKE) tfmodule/test + $(MAKE) test + +.PHONY: lint +lint:: + @true + +.PHONY: test +test:: + @true + +# ============================================================================== +# Targets โ€” Golang +# ============================================================================== + +.PHONY: go/test/environment/az +go/test/environment/az: +ifeq (,$(ARM_SUBSCRIPTION_ID)) + $(error "ARM_SUBSCRIPTION_ID was not set and `az` was not found in your PATH.") +else + @echo "Terratest will use Azure Subscription ID $$ARM_SUBSCRIPTION_ID" +endif + +.PHONY: go/test/environment/aws +go/test/environment/aws: + @echo "No environment configuration for AWS is defined." + +.PHONY: go/test/environment/gcp +go/test/environment/gcp: + @echo "No environment configuration for GCP is defined." + +# Performs any environmental setup required for a cloud provider based on the name of the repository. +.PHONY: go/test/environment +go/test/environment: +ifneq (,$(findstring tf-aws,$(CURRENT_DIR))) + $(MAKE) go/test/environment/aws +else ifneq (,$(findstring tf-az,$(CURRENT_DIR))) + $(MAKE) go/test/environment/az +else ifneq (,$(findstring tf-gcp,$(CURRENT_DIR))) + $(MAKE) go/test/environment/gcp +else + @echo "Unrecognized module type, no environmental setup will be performed." +endif + +.PHONY: go/list +go/list: + @echo -n "Test dirs: " + @echo $(GO_TEST_DIRECTORIES) | tr ' ' '\n' | sort + +.PHONY: go/lint +go/lint: + $(foreach test_dir,$(GO_TEST_DIRECTORIES),$(call go_lint,$(test_dir))) + +.PHONY: go/test +go/test: go/test/environment + $(foreach test_dir,$(GO_TEST_DIRECTORIES),$(call go_test,$(test_dir),$(TEST_RUN_EXCLUDE_READONLY))) + +.PHONY: go/readonly_test +go/readonly_test: + $(foreach test_dir,$(GO_TEST_DIRECTORIES),$(call go_test,$(test_dir),$(TEST_RUN_ONLY_READONLY))) + +# Golang extensions to lint / test +.PHONY: lint +lint:: +ifeq ($(DISABLE_MAKE_CHECK_LINT),false) + $(MAKE) go/lint +else + $(info "make go/lint has been disabled!") +endif + +.PHONY: test +test:: tfmodule/plan + $(MAKE) go/test + +# ============================================================================== +# Targets โ€” Terraform module +# ============================================================================== + +.PHONY: tfmodule/all +tfmodule/all: lint + +.PHONY: tfmodule/clean +tfmodule/clean: + @$(foreach module,$(ALL_TF_MODULES),$(call clean_terraform_module,$(module))) + @$(foreach module,$(ALL_EXAMPLES),$(call clean_terraform_module,$(module))) + +.PHONY: tfmodule/fmt +tfmodule/fmt: + $(TERRAFORM) fmt -recursive; + +.PHONY: tfmodule/init +tfmodule/init: + @$(foreach module,$(ALL_TF_MODULES),$(call init_terraform_module,$(module))) + @$(foreach module,$(ALL_EXAMPLES),$(call init_terraform_module,$(module))) + +.PHONY: tfmodule/lint +tfmodule/lint: tfmodule/init + @$(call check_terraform_fmt) + @$(foreach module,$(ALL_TF_MODULES),$(call tflint_terraform_module,$(module))) + @$(foreach module,$(ALL_TF_MODULES),$(call validate_terraform_module,$(module))) + @$(foreach module,$(ALL_EXAMPLES),$(call tflint_terraform_module,$(module))) + @$(foreach module,$(ALL_EXAMPLES),$(call validate_terraform_module,$(module))) + +.PHONY: tfmodule/list +tfmodule/list: + @echo -n "Modules: " + @echo $(ALL_TF_MODULES) | tr ' ' '\n' | sort + @echo -n "Examples: " + @echo $(ALL_EXAMPLES) | tr ' ' '\n' | sort + +.PHONY: tfmodule/plan +tfmodule/plan: tfmodule/init + @$(foreach module,$(ALL_EXAMPLES),$(call plan_terraform_module,$(module))) + +.PHONY: tfmodule/test +tfmodule/test: tfmodule/init + @$(FIND) . -maxdepth 2 -path './.terraform' -prune -o -name '*.tftest.hcl' -print | $(GREP) -q 'tftest' || { echo "No Terraform test files found, skipping terraform test."; exit 0; }; \ + echo && echo "Running Terraform tests ..."; \ + $(TERRAFORM) test + +.PHONY: tfmodule/create_example_providers +tfmodule/create_example_providers: tfmodule/init +ifdef PROVIDER_TEMPLATE + @$(if $(wildcard $(PROVIDER_TEMPLATE)),,$(error PROVIDER_TEMPLATE is set to '$(PROVIDER_TEMPLATE)' but the file does not exist)) + @$(foreach example,$(ALL_EXAMPLES),cp $(PROVIDER_TEMPLATE) $(call provider_file_path,$(example));) +else + @$(if $(findstring aws.global,$(shell grep -se "\\s*provider\\s*=" *.tf || true)),$(call create_example_providers,.),) + @$(foreach example,$(ALL_EXAMPLES),$(call create_example_providers,$(example))) endif + +# Terraform module extensions to lint / test +.PHONY: lint +lint:: + $(MAKE) tfmodule/create_example_providers + $(MAKE) tfmodule/lint + +.PHONY: test +test:: + $(MAKE) tfmodule/create_example_providers + $(MAKE) tfmodule/plan diff --git a/README.md b/README.md index 3212c0f..081e097 100644 --- a/README.md +++ b/README.md @@ -111,10 +111,6 @@ If `make check` target is successful, developer is good to commit the code to pr | [terraform](#requirement\_terraform) | ~> 1.5 | | [aws](#requirement\_aws) | ~> 5.14 | -## Providers - -No providers. - ## Modules | Name | Source | Version | @@ -129,71 +125,71 @@ No resources. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [name](#input\_name) | The name of this Lambda Function | `string` | n/a | yes | -| [description](#input\_description) | Description of your Lambda Function | `string` | `""` | no | -| [handler](#input\_handler) | Lambda Function entrypoint in your code | `string` | `"index.lambda_handler"` | no | -| [runtime](#input\_runtime) | Lambda Function runtime | `string` | `"python3.9"` | no | +| [allowed\_triggers](#input\_allowed\_triggers) | Map of allowed triggers to create Lambda permissions | `map(any)` | `{}` | no | | [architectures](#input\_architectures) | (Optional) Instruction set architecture for your Lambda function. Valid architectures are x86\_64 (default) and arm64. | `list(string)` |
[
"x86_64"
]
| no | -| [publish](#input\_publish) | Whether to publish creation/change as new Lambda Function Version. | `bool` | `true` | no | -| [ephemeral\_storage\_size](#input\_ephemeral\_storage\_size) | mount of ephemeral storage (/tmp) in MB your Lambda Function can use at runtime. Valid values are between 512 MB to 10,240 MB (10 GB). | `number` | `512` | no | -| [environment\_variables](#input\_environment\_variables) | A map that defines environment variables for the Lambda Function. | `map(string)` | `{}` | no | -| [memory\_size](#input\_memory\_size) | Amount of memory in MB your Lambda Function can use at runtime. Valid values are between 128 MB to 10,240 MB (10 GB), in 64 MB increments. | `number` | `128` | no | -| [timeout](#input\_timeout) | The amount of time your Lambda Function has to run in seconds. The maximum lifetime of a Lambda function execution is 15 minutes (900 seconds). | `number` | `3` | no | -| [create\_package](#input\_create\_package) | Controls whether Lambda package should be created | `bool` | `false` | no | -| [source\_path](#input\_source\_path) | The absolute path to a local file or directory containing your Lambda source code. Only valid if `create_package` is set to `true`. | `any` | `null` | no | -| [zip\_file\_path](#input\_zip\_file\_path) | Path of the source zip file with respect to module root | `string` | `null` | no | -| [store\_on\_s3](#input\_store\_on\_s3) | Whether to store produced artifacts on S3 or locally. | `bool` | `false` | no | -| [s3\_existing\_package](#input\_s3\_existing\_package) | The S3 bucket object with keys bucket, key, version pointing to an existing zip-file to use. Only valid if `create_package` is set to `false`. | `map(string)` | `null` | no | -| [s3\_bucket](#input\_s3\_bucket) | S3 bucket to store artifacts. Required if `store_on_s3` is set to `true`, ignored otherwise. | `string` | `null` | no | -| [s3\_prefix](#input\_s3\_prefix) | Directory name where artifacts should be stored in the S3 bucket. Defaults to `builds`. Required if `store_on_s3` is set to `true`, ignored otherwise. | `string` | `"builds"` | no | -| [layers](#input\_layers) | List of Lambda Layer Version ARNs (maximum of 5) to attach to your Lambda Function. | `list(string)` | `null` | no | -| [hash\_extra](#input\_hash\_extra) | The string to add into hashing function. Useful when building same source path for different functions. | `string` | `""` | no | -| [ignore\_source\_code\_hash](#input\_ignore\_source\_code\_hash) | Whether to ignore changes to the function's source code hash. Set to true if you manage infrastructure and code deployments separately. | `bool` | `false` | no | -| [authorization\_type](#input\_authorization\_type) | The type of authentication that the Lambda Function URL uses. Set to `AWS_IAM` to restrict access to authenticated IAM users only. Set to `NONE` to bypass IAM authentication and create a public endpoint (default). | `string` | `"NONE"` | no | -| [cors](#input\_cors) | CORS settings to be used by the Lambda Function URL |
object({
allow_credentials = optional(bool, false)
allow_headers = optional(list(string), null)
allow_methods = optional(list(string), null)
allow_origins = optional(list(string), null)
expose_headers = optional(list(string), null)
max_age = optional(number, 0)
})
| `{}` | no | -| [create\_lambda\_function\_url](#input\_create\_lambda\_function\_url) | Whether the Lambda Function URL resource should be created (default true). | `bool` | `true` | no | -| [invoke\_mode](#input\_invoke\_mode) | Invoke mode of the Lambda Function URL. Valid values are `BUFFERED` (default) and `RESPONSE_STREAM`. | `string` | `"BUFFERED"` | no | -| [attach\_policy\_statements](#input\_attach\_policy\_statements) | Controls whether `policy_statements` should be added to IAM role for Lambda Function | `bool` | `false` | no | -| [policy\_statements](#input\_policy\_statements) | Map of dynamic policy statements to attach to Lambda Function role | `any` | `{}` | no | -| [attach\_policy](#input\_attach\_policy) | Controls whether `policy` should be added to IAM role for Lambda Function | `bool` | `false` | no | -| [policy](#input\_policy) | Policy statement ARN to attach to Lambda Function role | `string` | `null` | no | +| [assume\_role\_policy\_statements](#input\_assume\_role\_policy\_statements) | Map of dynamic policy statements for assuming Lambda Function role (trust relationship) | `map(string)` | `{}` | no | +| [attach\_async\_event\_policy](#input\_attach\_async\_event\_policy) | Controls whether async event policy should be added to IAM role for Lambda Function | `bool` | `false` | no | +| [attach\_cloudwatch\_logs\_policy](#input\_attach\_cloudwatch\_logs\_policy) | Controls whether CloudWatch Logs policy should be added to IAM role for Lambda Function | `bool` | `true` | no | +| [attach\_create\_log\_group\_permission](#input\_attach\_create\_log\_group\_permission) | Controls whether to add the create log group permission to the CloudWatch logs policy | `bool` | `true` | no | +| [attach\_dead\_letter\_policy](#input\_attach\_dead\_letter\_policy) | Controls whether SNS/SQS dead letter notification policy should be added to IAM role for Lambda Function. Defaults to `false`. | `bool` | `false` | no | +| [attach\_network\_policy](#input\_attach\_network\_policy) | Controls whether VPC/network policy should be added to IAM role for Lambda Function | `bool` | `false` | no | | [attach\_policies](#input\_attach\_policies) | Controls whether `policies` should be added to IAM role for Lambda Function | `bool` | `false` | no | -| [policies](#input\_policies) | List of policy statement ARNs to attach to Lambda Function role | `list(string)` | `[]` | no | +| [attach\_policy](#input\_attach\_policy) | Controls whether `policy` should be added to IAM role for Lambda Function | `bool` | `false` | no | | [attach\_policy\_json](#input\_attach\_policy\_json) | Controls whether `policy_json` should be added to IAM role for Lambda Function | `bool` | `false` | no | -| [policy\_json](#input\_policy\_json) | An additional policy document as JSON to attach to the Lambda Function role | `string` | `null` | no | | [attach\_policy\_jsons](#input\_attach\_policy\_jsons) | Controls whether `policy_jsons` should be added to IAM role for Lambda Function | `bool` | `false` | no | -| [policy\_jsons](#input\_policy\_jsons) | An additional policy documents as JSON to attach to the Lambda Function role | `list(string)` | `[]` | no | -| [attach\_dead\_letter\_policy](#input\_attach\_dead\_letter\_policy) | Controls whether SNS/SQS dead letter notification policy should be added to IAM role for Lambda Function. Defaults to `false`. | `bool` | `false` | no | -| [dead\_letter\_target\_arn](#input\_dead\_letter\_target\_arn) | The ARN of an SNS topic or SQS queue to notify when an invocation fails. | `string` | `null` | no | -| [attach\_network\_policy](#input\_attach\_network\_policy) | Controls whether VPC/network policy should be added to IAM role for Lambda Function | `bool` | `false` | no | -| [attach\_async\_event\_policy](#input\_attach\_async\_event\_policy) | Controls whether async event policy should be added to IAM role for Lambda Function | `bool` | `false` | no | +| [attach\_policy\_statements](#input\_attach\_policy\_statements) | Controls whether `policy_statements` should be added to IAM role for Lambda Function | `bool` | `false` | no | | [attach\_tracing\_policy](#input\_attach\_tracing\_policy) | Controls whether X-Ray tracing policy should be added to IAM role for Lambda Function | `bool` | `false` | no | -| [assume\_role\_policy\_statements](#input\_assume\_role\_policy\_statements) | Map of dynamic policy statements for assuming Lambda Function role (trust relationship) | `map(string)` | `{}` | no | -| [trusted\_entities](#input\_trusted\_entities) | List of additional trusted entities for assuming Lambda Function role (trust relationship) | `any` | `[]` | no | -| [allowed\_triggers](#input\_allowed\_triggers) | Map of allowed triggers to create Lambda permissions | `map(any)` | `{}` | no | -| [attach\_cloudwatch\_logs\_policy](#input\_attach\_cloudwatch\_logs\_policy) | Controls whether CloudWatch Logs policy should be added to IAM role for Lambda Function | `bool` | `true` | no | -| [attach\_create\_log\_group\_permission](#input\_attach\_create\_log\_group\_permission) | Controls whether to add the create log group permission to the CloudWatch logs policy | `bool` | `true` | no | +| [authorization\_type](#input\_authorization\_type) | The type of authentication that the Lambda Function URL uses. Set to `AWS_IAM` to restrict access to authenticated IAM users only. Set to `NONE` to bypass IAM authentication and create a public endpoint (default). | `string` | `"NONE"` | no | | [cloudwatch\_logs\_kms\_key\_id](#input\_cloudwatch\_logs\_kms\_key\_id) | The ARN of the KMS Key to use when encrypting log data. | `string` | `null` | no | | [cloudwatch\_logs\_log\_group\_class](#input\_cloudwatch\_logs\_log\_group\_class) | Specified the log class of the log group. Possible values are: `STANDARD` (default) or `INFREQUENT_ACCESS` | `string` | `"STANDARD"` | no | | [cloudwatch\_logs\_retention\_in\_days](#input\_cloudwatch\_logs\_retention\_in\_days) | Specifies the number of days you want to retain log events in the specified log group. Possible values are: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, and 3653. Defaults to 30. | `number` | `30` | no | | [cloudwatch\_logs\_skip\_destroy](#input\_cloudwatch\_logs\_skip\_destroy) | Whether to keep the log group (and any logs it may contain) at destroy time. Defaults to false. | `bool` | `false` | no | | [cloudwatch\_logs\_tags](#input\_cloudwatch\_logs\_tags) | A map of tags to assign to the logs resource. | `map(string)` | `{}` | no | -| [tracing\_mode](#input\_tracing\_mode) | Tracing mode of the Lambda Function. Valid value can be either PassThrough (default) or Active. | `string` | `"PassThrough"` | no | -| [vpc\_security\_group\_ids](#input\_vpc\_security\_group\_ids) | List of security group ids when Lambda Function should run in the VPC. | `list(string)` | `null` | no | -| [vpc\_subnet\_ids](#input\_vpc\_subnet\_ids) | List of subnet ids when Lambda Function should run in the VPC. Usually private or intra subnets. | `list(string)` | `null` | no | +| [cors](#input\_cors) | CORS settings to be used by the Lambda Function URL |
object({
allow_credentials = optional(bool, false)
allow_headers = optional(list(string), null)
allow_methods = optional(list(string), null)
allow_origins = optional(list(string), null)
expose_headers = optional(list(string), null)
max_age = optional(number, 0)
})
| `{}` | no | +| [create](#input\_create) | Controls whether resources should be created. | `bool` | `false` | no | +| [create\_lambda\_function\_url](#input\_create\_lambda\_function\_url) | Whether the Lambda Function URL resource should be created (default true). | `bool` | `true` | no | +| [create\_package](#input\_create\_package) | Controls whether Lambda package should be created | `bool` | `false` | no | +| [dead\_letter\_target\_arn](#input\_dead\_letter\_target\_arn) | The ARN of an SNS topic or SQS queue to notify when an invocation fails. | `string` | `null` | no | +| [description](#input\_description) | Description of your Lambda Function | `string` | `""` | no | +| [environment\_variables](#input\_environment\_variables) | A map that defines environment variables for the Lambda Function. | `map(string)` | `{}` | no | +| [ephemeral\_storage\_size](#input\_ephemeral\_storage\_size) | mount of ephemeral storage (/tmp) in MB your Lambda Function can use at runtime. Valid values are between 512 MB to 10,240 MB (10 GB). | `number` | `512` | no | +| [handler](#input\_handler) | Lambda Function entrypoint in your code | `string` | `"index.lambda_handler"` | no | +| [hash\_extra](#input\_hash\_extra) | The string to add into hashing function. Useful when building same source path for different functions. | `string` | `""` | no | +| [ignore\_source\_code\_hash](#input\_ignore\_source\_code\_hash) | Whether to ignore changes to the function's source code hash. Set to true if you manage infrastructure and code deployments separately. | `bool` | `false` | no | +| [invoke\_mode](#input\_invoke\_mode) | Invoke mode of the Lambda Function URL. Valid values are `BUFFERED` (default) and `RESPONSE_STREAM`. | `string` | `"BUFFERED"` | no | | [lambda\_at\_edge](#input\_lambda\_at\_edge) | Set this to true if using Lambda@Edge, to enable publishing, limit the timeout, and allow edgelambda.amazonaws.com to invoke the function | `bool` | `false` | no | | [lambda\_at\_edge\_logs\_all\_regions](#input\_lambda\_at\_edge\_logs\_all\_regions) | Whether to specify a wildcard in IAM policy used by Lambda@Edge to allow logging in all regions | `bool` | `true` | no | +| [layers](#input\_layers) | List of Lambda Layer Version ARNs (maximum of 5) to attach to your Lambda Function. | `list(string)` | `null` | no | +| [memory\_size](#input\_memory\_size) | Amount of memory in MB your Lambda Function can use at runtime. Valid values are between 128 MB to 10,240 MB (10 GB), in 64 MB increments. | `number` | `128` | no | +| [name](#input\_name) | The name of this Lambda Function | `string` | n/a | yes | +| [policies](#input\_policies) | List of policy statement ARNs to attach to Lambda Function role | `list(string)` | `[]` | no | +| [policy](#input\_policy) | Policy statement ARN to attach to Lambda Function role | `string` | `null` | no | +| [policy\_json](#input\_policy\_json) | An additional policy document as JSON to attach to the Lambda Function role | `string` | `null` | no | +| [policy\_jsons](#input\_policy\_jsons) | An additional policy documents as JSON to attach to the Lambda Function role | `list(string)` | `[]` | no | +| [policy\_statements](#input\_policy\_statements) | Map of dynamic policy statements to attach to Lambda Function role | `any` | `{}` | no | +| [publish](#input\_publish) | Whether to publish creation/change as new Lambda Function Version. | `bool` | `true` | no | +| [runtime](#input\_runtime) | Lambda Function runtime | `string` | `"python3.9"` | no | +| [s3\_bucket](#input\_s3\_bucket) | S3 bucket to store artifacts. Required if `store_on_s3` is set to `true`, ignored otherwise. | `string` | `null` | no | +| [s3\_existing\_package](#input\_s3\_existing\_package) | The S3 bucket object with keys bucket, key, version pointing to an existing zip-file to use. Only valid if `create_package` is set to `false`. | `map(string)` | `null` | no | +| [s3\_prefix](#input\_s3\_prefix) | Directory name where artifacts should be stored in the S3 bucket. Defaults to `builds`. Required if `store_on_s3` is set to `true`, ignored otherwise. | `string` | `"builds"` | no | +| [source\_path](#input\_source\_path) | The absolute path to a local file or directory containing your Lambda source code. Only valid if `create_package` is set to `true`. | `any` | `null` | no | +| [store\_on\_s3](#input\_store\_on\_s3) | Whether to store produced artifacts on S3 or locally. | `bool` | `false` | no | | [tags](#input\_tags) | Map of tags to apply to this resource. | `map(string)` | `{}` | no | -| [create](#input\_create) | Controls whether resources should be created. | `bool` | `false` | no | +| [timeout](#input\_timeout) | The amount of time your Lambda Function has to run in seconds. The maximum lifetime of a Lambda function execution is 15 minutes (900 seconds). | `number` | `3` | no | +| [tracing\_mode](#input\_tracing\_mode) | Tracing mode of the Lambda Function. Valid value can be either PassThrough (default) or Active. | `string` | `"PassThrough"` | no | +| [trusted\_entities](#input\_trusted\_entities) | List of additional trusted entities for assuming Lambda Function role (trust relationship) | `any` | `[]` | no | +| [vpc\_security\_group\_ids](#input\_vpc\_security\_group\_ids) | List of security group ids when Lambda Function should run in the VPC. | `list(string)` | `null` | no | +| [vpc\_subnet\_ids](#input\_vpc\_subnet\_ids) | List of subnet ids when Lambda Function should run in the VPC. Usually private or intra subnets. | `list(string)` | `null` | no | +| [zip\_file\_path](#input\_zip\_file\_path) | Path of the source zip file with respect to module root | `string` | `null` | no | ## Outputs | Name | Description | |------|-------------| -| [lambda\_function\_arn](#output\_lambda\_function\_arn) | n/a | -| [lambda\_function\_name](#output\_lambda\_function\_name) | n/a | | [lambda\_cloudwatch\_log\_group\_arn](#output\_lambda\_cloudwatch\_log\_group\_arn) | n/a | | [lambda\_cloudwatch\_log\_group\_name](#output\_lambda\_cloudwatch\_log\_group\_name) | n/a | +| [lambda\_function\_arn](#output\_lambda\_function\_arn) | n/a | +| [lambda\_function\_name](#output\_lambda\_function\_name) | n/a | | [lambda\_function\_url](#output\_lambda\_function\_url) | n/a | | [lambda\_role\_arn](#output\_lambda\_role\_arn) | n/a | | [lambda\_role\_name](#output\_lambda\_role\_name) | n/a | diff --git a/examples/source_from_folder/README.md b/examples/source_from_folder/README.md index 6e23cdd..e6ea8d1 100644 --- a/examples/source_from_folder/README.md +++ b/examples/source_from_folder/README.md @@ -2,7 +2,7 @@ Demonstrates creating a Lambda Function from a local folder. - + ## Requirements | Name | Version | @@ -10,10 +10,6 @@ Demonstrates creating a Lambda Function from a local folder. | [terraform](#requirement\_terraform) | ~> 1.5 | | [aws](#requirement\_aws) | ~> 5.14 | -## Providers - -No providers. - ## Modules | Name | Source | Version | @@ -29,25 +25,25 @@ No resources. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [resource\_names\_map](#input\_resource\_names\_map) | A map of key to resource\_name that will be used by tf-launch-module\_library-resource\_name to generate resource names |
map(object({
name = string
max_length = optional(number, 60)
region = optional(string, "eastus2")
}))
|
{
"lambda_function": {
"max_length": 80,
"name": "fn",
"region": "us-east-2"
}
}
| no | -| [instance\_env](#input\_instance\_env) | Number that represents the instance of the environment. | `number` | `0` | no | -| [instance\_resource](#input\_instance\_resource) | Number that represents the instance of the resource. | `number` | `0` | no | -| [logical\_product\_family](#input\_logical\_product\_family) | (Required) Name of the product family for which the resource is created.
Example: org\_name, department\_name. | `string` | `"launch"` | no | -| [logical\_product\_service](#input\_logical\_product\_service) | (Required) Name of the product service for which the resource is created.
For example, backend, frontend, middleware etc. | `string` | `"lambda"` | no | | [class\_env](#input\_class\_env) | (Required) Environment where resource is going to be deployed. For example. dev, qa, uat | `string` | `"demo"` | no | | [create\_package](#input\_create\_package) | Controls whether Lambda package should be created | `bool` | `false` | no | | [handler](#input\_handler) | Lambda Function entrypoint in your code | `string` | `"index.lambda_handler"` | no | +| [instance\_env](#input\_instance\_env) | Number that represents the instance of the environment. | `number` | `0` | no | +| [instance\_resource](#input\_instance\_resource) | Number that represents the instance of the resource. | `number` | `0` | no | +| [logical\_product\_family](#input\_logical\_product\_family) | (Required) Name of the product family for which the resource is created.
Example: org\_name, department\_name. | `string` | `"launch"` | no | +| [logical\_product\_service](#input\_logical\_product\_service) | (Required) Name of the product service for which the resource is created.
For example, backend, frontend, middleware etc. | `string` | `"lambda"` | no | +| [resource\_names\_map](#input\_resource\_names\_map) | A map of key to resource\_name that will be used by tf-launch-module\_library-resource\_name to generate resource names |
map(object({
name = string
max_length = optional(number, 60)
region = optional(string, "eastus2")
}))
|
{
"lambda_function": {
"max_length": 80,
"name": "fn",
"region": "us-east-2"
}
}
| no | | [source\_path](#input\_source\_path) | The absolute path to a local file or directory containing your Lambda source code. Only valid if `create_package` is set to `true`. | `any` | `null` | no | ## Outputs | Name | Description | |------|-------------| -| [lambda\_function\_arn](#output\_lambda\_function\_arn) | n/a | -| [lambda\_function\_name](#output\_lambda\_function\_name) | n/a | | [lambda\_cloudwatch\_log\_group\_arn](#output\_lambda\_cloudwatch\_log\_group\_arn) | n/a | | [lambda\_cloudwatch\_log\_group\_name](#output\_lambda\_cloudwatch\_log\_group\_name) | n/a | +| [lambda\_function\_arn](#output\_lambda\_function\_arn) | n/a | +| [lambda\_function\_name](#output\_lambda\_function\_name) | n/a | | [lambda\_function\_url](#output\_lambda\_function\_url) | n/a | | [lambda\_role\_arn](#output\_lambda\_role\_arn) | n/a | | [lambda\_role\_name](#output\_lambda\_role\_name) | n/a | - + diff --git a/examples/source_from_zip/README.md b/examples/source_from_zip/README.md index 5e5139d..dd48b7f 100644 --- a/examples/source_from_zip/README.md +++ b/examples/source_from_zip/README.md @@ -2,7 +2,7 @@ Demonstrates creating a Lambda Function from a zip file. - + ## Requirements | Name | Version | @@ -10,10 +10,6 @@ Demonstrates creating a Lambda Function from a zip file. | [terraform](#requirement\_terraform) | ~> 1.5 | | [aws](#requirement\_aws) | ~> 5.14 | -## Providers - -No providers. - ## Modules | Name | Source | Version | @@ -29,25 +25,25 @@ No resources. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [resource\_names\_map](#input\_resource\_names\_map) | A map of key to resource\_name that will be used by tf-launch-module\_library-resource\_name to generate resource names |
map(object({
name = string
max_length = optional(number, 60)
region = optional(string, "eastus2")
}))
|
{
"lambda_function": {
"max_length": 80,
"name": "fn",
"region": "us-east-2"
}
}
| no | -| [instance\_env](#input\_instance\_env) | Number that represents the instance of the environment. | `number` | `0` | no | -| [instance\_resource](#input\_instance\_resource) | Number that represents the instance of the resource. | `number` | `0` | no | -| [logical\_product\_family](#input\_logical\_product\_family) | (Required) Name of the product family for which the resource is created.
Example: org\_name, department\_name. | `string` | `"launch"` | no | -| [logical\_product\_service](#input\_logical\_product\_service) | (Required) Name of the product service for which the resource is created.
For example, backend, frontend, middleware etc. | `string` | `"lambda"` | no | | [class\_env](#input\_class\_env) | (Required) Environment where resource is going to be deployed. For example. dev, qa, uat | `string` | `"demo"` | no | | [create\_package](#input\_create\_package) | Controls whether Lambda package should be created | `bool` | `false` | no | | [handler](#input\_handler) | Lambda Function entrypoint in your code | `string` | `"index.lambda_handler"` | no | +| [instance\_env](#input\_instance\_env) | Number that represents the instance of the environment. | `number` | `0` | no | +| [instance\_resource](#input\_instance\_resource) | Number that represents the instance of the resource. | `number` | `0` | no | +| [logical\_product\_family](#input\_logical\_product\_family) | (Required) Name of the product family for which the resource is created.
Example: org\_name, department\_name. | `string` | `"launch"` | no | +| [logical\_product\_service](#input\_logical\_product\_service) | (Required) Name of the product service for which the resource is created.
For example, backend, frontend, middleware etc. | `string` | `"lambda"` | no | +| [resource\_names\_map](#input\_resource\_names\_map) | A map of key to resource\_name that will be used by tf-launch-module\_library-resource\_name to generate resource names |
map(object({
name = string
max_length = optional(number, 60)
region = optional(string, "eastus2")
}))
|
{
"lambda_function": {
"max_length": 80,
"name": "fn",
"region": "us-east-2"
}
}
| no | | [zip\_file\_path](#input\_zip\_file\_path) | Path of the source zip file with respect to module root | `string` | `null` | no | ## Outputs | Name | Description | |------|-------------| -| [lambda\_function\_arn](#output\_lambda\_function\_arn) | n/a | -| [lambda\_function\_name](#output\_lambda\_function\_name) | n/a | | [lambda\_cloudwatch\_log\_group\_arn](#output\_lambda\_cloudwatch\_log\_group\_arn) | n/a | | [lambda\_cloudwatch\_log\_group\_name](#output\_lambda\_cloudwatch\_log\_group\_name) | n/a | +| [lambda\_function\_arn](#output\_lambda\_function\_arn) | n/a | +| [lambda\_function\_name](#output\_lambda\_function\_name) | n/a | | [lambda\_function\_url](#output\_lambda\_function\_url) | n/a | | [lambda\_role\_arn](#output\_lambda\_role\_arn) | n/a | | [lambda\_role\_name](#output\_lambda\_role\_name) | n/a | - + diff --git a/tests/post_deploy_functional/main_test.go b/tests/post_deploy_functional/main_test.go index 9ef78f2..c4d71b4 100644 --- a/tests/post_deploy_functional/main_test.go +++ b/tests/post_deploy_functional/main_test.go @@ -25,7 +25,7 @@ const ( infraTFVarFileNameDefault = "test.tfvars" ) -func TestLambdaLayerModule(t *testing.T) { +func TestLambdaFunctionModule(t *testing.T) { ctx := types.CreateTestContextBuilder(). SetTestConfig(&testimpl.ThisTFModuleConfig{}). diff --git a/tests/post_deploy_functional_readonly/main_test.go b/tests/post_deploy_functional_readonly/main_test.go index 9ef78f2..fa84952 100644 --- a/tests/post_deploy_functional_readonly/main_test.go +++ b/tests/post_deploy_functional_readonly/main_test.go @@ -25,18 +25,13 @@ const ( infraTFVarFileNameDefault = "test.tfvars" ) -func TestLambdaLayerModule(t *testing.T) { +func TestLambdaFunctionModuleReadOnly(t *testing.T) { ctx := types.CreateTestContextBuilder(). SetTestConfig(&testimpl.ThisTFModuleConfig{}). SetTestConfigFolderName(testConfigsExamplesFolderDefault). SetTestConfigFileName(infraTFVarFileNameDefault). - SetTestSpecificFlags(map[string]types.TestFlags{ - "source_from_folder": { - "IS_TERRAFORM_IDEMPOTENT_APPLY": false, - }, - }). Build() - lib.RunSetupTestTeardown(t, *ctx, testimpl.TestComposableComplete) + lib.RunNonDestructiveTest(t, *ctx, testimpl.TestComposableCompleteReadOnly) } diff --git a/tests/testimpl/test_impl.go b/tests/testimpl/test_impl.go index fc8cce1..c42d8d8 100644 --- a/tests/testimpl/test_impl.go +++ b/tests/testimpl/test_impl.go @@ -15,12 +15,11 @@ import ( "github.com/stretchr/testify/require" ) -func TestComposableComplete(t *testing.T, ctx types.TestContext) { +func TestComposableCompleteReadOnly(t *testing.T, ctx types.TestContext) { lambdaClient := GetAWSLambdaClient(t) functionArn := terraform.Output(t, ctx.TerratestTerraformOptions(), "lambda_function_arn") functionName := terraform.Output(t, ctx.TerratestTerraformOptions(), "lambda_function_name") - functionUrl := terraform.Output(t, ctx.TerratestTerraformOptions(), "lambda_function_url") t.Run("TestLambdaFunctionExists", func(t *testing.T) { function, err := lambdaClient.GetFunction(context.TODO(), &lambda.GetFunctionInput{ @@ -33,6 +32,12 @@ func TestComposableComplete(t *testing.T, ctx types.TestContext) { assert.Equal(t, *function.Configuration.FunctionArn, functionArn, "Expected ARN did not match actual ARN!") assert.Equal(t, *function.Configuration.FunctionName, functionName, "Expected Name did not match actual Name!") }) +} + +func TestComposableComplete(t *testing.T, ctx types.TestContext) { + TestComposableCompleteReadOnly(t, ctx) + + functionUrl := terraform.Output(t, ctx.TerratestTerraformOptions(), "lambda_function_url") t.Run("InvokeExampleSourceFromFolder", func(t *testing.T) { ctx.EnabledOnlyForTests(t, "source_from_folder")