diff --git a/Containerfile b/Containerfile index bd9d31b..a5b62b3 100644 --- a/Containerfile +++ b/Containerfile @@ -22,9 +22,7 @@ COPY --from=builder /build/patternizer /usr/local/bin/patternizer ARG PATTERNIZER_RESOURCES_DIR=/tmp/resources WORKDIR ${PATTERNIZER_RESOURCES_DIR} -COPY pattern.sh . -COPY values-secret.yaml.template . -COPY Makefile-pattern . +COPY resources/* . ARG PATTERN_REPO_ROOT=/repo WORKDIR ${PATTERN_REPO_ROOT} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile index e9a4ec4..80c3af3 100644 --- a/Makefile +++ b/Makefile @@ -63,9 +63,16 @@ test-coverage: ## Run unit tests with coverage report cd $(SRC_DIR) && $(GO_TEST) ./... -coverprofile=coverage.out cd $(SRC_DIR) && $(GO_CMD) tool cover -func=coverage.out +# Shellcheck for integration test script +.PHONY: shellcheck +shellcheck: ## Run shellcheck on integration test script + @echo "Running shellcheck on integration test script..." + @podman run --pull always -v "$(PWD):/mnt:z" docker.io/koalaman/shellcheck:stable test/integration_test.sh + @echo "Shellcheck passed" + # Integration tests .PHONY: test-integration -test-integration: build ## Run integration tests +test-integration: build shellcheck ## Run integration tests @echo "Running integration tests..." PATTERNIZER_BINARY=./$(SRC_DIR)/$(BINARY_NAME) ./test/integration_test.sh diff --git a/README.md b/README.md index c87a183..d1f9106 100644 --- a/README.md +++ b/README.md @@ -3,339 +3,193 @@ [![Quay Repository](https://img.shields.io/badge/Quay.io-patternizer-blue?logo=quay)](https://quay.io/repository/dminnear/patternizer) [![CI Pipeline](https://github.com/dminnear-rh/patternizer/actions/workflows/ci.yaml/badge.svg?branch=main)](https://github.com/dminnear-rh/patternizer/actions/workflows/ci.yaml) -> **Note:** This tool was developed with assistance from [Cursor](https://cursor.sh), an AI-powered code editor. - -**Patternizer** is a CLI tool and container utility designed to bootstrap Validated Pattern repositories. It automatically generates the necessary `values-global.yaml` and `values-.yaml` files by inspecting Git repositories, discovering Helm charts, and applying sensible defaults. +**Patternizer** is a command-line tool that bootstraps a Git repository containing Helm charts into a ready-to-use Validated Pattern. It automatically generates the necessary scaffolding, configuration files, and utility scripts, so you can get your pattern up and running in minutes. -The tool provides both a standalone CLI and containerized execution for maximum flexibility and consistency across environments. +> **Note:** This tool was developed with assistance from [Cursor](https://cursor.sh), an AI-powered code editor. ---- +- [Patternizer](#patternizer) + - [Features](#features) + - [Quick Start](#quick-start) + - [Example Workflow](#example-workflow) + - [Usage Details](#usage-details) + - [Container Usage (Recommended)](#container-usage-recommended) + - [**Initialize without secrets:**](#initialize-without-secrets) + - [**Initialize with secrets support:**](#initialize-with-secrets-support) + - [Understanding Secrets Management](#understanding-secrets-management) + - [Generated Files](#generated-files) + - [Development \& Contributing](#development--contributing) + - [Prerequisites](#prerequisites) + - [Local Development Workflow](#local-development-workflow) + - [Common Makefile Targets](#common-makefile-targets) + - [Testing Strategy](#testing-strategy) + - [Architecture](#architecture) + - [CI/CD Pipeline](#cicd-pipeline) + - [How to Contribute](#how-to-contribute) ## Features -- 🚀 **CLI-first design** with intuitive commands and help system -- 📦 **Container support** for consistent execution across environments -- 🔍 **Auto-discovery** of Helm charts and Git repository metadata -- 🔐 **Secrets integration** with Vault and External Secrets support -- ✅ **Comprehensive testing** with unit and integration tests -- 🏗️ **Multi-stage builds** for minimal container images -- 🛠️ **Makefile-driven development** for consistent local development and CI + - 🚀 **CLI-first design** with intuitive commands and help system + - 📦 **Container-native** for consistent execution across all environments + - 🔍 **Auto-discovery** of Helm charts and Git repository metadata + - 🔐 **Optional secrets integration** with Vault and External Secrets + - 🏗️ **Makefile-driven** utility scripts for easy pattern management -## Quick Start for Developers +## Quick Start -```bash -# Clone the repository -git clone https://github.com/dminnear-rh/patternizer.git -cd patternizer +This guide assumes you have a Git repository containing one or more Helm charts. -# Set up development environment -make dev-setup +**Prerequisites:** -# See all available targets -make help + * Podman or Docker + * A Git repository you want to convert into a pattern -# Build and test -make build -make test -``` - ---- - -## CLI Usage - -### Available Commands +Navigate to your repository's root directory and run the initialization command: ```bash -# Show help and available commands -patternizer help - -# Initialize pattern files (without secrets) -patternizer init - -# Initialize pattern files with secrets support -patternizer init --with-secrets - -# Show help for specific commands -patternizer init help +# In the root of your pattern-repo +podman run --pull=always --rm -it -v .:/repo:z quay.io/dminnear/patternizer init ``` -### Output Files - -The `patternizer init` command generates: - -- `values-global.yaml` - Global pattern configuration with `global.secretLoader.disabled: true` -- `values-.yaml` - Cluster group-specific configuration -- `pattern.sh` - Utility script for pattern operations -- `Makefile` - Simple include-based Makefile that includes `Makefile-pattern` -- `Makefile-pattern` - Contains all pattern targets and dynamically reads secrets config from `values-global.yaml` - -When using `--with-secrets`: -- `values-secret.yaml.template` - Template for secrets configuration -- `values-global.yaml` with `global.secretLoader.disabled: false` (enables secrets) -- Additional applications (vault, golang-external-secrets) in cluster group values - -The secrets loading behavior is controlled entirely by the `global.secretLoader.disabled` field in `values-global.yaml`. - ---- - -## Container Usage +This single command will generate all the necessary files to turn your repository into a Validated Pattern. -Use the prebuilt container from Quay without needing to install anything locally: - -### Basic Initialization - -```bash -# Navigate to your pattern repository -cd /path/to/your/pattern-repo - -# Initialize without secrets -podman run --rm -it -v .:/repo:z quay.io/dminnear/patternizer init +## Example Workflow -# Initialize with secrets support -podman run --rm -it -v .:/repo:z quay.io/dminnear/patternizer init --with-secrets -``` +1. **Clone or create your pattern repository:** ---- + ```bash + git clone https://github.com/your-org/your-pattern.git + cd your-pattern + git checkout -b initialize-pattern + ``` -## Example Workflow +2. **Initialize the pattern using Patternizer:** -1. **Clone or create a pattern repository:** - ```bash - git clone https://github.com/your-org/your-pattern.git - cd your-pattern - ``` + ```bash + podman run --pull=always --rm -it -v .:/repo:z quay.io/dminnear/patternizer init + ``` -2. **Initialize the pattern:** - ```bash - podman run --rm -it -v .:/repo:z quay.io/dminnear/patternizer init - ``` +3. **Review, commit, and push the generated files:** -3. **Review generated files:** - ```bash - ls -la values-*.yaml pattern.sh Makefile* - ``` + ```bash + git status + git add . + git commit -m 'initialize pattern using patternizer' + git push -u origin initialize-pattern + ``` -4. **Install the pattern:** - ```bash - ./pattern.sh make install - ``` +4. **Install the pattern:** ---- + ```bash + ./pattern.sh make install + ``` -## Development +## Usage Details -### Prerequisites +### Container Usage (Recommended) -- Go 1.24+ -- Podman or Docker -- Git -- Make +Using the prebuilt container is the easiest way to run Patternizer, as it requires no local installation. The `-v .:/repo:z` flag mounts your current directory into the container's `/repo` workspace. -### Quick Start +#### **Initialize without secrets:** ```bash -# Set up development environment (installs dependencies and tools) -make dev-setup - -# Show all available targets -make help +podman run --pull=always --rm -it -v .:/repo:z quay.io/dminnear/patternizer init ``` -### Common Development Tasks +#### **Initialize with secrets support:** ```bash -# Build the CLI -make build - -# Run all tests (unit + integration) -make test - -# Run only unit tests -make test-unit - -# Run only integration tests -make test-integration - -# Build container image locally -make local-container-build - -# Run full CI pipeline locally -make ci - -# Quick feedback loop (format check, vet, build, unit tests) -make check +podman run --pull=always --rm -it -v .:/repo:z quay.io/dminnear/patternizer init --with-secrets ``` -### Code Quality +### Understanding Secrets Management -The project uses comprehensive linting and formatting: +You can start simple and add secrets management later. -```bash -# Run all linting checks (gofmt, go vet, golangci-lint) -make lint + * By default, `patternizer init` disables secret loading. + * To add secrets scaffolding, run `patternizer init --with-secrets` at any time. This will update your configuration to enable secrets. + * **Important:** This action is not easily reversible. We recommend committing your work to Git *before* adding secrets support. -# Format code -make fmt +For more details on how secrets work in the framework, see the [Secrets Management Documentation](https://validatedpatterns.io/learn/secrets-management-in-the-validated-patterns-framework/). -# Run individual lint checks -make lint-fmt # gofmt check -make lint-vet # go vet -make lint-golangci # golangci-lint -``` +### Generated Files ---- - -## Testing - -### Unit Tests - -The project includes comprehensive unit tests across multiple packages: - -**Main Package Tests (`src/main_test.go`):** -- `TestGetResourcePath()` - Resource path resolution with and without environment variables -- `TestNewDefaultValuesGlobal()` - Global configuration default values validation -- `TestNewDefaultValuesClusterGroup()` - Cluster group configuration generation and secrets integration - -**Helm Package Tests (`src/internal/helm/helm_test.go`):** -- `TestFindTopLevelCharts()` - Helm chart discovery functionality with comprehensive test scenarios: - - Correctly identifies valid top-level charts (chart1, chart2) - - Properly skips sub-charts in `charts/` directories - - Ignores hidden directories (`.hidden-chart`) and invalid chart structures - - Handles edge cases: missing Chart.yaml, missing values.yaml, missing templates directory, templates as file -- `TestIsHelmChart()` - Chart structure validation: - - Validates required files: Chart.yaml, values.yaml, templates/ directory - - Tests various invalid configurations and edge cases - -**Pattern Package Tests (`src/internal/pattern/pattern_test.go`):** -- `TestExtractPatternNameFromURL()` - Git URL parsing for multiple formats: - - SSH URLs: `git@github.com:user/repo.git`, `git@gitlab.com:group/subgroup/repo.git` - - HTTPS/HTTP URLs: `https://github.com/user/repo.git`, `http://github.com/user/repo` - - Error handling for invalid URLs and unsupported protocols -- `TestProcessGlobalValuesPreservesFields()` - Field preservation during YAML processing: - - Preserves existing custom fields at all nesting levels - - Maintains custom arrays, nested objects, and primitive values - - Intelligently merges new defaults with existing configurations -- `TestProcessClusterGroupValuesPreservesFields()` - Cluster group values field preservation: - - Preserves custom applications, subscriptions, and cluster-level fields - - Adds new applications while maintaining existing ones - - Maintains custom fields within applications and subscriptions -- `TestProcessGlobalValuesWithNewFile()` - New file creation with proper defaults -- `TestProcessGlobalValuesWithSecrets()` - Validates secrets configuration: - - Tests `ProcessGlobalValues` with `withSecrets=true` - - Verifies `global.secretLoader.disabled: false` is set correctly - - Ensures secrets-enabled configuration is properly generated - -### Integration Tests - -The integration test suite (`test/integration_test.sh`) validates the complete CLI workflow with four comprehensive test scenarios: - -**Test 1: Basic Initialization (Without Secrets)** -- Clones the [trivial-pattern](https://github.com/dminnear-rh/trivial-pattern) repository -- Runs `patternizer init` and validates generated files -- Verifies `values-global.yaml` contains `global.secretLoader.disabled: true` -- Validates `values-prod.yaml` content using YAML normalization -- Ensures `pattern.sh` is created and executable -- Validates `Makefile` (include-based) and `Makefile-pattern` are created - -**Test 2: Initialization with Secrets** -- Tests `patternizer init --with-secrets` functionality -- Verifies `values-global.yaml` contains `global.secretLoader.disabled: false` -- Validates secrets-specific applications (vault, golang-external-secrets) are added -- Verifies additional namespaces and `values-secret.yaml.template` are created -- Ensures `pattern.sh` and both Makefile files are properly generated - -**Test 3: Custom Pattern and Cluster Group Names** -- Tests field preservation and intelligent merging of existing configurations -- Pre-populates custom `values-global.yaml` with renamed pattern and cluster group -- Verifies custom names are preserved while adding missing default configurations -- Validates custom cluster group file generation (e.g., `values-renamed-cluster-group.yaml`) -- Ensures `global.secretLoader.disabled: false` is set correctly with `--with-secrets` - -**Test 4: Sequential Execution** -- Tests running `patternizer init` followed by `patternizer init --with-secrets` -- Validates that the second command properly upgrades the configuration -- Ensures `global.secretLoader.disabled` transitions from `true` to `false` -- Verifies final state matches direct `--with-secrets` execution - -Run integration tests locally: -```bash -# Run integration tests (automatically builds binary first) -make test-integration +Running `patternizer init` creates the following: -# Or run all tests (unit + integration) -make test -``` + * `values-global.yaml`: Global pattern configuration. + * `values-.yaml`: Cluster group-specific values. + * `pattern.sh`: A utility script for common pattern operations (`install`, `upgrade`, etc.). + * `Makefile`: A simple Makefile that includes `Makefile-pattern`. + * `Makefile-pattern`: The core Makefile with all pattern-related targets. ---- +Using the `--with-secrets` flag additionally creates: -## CI/CD Pipeline + * `values-secret.yaml.template`: A template for defining your secrets. + * It also updates `values-global.yaml` to set `global.secretLoader.disabled: false` and adds Vault and External Secrets Operator to the cluster group values. -The project uses a comprehensive CI pipeline with three stages that leverage the Makefile for consistency: +## Development & Contributing -1. **Lint & Format**: `make lint` - Code quality checks with `gofmt`, `go vet`, and `golangci-lint` -2. **Build & Test**: `make build`, `make test-unit`, `make test-coverage`, `make test-integration` -3. **Container Build**: Multi-stage container build and push to Quay.io +This section is for developers who want to contribute to the Patternizer project itself. -All code must pass linting and tests before being merged or deployed. +### Prerequisites -The CI pipeline uses the same Makefile targets that developers use locally, ensuring perfect consistency between local development and CI environments. You can run the same checks locally with `make ci`. + * Go (see `go.mod` for version) + * Podman or Docker + * Git + * Make ---- +### Local Development Workflow -## Architecture +```bash +# 1. Clone the repository +git clone https://github.com/dminnear-rh/patternizer.git +cd patternizer -The CLI is organized into focused packages following Go best practices: +# 2. Set up the development environment (installs tools) +make dev-setup -**Main Package (`src/`):** -- `main.go` - Application entry point +# 3. Make your changes... -**Command Package (`src/cmd/`):** -- `root.go` - Cobra CLI setup and root command -- `init.go` - Initialization command logic and orchestration +# 4. Run the full CI suite locally before committing +make ci +``` -**Internal Packages (`src/internal/`):** -- `fileutils/` - File operations, resource management, and path resolution -- `helm/` - Helm chart discovery and validation -- `pattern/` - Core pattern processing, Git operations, and URL parsing -- `types/` - YAML structure definitions and default value constructors +### Common Makefile Targets -**Key Design Principles:** -- **Separation of Concerns**: Each package has a single, well-defined responsibility -- **Testability**: All packages are thoroughly unit tested with comprehensive coverage -- **Field Preservation**: YAML processing preserves all user-defined custom fields -- **Error Handling**: Comprehensive error handling with descriptive messages -- **Modularity**: Clean interfaces between packages for maintainability +The `Makefile` is the single source of truth for all development and CI tasks. -This modular design makes the codebase maintainable, testable, and extensible. + * `make help`: Show all available targets. + * `make check`: Quick feedback loop (format, vet, build, unit tests). + * `make build`: Build the `patternizer` binary. + * `make test`: Run all tests (unit and integration). + * `make test-unit`: Run unit tests only. + * `make test-integration`: Run integration tests only. + * `make lint`: Run all code quality checks. + * `make local-container-build`: Build the container image locally. ---- +### Testing Strategy -## Contributing +Patternizer has a comprehensive test suite to ensure stability and correctness. -1. Fork the repository -2. Create a feature branch -3. Make your changes -4. Run the development workflow: - ```bash - make dev-setup # Set up development environment - make check # Quick feedback loop - make test # Run all tests - make lint # Run all linting checks - ``` -5. Submit a pull request + * **Unit Tests:** Located alongside the code they test (e.g., `src/internal/helm/helm_test.go`), these tests cover individual functions and packages in isolation. They validate Helm chart detection, Git URL parsing, and YAML processing logic. + * **Integration Tests:** The integration test suite (`test/integration_test.sh`) validates the end-to-end CLI workflow against a real Git repository. Key scenarios include: + 1. **Basic Init:** Validates default file generation without secrets. + 2. **Init with Secrets:** Ensures secrets-related applications and files are correctly added. + 3. **Configuration Preservation:** Verifies that existing custom values are preserved when the tool is re-run. + 4. **Sequential Execution:** Tests running `init` and then `init --with-secrets` to ensure a clean upgrade. + 5. **Selective File Overwriting:** Confirms that running `init` on a repository with pre-existing custom files correctly **merges YAML configurations**, preserves user-modified files (like `Makefile` and `values-secret.yaml.template`), and only overwrites essential, generated scripts (`pattern.sh`, `Makefile-pattern`). + 6. **Mixed State Handling:** Validates that the tool correctly initializes a partially-configured repository, **creating files that are missing** while leaving existing ones untouched. -All contributions must pass the CI pipeline including linting, formatting, and comprehensive testing. +### Architecture -### Development Workflow +The CLI is organized into focused packages following Go best practices, with a clean separation of concerns between command-line logic (`cmd`), core business logic (`internal`), and file operations (`fileutils`). This modular design makes the codebase maintainable, testable, and extensible. -For the best development experience: -```bash -# Initial setup -make dev-setup +### CI/CD Pipeline -# During development (fast feedback) -make check +The GitHub Actions pipeline (`.github/workflows/ci.yaml`) runs on every push and pull request. It uses the same `Makefile` targets that developers use locally, ensuring perfect consistency between local and CI environments. -# Before committing -make ci # Runs the full CI pipeline locally -``` +### How to Contribute + +1. Fork the repository. +2. Create a feature branch for your changes. +3. Make your changes and ensure they pass the local CI check (`make ci`). +4. Submit a pull request. diff --git a/resources/Makefile b/resources/Makefile new file mode 100644 index 0000000..abc130d --- /dev/null +++ b/resources/Makefile @@ -0,0 +1,5 @@ +# Generated by patternizer +# This Makefile includes the common pattern targets from Makefile-pattern +# You can add custom targets above or below the include line + +include Makefile-pattern diff --git a/Makefile-pattern b/resources/Makefile-pattern similarity index 100% rename from Makefile-pattern rename to resources/Makefile-pattern diff --git a/pattern.sh b/resources/pattern.sh similarity index 100% rename from pattern.sh rename to resources/pattern.sh diff --git a/values-secret.yaml.template b/resources/values-secret.yaml.template similarity index 100% rename from values-secret.yaml.template rename to resources/values-secret.yaml.template diff --git a/src/cmd/init.go b/src/cmd/init.go index 60bac3f..f010baf 100644 --- a/src/cmd/init.go +++ b/src/cmd/init.go @@ -36,7 +36,7 @@ func runInit(withSecrets bool) error { } // Copy pattern.sh and Makefile from resources - resourcesDir, err := fileutils.GetResourcePath() + resourcesDir, err := fileutils.GetResourcesPath() if err != nil { return fmt.Errorf("error getting resource path: %w", err) } @@ -55,10 +55,11 @@ func runInit(withSecrets bool) error { } // Create a simple Makefile that includes Makefile-pattern (only if it doesn't exist) + makefileSrc := filepath.Join(resourcesDir, "Makefile") makefileDst := filepath.Join(repoRoot, "Makefile") if _, err := os.Stat(makefileDst); os.IsNotExist(err) { - if err := fileutils.CreateIncludeMakefile(makefileDst); err != nil { - return fmt.Errorf("error creating Makefile: %w", err) + if err := fileutils.CopyFile(makefileSrc, makefileDst); err != nil { + return fmt.Errorf("error copying Makefile: %w", err) } } diff --git a/src/internal/fileutils/fileutils.go b/src/internal/fileutils/fileutils.go index ba51940..adc8246 100644 --- a/src/internal/fileutils/fileutils.go +++ b/src/internal/fileutils/fileutils.go @@ -47,47 +47,28 @@ func CopyFile(src, dst string) error { // HandleSecretsSetup handles the setup for secrets usage by copying the secrets template. func HandleSecretsSetup(resourcesDir, repoRoot string) (err error) { - // Copy the values-secret.yaml.template file to the pattern root + // Copy the values-secret.yaml.template file to the pattern root only if it doesn't already exist secretsTemplateSrc := filepath.Join(resourcesDir, "values-secret.yaml.template") secretsTemplateDst := filepath.Join(repoRoot, "values-secret.yaml.template") - if err = CopyFile(secretsTemplateSrc, secretsTemplateDst); err != nil { - return fmt.Errorf("error copying secrets template: %w", err) + if _, err := os.Stat(secretsTemplateDst); os.IsNotExist(err) { + if err = CopyFile(secretsTemplateSrc, secretsTemplateDst); err != nil { + return fmt.Errorf("error copying secrets template: %w", err) + } } return nil } -// CreateIncludeMakefile creates a simple Makefile that includes Makefile-pattern. -func CreateIncludeMakefile(makefilePath string) error { - content := `# Generated by patternizer -# This Makefile includes the common pattern targets from Makefile-pattern -# You can add custom targets above or below the include line - -include Makefile-pattern -` - - if err := os.WriteFile(makefilePath, []byte(content), 0o644); err != nil { - return fmt.Errorf("failed to create Makefile: %w", err) - } - - return nil -} - -// GetResourcePath returns the path to the resources directory. +// GetResourcesPath returns the path to the resources directory. // It checks the PATTERNIZER_RESOURCES_DIR environment variable first, // and falls back to the current working directory. -func GetResourcePath() (path string, err error) { +func GetResourcesPath() (path string, err error) { path = os.Getenv("PATTERNIZER_RESOURCES_DIR") if path != "" { return path, nil } - // Fall back to current directory - path, err = os.Getwd() - if err != nil { - return "", fmt.Errorf("failed to get current directory: %w", err) - } - - return path, nil + // Error out if the resources directory is not found + return "", fmt.Errorf("PATTERNIZER_RESOURCES_DIR environment variable is not set") } diff --git a/src/main_test.go b/src/main_test.go index 77b85ce..b48bda1 100644 --- a/src/main_test.go +++ b/src/main_test.go @@ -11,7 +11,7 @@ import ( func TestGetResourcePath(t *testing.T) { // Test with environment variable set os.Setenv("PATTERNIZER_RESOURCES_DIR", "/tmp/test") - path, err := fileutils.GetResourcePath() + path, err := fileutils.GetResourcesPath() if err != nil { t.Fatalf("Expected no error, got %v", err) } @@ -21,13 +21,12 @@ func TestGetResourcePath(t *testing.T) { // Test with environment variable unset os.Unsetenv("PATTERNIZER_RESOURCES_DIR") - path, err = fileutils.GetResourcePath() - if err != nil { - t.Fatalf("Expected no error, got %v", err) + path, err = fileutils.GetResourcesPath() + if err == nil { + t.Fatal("Expected error, got nil") } - // Should return current directory - if path == "" { - t.Fatalf("Expected non-empty path") + if path != "" { + t.Fatalf("Expected empty path, got %s", path) } } diff --git a/test/expected_values_custom_cluster_overwrite.yaml b/test/expected_values_custom_cluster_overwrite.yaml new file mode 100644 index 0000000..1189dde --- /dev/null +++ b/test/expected_values_custom_cluster_overwrite.yaml @@ -0,0 +1,42 @@ +clusterGroup: + name: custom-cluster + isHubCluster: true + customClusterField: user-cluster-config + namespaces: + - custom-pattern-name + - vault + - golang-external-secrets + projects: + - custom-pattern-name + - custom-cluster + subscriptions: {} + applications: + custom-user-app: + customAppField: user-app-config + name: custom-user-app + namespace: user-namespace + path: user/path + project: custom-pattern-name + golang-external-secrets: + name: golang-external-secrets + namespace: golang-external-secrets + project: custom-cluster + chart: golang-external-secrets + chartVersion: 0.1.* + simple: + name: simple + namespace: custom-pattern-name + project: custom-pattern-name + path: charts/simple + trivial: + name: trivial + namespace: custom-pattern-name + project: custom-pattern-name + path: charts/trivial + vault: + name: vault + namespace: vault + project: custom-cluster + chart: hashicorp-vault + chartVersion: 0.1.* +customClusterTopLevel: user-cluster-top-level diff --git a/test/expected_values_global_overwrite.yaml b/test/expected_values_global_overwrite.yaml new file mode 100644 index 0000000..1c74638 --- /dev/null +++ b/test/expected_values_global_overwrite.yaml @@ -0,0 +1,14 @@ +global: + pattern: custom-pattern-name + customGlobalField: user-custom-value + secretLoader: + disabled: false + customSecretField: user-secret-config +main: + clusterGroupName: custom-cluster + customMainField: user-main-value + multiSourceConfig: + enabled: true + clusterGroupChartVersion: 0.8.* + customMultiSourceField: user-multisource-config +customTopLevelField: user-top-level-value diff --git a/test/initial_makefile_overwrite b/test/initial_makefile_overwrite new file mode 100644 index 0000000..666754d --- /dev/null +++ b/test/initial_makefile_overwrite @@ -0,0 +1,6 @@ +# Custom user Makefile +# This should NOT be overwritten + +.PHONY: custom-target +custom-target: + @echo "This is a custom user target" diff --git a/test/initial_makefile_pattern_overwrite b/test/initial_makefile_pattern_overwrite new file mode 100644 index 0000000..610d40b --- /dev/null +++ b/test/initial_makefile_pattern_overwrite @@ -0,0 +1,6 @@ +# Old Makefile-pattern content +# This SHOULD be overwritten + +.PHONY: old-target +old-target: + @echo "This is old content" diff --git a/test/initial_pattern_sh_overwrite b/test/initial_pattern_sh_overwrite new file mode 100644 index 0000000..62c2639 --- /dev/null +++ b/test/initial_pattern_sh_overwrite @@ -0,0 +1,4 @@ +#!/bin/bash +# Old pattern.sh content +# This SHOULD be overwritten +echo "Old pattern.sh script" diff --git a/test/initial_values_custom_cluster_overwrite.yaml b/test/initial_values_custom_cluster_overwrite.yaml new file mode 100644 index 0000000..082a5fc --- /dev/null +++ b/test/initial_values_custom_cluster_overwrite.yaml @@ -0,0 +1,12 @@ +clusterGroup: + name: custom-cluster + isHubCluster: true + customClusterField: user-cluster-config + applications: + custom-user-app: + name: custom-user-app + namespace: user-namespace + path: user/path + customAppField: user-app-config + project: custom-pattern-name +customClusterTopLevel: user-cluster-top-level diff --git a/test/initial_values_global_overwrite.yaml b/test/initial_values_global_overwrite.yaml new file mode 100644 index 0000000..a347cf8 --- /dev/null +++ b/test/initial_values_global_overwrite.yaml @@ -0,0 +1,14 @@ +global: + pattern: custom-pattern-name + customGlobalField: user-custom-value + secretLoader: + disabled: true + customSecretField: user-secret-config +main: + clusterGroupName: custom-cluster + customMainField: user-main-value + multiSourceConfig: + enabled: true + clusterGroupChartVersion: 0.8.* + customMultiSourceField: user-multisource-config +customTopLevelField: user-top-level-value diff --git a/test/initial_values_secret_template_overwrite.yaml b/test/initial_values_secret_template_overwrite.yaml new file mode 100644 index 0000000..de1e5bc --- /dev/null +++ b/test/initial_values_secret_template_overwrite.yaml @@ -0,0 +1,12 @@ +# Custom user secrets template +# This should NOT be overwritten + +version: "2.0" +secrets: + - name: aws-creds + fields: + - name: aws_access_key_id + value: "An aws access key" + + - name: aws_secret_access_key + value: "An aws access secret key" diff --git a/test/integration_test.sh b/test/integration_test.sh index 97b2634..d5f5602 100755 --- a/test/integration_test.sh +++ b/test/integration_test.sh @@ -14,6 +14,9 @@ TEST_REPO_URL="https://github.com/dminnear-rh/trivial-pattern.git" TEST_DIR="/tmp/patternizer-integration-test" TEST_DIR_SECRETS="/tmp/patternizer-integration-test-secrets" TEST_DIR_CUSTOM="/tmp/patternizer-integration-test-custom" +TEST_DIR_SEQUENTIAL="/tmp/patternizer-integration-test-sequential" +TEST_DIR_OVERWRITE="/tmp/patternizer-integration-test-overwrite" +TEST_DIR_MIXED="/tmp/patternizer-integration-test-mixed" echo -e "${YELLOW}Starting patternizer integration tests...${NC}" @@ -27,6 +30,15 @@ fi if [ -d "$TEST_DIR_CUSTOM" ]; then rm -rf "$TEST_DIR_CUSTOM" fi +if [ -d "$TEST_DIR_SEQUENTIAL" ]; then + rm -rf "$TEST_DIR_SEQUENTIAL" +fi +if [ -d "$TEST_DIR_OVERWRITE" ]; then + rm -rf "$TEST_DIR_OVERWRITE" +fi +if [ -d "$TEST_DIR_MIXED" ]; then + rm -rf "$TEST_DIR_MIXED" +fi # Convert PATTERNIZER_BINARY to absolute path before changing directories PATTERNIZER_BINARY=$(realpath "$PATTERNIZER_BINARY") @@ -34,14 +46,31 @@ PATTERNIZER_BINARY=$(realpath "$PATTERNIZER_BINARY") # Get the absolute path to the repository root (where resource files are located) REPO_ROOT=$(pwd) +# Export resources directory so patternizer can find resource files +export PATTERNIZER_RESOURCES_DIR="$REPO_ROOT/resources" + # Set absolute paths to expected files -EXPECTED_VALUES_GLOBAL="$REPO_ROOT/test/expected_values_global.yaml" +EXPECTED_VALUES_CUSTOM_CLUSTER_OVERWRITE="$REPO_ROOT/test/expected_values_custom_cluster_overwrite.yaml" +EXPECTED_VALUES_GLOBAL_CUSTOM="$REPO_ROOT/test/expected_values_global_custom.yaml" +EXPECTED_VALUES_GLOBAL_OVERWRITE="$REPO_ROOT/test/expected_values_global_overwrite.yaml" EXPECTED_VALUES_GLOBAL_WITH_SECRETS="$REPO_ROOT/test/expected_values_global_with_secrets.yaml" -EXPECTED_VALUES_PROD="$REPO_ROOT/test/expected_values_prod.yaml" +EXPECTED_VALUES_GLOBAL="$REPO_ROOT/test/expected_values_global.yaml" EXPECTED_VALUES_PROD_WITH_SECRETS="$REPO_ROOT/test/expected_values_prod_with_secrets.yaml" -EXPECTED_VALUES_GLOBAL_CUSTOM="$REPO_ROOT/test/expected_values_global_custom.yaml" +EXPECTED_VALUES_PROD="$REPO_ROOT/test/expected_values_prod.yaml" EXPECTED_VALUES_RENAMED_CLUSTER_GROUP="$REPO_ROOT/test/expected_values_renamed_cluster_group.yaml" +INITIAL_MAKEFILE_OVERWRITE="$REPO_ROOT/test/initial_makefile_overwrite" +INITIAL_MAKEFILE_PATTERN_OVERWRITE="$REPO_ROOT/test/initial_makefile_pattern_overwrite" +INITIAL_PATTERN_SH_OVERWRITE="$REPO_ROOT/test/initial_pattern_sh_overwrite" +INITIAL_VALUES_CUSTOM_CLUSTER_OVERWRITE="$REPO_ROOT/test/initial_values_custom_cluster_overwrite.yaml" INITIAL_VALUES_GLOBAL_CUSTOM="$REPO_ROOT/test/initial_values_global_custom.yaml" +INITIAL_VALUES_GLOBAL_OVERWRITE="$REPO_ROOT/test/initial_values_global_overwrite.yaml" +INITIAL_VALUES_SECRET_TEMPLATE_OVERWRITE="$REPO_ROOT/test/initial_values_secret_template_overwrite.yaml" + +# Set paths for expected resource files +EXPECTED_MAKEFILE="$PATTERNIZER_RESOURCES_DIR/Makefile" +EXPECTED_MAKEFILE_PATTERN="$PATTERNIZER_RESOURCES_DIR/Makefile-pattern" +EXPECTED_PATTERN_SH="$PATTERNIZER_RESOURCES_DIR/pattern.sh" +EXPECTED_VALUES_SECRET_TEMPLATE="$PATTERNIZER_RESOURCES_DIR/values-secret.yaml.template" # Check if patternizer binary exists and is executable if [ ! -x "$PATTERNIZER_BINARY" ]; then @@ -92,26 +121,46 @@ except Exception as e: fi } -# Function to check file content -check_file_content() { - local file="$1" - local pattern="$2" +# Function to print test section headers +test_header() { + echo -e "${YELLOW}$1${NC}" +} + +# Function to print test pass messages +test_pass() { + echo -e "${GREEN}PASS: $1${NC}" +} + +# Function to print test fail messages and exit +test_fail() { + echo -e "${RED}FAIL: $1${NC}" + exit 1 +} + +# Function to compare two files exactly with diff, showing differences on failure +compare_files() { + local expected_file="$1" + local actual_file="$2" local description="$3" - if [ ! -f "$file" ]; then - echo -e "${RED}FAIL: $description - file not found: $file${NC}" - return 1 + if [ ! -f "$actual_file" ]; then + test_fail "$description - file not created: $actual_file" fi - if grep -q "$pattern" "$file"; then - echo -e "${GREEN}PASS: $description${NC}" + if [ ! -f "$expected_file" ]; then + test_fail "$description - expected file not found: $expected_file" + fi + + if diff "$expected_file" "$actual_file" > /dev/null; then + test_pass "$description" return 0 else echo -e "${RED}FAIL: $description${NC}" - echo "Pattern '$pattern' not found in $file" - echo "File contents:" - cat "$file" - return 1 + echo "Expected file: $expected_file" + echo "Actual file: $actual_file" + echo "Diff:" + diff "$expected_file" "$actual_file" || true + exit 1 fi } @@ -121,41 +170,26 @@ check_file_exists() { local description="$2" if [ -f "$file" ]; then - echo -e "${GREEN}PASS: $description${NC}" + test_pass "$description" return 0 else - echo -e "${RED}FAIL: $description - file not found: $file${NC}" - return 1 - fi -} - -# Function to check file/directory doesn't exist -check_not_exists() { - local path="$1" - local description="$2" - - if [ ! -e "$path" ]; then - echo -e "${GREEN}PASS: $description${NC}" - return 0 - else - echo -e "${RED}FAIL: $description - path still exists: $path${NC}" - return 1 + test_fail "$description - file not found: $file" fi } # # Test 1: Basic initialization (without secrets) # -echo -e "${YELLOW}=== Test 1: Basic initialization (without secrets) ===${NC}" +test_header "=== Test 1: Basic initialization (without secrets) ===" -echo -e "${YELLOW}Cloning test repository...${NC}" +test_header "Cloning test repository..." git clone "$TEST_REPO_URL" "$TEST_DIR" cd "$TEST_DIR" -echo -e "${YELLOW}Running patternizer init...${NC}" -PATTERNIZER_RESOURCES_DIR="$REPO_ROOT" "$PATTERNIZER_BINARY" init +test_header "Running patternizer init..." +"$PATTERNIZER_BINARY" init -echo -e "${YELLOW}Running verification tests...${NC}" +test_header "Running verification tests..." # Test 1.1: Check values-global.yaml compare_yaml "$EXPECTED_VALUES_GLOBAL" "values-global.yaml" "values-global.yaml content" @@ -171,28 +205,28 @@ else exit 1 fi -# Test 1.4: Check Makefile exists (simple include-based Makefile) -check_file_exists "Makefile" "Makefile exists (init without secrets)" +# Test 1.4: Check Makefile has exact expected content +compare_files "$EXPECTED_MAKEFILE" "Makefile" "Makefile has expected content (init without secrets)" -# Test 1.5: Check Makefile-pattern exists (contains the actual targets) -check_file_exists "Makefile-pattern" "Makefile-pattern exists (init without secrets)" +# Test 1.5: Check Makefile-pattern has exact expected content +compare_files "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" "Makefile-pattern has expected content (init without secrets)" -echo -e "${GREEN}=== Test 1: Basic initialization PASSED ===${NC}" +test_pass "=== Test 1: Basic initialization PASSED ===" # # Test 2: Initialization with secrets # -echo -e "${YELLOW}=== Test 2: Initialization with secrets ===${NC}" +test_header "=== Test 2: Initialization with secrets ===" cd "$REPO_ROOT" # Go back to repo root -echo -e "${YELLOW}Cloning test repository for secrets test...${NC}" +test_header "Cloning test repository for secrets test..." git clone "$TEST_REPO_URL" "$TEST_DIR_SECRETS" cd "$TEST_DIR_SECRETS" -echo -e "${YELLOW}Running patternizer init --with-secrets...${NC}" -PATTERNIZER_RESOURCES_DIR="$REPO_ROOT" "$PATTERNIZER_BINARY" init --with-secrets +test_header "Running patternizer init --with-secrets..." +"$PATTERNIZER_BINARY" init --with-secrets -echo -e "${YELLOW}Running verification tests for secrets...${NC}" +test_header "Running verification tests for secrets..." # Test 2.1: Check values-global.yaml (secretLoader.disabled should be false with secrets) compare_yaml "$EXPECTED_VALUES_GLOBAL_WITH_SECRETS" "values-global.yaml" "values-global.yaml content (with secrets)" @@ -208,34 +242,34 @@ else exit 1 fi -# Test 2.4: Check values-secret.yaml.template exists -check_file_exists "values-secret.yaml.template" "values-secret.yaml.template file exists" +# Test 2.4: Check values-secret.yaml.template has exact expected content +compare_files "$EXPECTED_VALUES_SECRET_TEMPLATE" "values-secret.yaml.template" "values-secret.yaml.template has expected content" -# Test 2.5: Check Makefile exists (simple include-based Makefile) -check_file_exists "Makefile" "Makefile exists (init with secrets)" +# Test 2.5: Check Makefile has exact expected content +compare_files "$EXPECTED_MAKEFILE" "Makefile" "Makefile has expected content (init with secrets)" -# Test 2.6: Check Makefile-pattern exists (contains the actual targets) -check_file_exists "Makefile-pattern" "Makefile-pattern exists (init with secrets)" +# Test 2.6: Check Makefile-pattern has exact expected content +compare_files "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" "Makefile-pattern has expected content (init with secrets)" -echo -e "${GREEN}=== Test 2: Initialization with secrets PASSED ===${NC}" +test_pass "=== Test 2: Initialization with secrets PASSED ===" # # Test 3: Custom pattern and cluster group names (merging test with secrets) # -echo -e "${YELLOW}=== Test 3: Custom pattern and cluster group names (with secrets) ===${NC}" +test_header "=== Test 3: Custom pattern and cluster group names (with secrets) ===" cd "$REPO_ROOT" # Go back to repo root -echo -e "${YELLOW}Cloning test repository for custom names test...${NC}" +test_header "Cloning test repository for custom names test..." git clone "$TEST_REPO_URL" "$TEST_DIR_CUSTOM" cd "$TEST_DIR_CUSTOM" -echo -e "${YELLOW}Setting up initial values-global.yaml with custom names...${NC}" +test_header "Setting up initial values-global.yaml with custom names..." cp "$INITIAL_VALUES_GLOBAL_CUSTOM" "values-global.yaml" -echo -e "${YELLOW}Running patternizer init --with-secrets (should preserve custom names)...${NC}" -PATTERNIZER_RESOURCES_DIR="$REPO_ROOT" "$PATTERNIZER_BINARY" init --with-secrets +test_header "Running patternizer init --with-secrets (should preserve custom names)..." +"$PATTERNIZER_BINARY" init --with-secrets -echo -e "${YELLOW}Running verification tests for custom names...${NC}" +test_header "Running verification tests for custom names..." # Test 3.1: Check values-global.yaml preserves custom names and adds multiSourceConfig compare_yaml "$EXPECTED_VALUES_GLOBAL_CUSTOM" "values-global.yaml" "values-global.yaml content (custom names)" @@ -251,41 +285,35 @@ else exit 1 fi -# Test 3.4: Check values-secret.yaml.template exists -check_file_exists "values-secret.yaml.template" "values-secret.yaml.template file exists (custom names)" +# Test 3.4: Check values-secret.yaml.template has exact expected content +compare_files "$EXPECTED_VALUES_SECRET_TEMPLATE" "values-secret.yaml.template" "values-secret.yaml.template has expected content (custom names)" -# Test 3.5: Check Makefile exists (simple include-based Makefile) -check_file_exists "Makefile" "Makefile exists (custom names with secrets)" +# Test 3.5: Check Makefile has exact expected content +compare_files "$EXPECTED_MAKEFILE" "Makefile" "Makefile has expected content (custom names with secrets)" -# Test 3.6: Check Makefile-pattern exists (contains the actual targets) -check_file_exists "Makefile-pattern" "Makefile-pattern exists (custom names with secrets)" +# Test 3.6: Check Makefile-pattern has exact expected content +compare_files "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" "Makefile-pattern has expected content (custom names with secrets)" -echo -e "${GREEN}=== Test 3: Custom pattern and cluster group names (with secrets) PASSED ===${NC}" +test_pass "=== Test 3: Custom pattern and cluster group names (with secrets) PASSED ===" # # Test 4: Sequential execution (init followed by init --with-secrets) # -echo -e "${YELLOW}=== Test 4: Sequential execution (init + init --with-secrets) ===${NC}" +test_header "=== Test 4: Sequential execution (init + init --with-secrets) ===" cd "$REPO_ROOT" # Go back to repo root -TEST_DIR_SEQUENTIAL="/tmp/patternizer-integration-test-sequential" -# Clean up any previous sequential test runs -if [ -d "$TEST_DIR_SEQUENTIAL" ]; then - rm -rf "$TEST_DIR_SEQUENTIAL" -fi - -echo -e "${YELLOW}Cloning test repository for sequential test...${NC}" +test_header "Cloning test repository for sequential test..." git clone "$TEST_REPO_URL" "$TEST_DIR_SEQUENTIAL" cd "$TEST_DIR_SEQUENTIAL" -echo -e "${YELLOW}Running patternizer init (first)...${NC}" -PATTERNIZER_RESOURCES_DIR="$REPO_ROOT" "$PATTERNIZER_BINARY" init +test_header "Running patternizer init (first)..." +"$PATTERNIZER_BINARY" init -echo -e "${YELLOW}Running patternizer init --with-secrets (second)...${NC}" -PATTERNIZER_RESOURCES_DIR="$REPO_ROOT" "$PATTERNIZER_BINARY" init --with-secrets +test_header "Running patternizer init --with-secrets (second)..." +"$PATTERNIZER_BINARY" init --with-secrets -echo -e "${YELLOW}Running verification tests for sequential execution...${NC}" +test_header "Running verification tests for sequential execution..." # Test 4.1: Check values-global.yaml (should have secretLoader.disabled=false after --with-secrets) compare_yaml "$EXPECTED_VALUES_GLOBAL_WITH_SECRETS" "values-global.yaml" "values-global.yaml content (sequential)" @@ -301,19 +329,124 @@ else exit 1 fi -# Test 4.4: Check values-secret.yaml.template exists -check_file_exists "values-secret.yaml.template" "values-secret.yaml.template file exists (sequential)" +# Test 4.4: Check values-secret.yaml.template has exact expected content +compare_files "$EXPECTED_VALUES_SECRET_TEMPLATE" "values-secret.yaml.template" "values-secret.yaml.template has expected content (sequential)" + +# Test 4.5: Check Makefile has exact expected content +compare_files "$EXPECTED_MAKEFILE" "Makefile" "Makefile has expected content (sequential execution)" + +# Test 4.6: Check Makefile-pattern has exact expected content +compare_files "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" "Makefile-pattern has expected content (sequential execution)" + +test_pass "=== Test 4: Sequential execution PASSED ===" + +# +# Test 5: File overwrite behavior with existing custom files +# +test_header "=== Test 5: File overwrite behavior with existing custom files ===" + +cd "$REPO_ROOT" # Go back to repo root + +test_header "Cloning test repository for overwrite behavior test..." +git clone "$TEST_REPO_URL" "$TEST_DIR_OVERWRITE" +cd "$TEST_DIR_OVERWRITE" + +test_header "Setting up existing custom files..." + +# Copy initial files to set up the test scenario +cp "$INITIAL_VALUES_GLOBAL_OVERWRITE" "values-global.yaml" +cp "$INITIAL_VALUES_CUSTOM_CLUSTER_OVERWRITE" "values-custom-cluster.yaml" +cp "$INITIAL_MAKEFILE_OVERWRITE" "Makefile" +cp "$INITIAL_MAKEFILE_PATTERN_OVERWRITE" "Makefile-pattern" +cp "$INITIAL_PATTERN_SH_OVERWRITE" "pattern.sh" +cp "$INITIAL_VALUES_SECRET_TEMPLATE_OVERWRITE" "values-secret.yaml.template" -# Test 4.5: Check Makefile exists (simple include-based Makefile) -check_file_exists "Makefile" "Makefile exists (sequential execution)" +# Make pattern.sh executable to match real scenarios +chmod +x "pattern.sh" + +test_header "Running patternizer init --with-secrets..." +"$PATTERNIZER_BINARY" init --with-secrets + +test_header "Verifying file overwrite behavior..." + +# Test 5.1: values-global.yaml should preserve custom fields and merge with defaults +compare_yaml "$EXPECTED_VALUES_GLOBAL_OVERWRITE" "values-global.yaml" "values-global.yaml content (preserves custom fields with --with-secrets)" + +# Test 5.2: values-custom-cluster.yaml should preserve custom fields and merge with defaults +compare_yaml "$EXPECTED_VALUES_CUSTOM_CLUSTER_OVERWRITE" "values-custom-cluster.yaml" "values-custom-cluster.yaml content (preserves custom fields)" + +# Test 5.3: Makefile should NOT be overwritten +compare_files "$INITIAL_MAKEFILE_OVERWRITE" "Makefile" "Makefile was not overwritten (content preserved)" + +# Test 5.4: Makefile-pattern SHOULD be overwritten with exact expected content +compare_files "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" "Makefile-pattern was overwritten with correct content" + +# Test 5.5: pattern.sh SHOULD be overwritten with exact expected content and be executable +compare_files "$EXPECTED_PATTERN_SH" "pattern.sh" "pattern.sh was overwritten with correct content" + +# Verify it's executable +if [ -x "pattern.sh" ]; then + test_pass "pattern.sh is executable" +else + test_fail "pattern.sh is not executable" +fi -# Test 4.6: Check Makefile-pattern exists (contains the actual targets) -check_file_exists "Makefile-pattern" "Makefile-pattern exists (sequential execution)" +# Test 5.6: values-secret.yaml.template should NOT be overwritten +compare_files "$INITIAL_VALUES_SECRET_TEMPLATE_OVERWRITE" "values-secret.yaml.template" "values-secret.yaml.template was not overwritten (content preserved)" + +test_pass "=== Test 5: File overwrite behavior PASSED ===" + +# +# Test 6: Mixed file overwrite behavior (some files exist, some don't) +# +test_header "=== Test 6: Mixed file overwrite behavior ===" + +cd "$REPO_ROOT" # Go back to repo root + +test_header "Cloning test repository for mixed scenario..." +git clone "$TEST_REPO_URL" "$TEST_DIR_MIXED" +cd "$TEST_DIR_MIXED" + +test_header "Setting up partial existing files..." + +# Only create some files to test mixed scenarios + +# Copy initial files for mixed scenario +cp "$INITIAL_MAKEFILE_OVERWRITE" "Makefile" +cp "$INITIAL_VALUES_SECRET_TEMPLATE_OVERWRITE" "values-secret.yaml.template" + +# Don't create values-global.yaml, values-prod.yaml (should be created) +# Don't create Makefile-pattern, pattern.sh (should be created/overwritten) + +test_header "Running patternizer init --with-secrets on mixed repository..." +"$PATTERNIZER_BINARY" init --with-secrets + +test_header "Verifying mixed overwrite behavior..." + +# Test 6.1: Files that should be created with exact expected content +check_file_exists "values-global.yaml" "values-global.yaml created when missing" +check_file_exists "values-prod.yaml" "values-prod.yaml created when missing" + +compare_files "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" "Makefile-pattern created with correct content" + +compare_files "$EXPECTED_PATTERN_SH" "pattern.sh" "pattern.sh created with correct content" + +# Test 6.2: Files that should be preserved +compare_files "$INITIAL_MAKEFILE_OVERWRITE" "Makefile" "Existing Makefile preserved in mixed scenario" + +compare_files "$INITIAL_VALUES_SECRET_TEMPLATE_OVERWRITE" "values-secret.yaml.template" "Existing values-secret.yaml.template preserved in mixed scenario" + +# Test 6.3: Verify pattern.sh is executable +if [ -x "pattern.sh" ]; then + test_pass "pattern.sh is executable in mixed scenario" +else + test_fail "pattern.sh is not executable in mixed scenario" +fi -echo -e "${GREEN}=== Test 4: Sequential execution PASSED ===${NC}" +test_pass "=== Test 6: Mixed file overwrite behavior PASSED ===" -echo -e "${GREEN}All integration tests passed!${NC}" +test_pass "All integration tests passed!" # Clean up cd "$REPO_ROOT" -rm -rf "$TEST_DIR" "$TEST_DIR_SECRETS" "$TEST_DIR_CUSTOM" "$TEST_DIR_SEQUENTIAL" +rm -rf "$TEST_DIR" "$TEST_DIR_SECRETS" "$TEST_DIR_CUSTOM" "$TEST_DIR_SEQUENTIAL" "$TEST_DIR_OVERWRITE" "$TEST_DIR_MIXED"