From 64525c17f9e6f1b1db7d93842fd2fd2d4a90c593 Mon Sep 17 00:00:00 2001 From: Drew Minnear Date: Wed, 23 Jul 2025 17:49:36 -0400 Subject: [PATCH 1/9] initial updates --- README.md | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c87a183..87da201 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,11 @@ > **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 CLI tool and container utility designed to bootstrap Validated Pattern repositories. It automatically generates the necessary files for a Validated Pattern from a Git repo containing Helm charts. -The tool provides both a standalone CLI and containerized execution for maximum flexibility and consistency across environments. +Patternizer is available as a standalone CLI, however, it is most conventiently used from a container [as described below](#container-usage). + +By default, Patternizer will not add the necessary files and values for loading secrets as part of your pattern. This is to make it simpler and clearer as you develop patterns which pieces are needed. You are able to run [`init --with-secrets`](#basic-initialization) at any point, even if you already ran the init without the secrets flag, to add the scaffolding for secrets into your pattern. So don't be afraid to start without secrets and then add them in as you want more control over DB credentials, ssh keys, etc.. When/if you do decide to add secrets please reference the [Secrets Management](https://validatedpatterns.io/learn/secrets-management-in-the-validated-patterns-framework/) section of our site. Finally, while you can always run init again with the --with-secrets flag to add secrets to your pattern, you cannot then run the init command without that flag to remove the secrets scaffolding that was added. If you're worried you may have such a case, then you should commit your changes to Git before running the init command with the --with-secrets flag. --- @@ -34,25 +36,34 @@ make dev-setup # See all available targets make help -# Build and test -make build -make test +# Quick feedback loop (format check, vet, build, unit tests) +make check ``` --- ## CLI Usage +[Container usage](#container-usage) is recommended but you are also able to run the CLI directly by following the steps below. + +```bash +# Build the binary +make build + +# Copy the binary somewhere onto your $PATH +cp src/patternizer /usr/local/bin +``` + ### Available Commands ```bash # Show help and available commands patternizer help -# Initialize pattern files (without secrets) +# Initialize pattern files (without secret loading) patternizer init -# Initialize pattern files with secrets support +# Initialize pattern files with secret loading patternizer init --with-secrets # Show help for specific commands @@ -89,10 +100,10 @@ Use the prebuilt container from Quay without needing to install anything locally cd /path/to/your/pattern-repo # Initialize without secrets -podman run --rm -it -v .:/repo:z quay.io/dminnear/patternizer init +podman run --pull=always --rm -it -v .:/repo:z quay.io/dminnear/patternizer init # Initialize with secrets support -podman run --rm -it -v .:/repo:z quay.io/dminnear/patternizer init --with-secrets +podman run --pull=always --rm -it -v .:/repo:z quay.io/dminnear/patternizer init --with-secrets ``` --- @@ -103,16 +114,19 @@ podman run --rm -it -v .:/repo:z quay.io/dminnear/patternizer init --with-secret ```bash git clone https://github.com/your-org/your-pattern.git cd your-pattern + git checkout -b run-patternizer ``` 2. **Initialize the pattern:** ```bash - podman run --rm -it -v .:/repo:z quay.io/dminnear/patternizer init + podman run --pull=always --rm -it -v .:/repo:z quay.io/dminnear/patternizer init ``` -3. **Review generated files:** +3. **Commit and push generated files:** ```bash - ls -la values-*.yaml pattern.sh Makefile* + git add . + git commit -m 'initialize pattern using patternizer' + git push -u origin run-patternizer ``` 4. **Install the pattern:** @@ -156,7 +170,7 @@ make test-unit # Run only integration tests make test-integration -# Build container image locally +# Build container image locally (as `patternizer:local`) make local-container-build # Run full CI pipeline locally @@ -318,9 +332,7 @@ This modular design makes the codebase maintainable, testable, and extensible. 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 + make ci # Lint/build/test your changes in one step ``` 5. Submit a pull request From 3c1fe1b1703887aeb058280bcdde976939b8d906 Mon Sep 17 00:00:00 2001 From: Drew Minnear Date: Wed, 23 Jul 2025 18:04:38 -0400 Subject: [PATCH 2/9] simplify readme and make more clear/brief --- README.md | 404 +++++++++++++++++------------------------------------- 1 file changed, 122 insertions(+), 282 deletions(-) diff --git a/README.md b/README.md index 87da201..b357571 100644 --- a/README.md +++ b/README.md @@ -3,351 +3,191 @@ [![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 files for a Validated Pattern from a Git repo containing Helm charts. +**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. -Patternizer is available as a standalone CLI, however, it is most conventiently used from a container [as described below](#container-usage). - -By default, Patternizer will not add the necessary files and values for loading secrets as part of your pattern. This is to make it simpler and clearer as you develop patterns which pieces are needed. You are able to run [`init --with-secrets`](#basic-initialization) at any point, even if you already ran the init without the secrets flag, to add the scaffolding for secrets into your pattern. So don't be afraid to start without secrets and then add them in as you want more control over DB credentials, ssh keys, etc.. When/if you do decide to add secrets please reference the [Secrets Management](https://validatedpatterns.io/learn/secrets-management-in-the-validated-patterns-framework/) section of our site. Finally, while you can always run init again with the --with-secrets flag to add secrets to your pattern, you cannot then run the init command without that flag to remove the secrets scaffolding that was added. If you're worried you may have such a case, then you should commit your changes to Git before running the init command with the --with-secrets flag. +> **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 -# Quick feedback loop (format check, vet, build, unit tests) -make check -``` - ---- - -## CLI Usage - -[Container usage](#container-usage) is recommended but you are also able to run the CLI directly by following the steps below. +Navigate to your repository's root directory and run the initialization command: ```bash -# Build the binary -make build - -# Copy the binary somewhere onto your $PATH -cp src/patternizer /usr/local/bin +# In the root of your pattern-repo +podman run --pull=always --rm -it -v .:/repo:z quay.io/dminnear/patternizer init ``` -### Available Commands +This single command will generate all the necessary files to turn your repository into a Validated Pattern. -```bash -# Show help and available commands -patternizer help +## Example Workflow -# Initialize pattern files (without secret loading) -patternizer init +1. **Clone or create your pattern repository:** -# Initialize pattern files with secret loading -patternizer init --with-secrets + ```bash + git clone https://github.com/your-org/your-pattern.git + cd your-pattern + git checkout -b initialize-pattern + ``` -# Show help for specific commands -patternizer init help -``` +2. **Initialize the pattern using Patternizer:** -### Output Files + ```bash + podman run --pull=always --rm -it -v .:/repo:z quay.io/dminnear/patternizer init + ``` -The `patternizer init` command generates: +3. **Review, commit, and push the generated files:** -- `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` + ```bash + git status + git add . + git commit -m 'initialize pattern using patternizer' + git push -u origin initialize-pattern + ``` -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 +4. **Install the pattern:** -The secrets loading behavior is controlled entirely by the `global.secretLoader.disabled` field in `values-global.yaml`. + ```bash + ./pattern.sh make install + ``` ---- +## Usage Details -## Container Usage +### Container Usage (Recommended) -Use the prebuilt container from Quay without needing to install anything locally: +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. -### Basic Initialization +#### **Initialize without secrets:** ```bash -# Navigate to your pattern repository -cd /path/to/your/pattern-repo - -# Initialize without secrets podman run --pull=always --rm -it -v .:/repo:z quay.io/dminnear/patternizer init +``` -# Initialize with secrets support +#### **Initialize with secrets support:** + +```bash podman run --pull=always --rm -it -v .:/repo:z quay.io/dminnear/patternizer init --with-secrets ``` ---- +### Understanding Secrets Management -## Example Workflow +You can start simple and add secrets management later. -1. **Clone or create a pattern repository:** - ```bash - git clone https://github.com/your-org/your-pattern.git - cd your-pattern - git checkout -b run-patternizer - ``` + * 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. -2. **Initialize the pattern:** - ```bash - podman run --pull=always --rm -it -v .:/repo:z quay.io/dminnear/patternizer init - ``` +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/). -3. **Commit and push generated files:** - ```bash - git add . - git commit -m 'initialize pattern using patternizer' - git push -u origin run-patternizer - ``` +### Generated Files -4. **Install the pattern:** - ```bash - ./pattern.sh make install - ``` +Running `patternizer init` creates the following: ---- + * `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. -## Development +Using the `--with-secrets` flag additionally creates: -### Prerequisites + * `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. -- Go 1.24+ -- Podman or Docker -- Git -- Make +## Development & Contributing -### Quick Start +This section is for developers who want to contribute to the Patternizer project itself. -```bash -# Set up development environment (installs dependencies and tools) -make dev-setup +### Prerequisites -# Show all available targets -make help -``` + * Go (see `go.mod` for version) + * Podman or Docker + * Git + * Make -### Common Development Tasks +### Local Development Workflow ```bash -# Build the CLI -make build - -# Run all tests (unit + integration) -make test - -# Run only unit tests -make test-unit +# 1. Clone the repository +git clone https://github.com/dminnear-rh/patternizer.git +cd patternizer -# Run only integration tests -make test-integration +# 2. Set up the development environment (installs tools) +make dev-setup -# Build container image locally (as `patternizer:local`) -make local-container-build +# 3. Make your changes... -# Run full CI pipeline locally +# 4. Run the full CI suite locally before committing make ci - -# Quick feedback loop (format check, vet, build, unit tests) -make check -``` - -### Code Quality - -The project uses comprehensive linting and formatting: - -```bash -# Run all linting checks (gofmt, go vet, golangci-lint) -make lint - -# Format code -make fmt - -# Run individual lint checks -make lint-fmt # gofmt check -make lint-vet # go vet -make lint-golangci # golangci-lint -``` - ---- - -## 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 - -# Or run all tests (unit + integration) -make test ``` ---- - -## CI/CD Pipeline - -The project uses a comprehensive CI pipeline with three stages that leverage the Makefile for consistency: - -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 - -All code must pass linting and tests before being merged or deployed. - -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`. - ---- +### Common Makefile Targets -## Architecture +The `Makefile` is the single source of truth for all development and CI tasks. -The CLI is organized into focused packages following Go best practices: + * `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. -**Main Package (`src/`):** -- `main.go` - Application entry point +### Testing Strategy -**Command Package (`src/cmd/`):** -- `root.go` - Cobra CLI setup and root command -- `init.go` - Initialization command logic and orchestration +Patternizer has a comprehensive test suite to ensure stability and correctness. -**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 + * **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. -**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 +### Architecture -This modular design makes the codebase maintainable, testable, and extensible. +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. ---- +### CI/CD Pipeline -## Contributing +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. -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 ci # Lint/build/test your changes in one step - ``` -5. Submit a pull request +### How to Contribute -All contributions must pass the CI pipeline including linting, formatting, and comprehensive testing. - -### Development Workflow - -For the best development experience: -```bash -# Initial setup -make dev-setup - -# During development (fast feedback) -make check - -# Before committing -make ci # Runs the full CI pipeline locally -``` +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. From 0836a45acea4c8f65b862e348c4de79cc6d08ece Mon Sep 17 00:00:00 2001 From: Drew Minnear Date: Wed, 23 Jul 2025 18:05:08 -0400 Subject: [PATCH 3/9] add apache 2 LICENSE --- LICENSE | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 LICENSE 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. From 7b03fa7ec8a2d5ade1cc242539a636f83d480d27 Mon Sep 17 00:00:00 2001 From: Drew Minnear Date: Wed, 23 Jul 2025 20:41:01 -0400 Subject: [PATCH 4/9] improve integration tests and move resources into own dir --- Containerfile | 4 +- resources/Makefile | 5 + .../Makefile-pattern | 0 pattern.sh => resources/pattern.sh | 0 .../values-secret.yaml.template | 0 src/cmd/init.go | 7 +- src/internal/fileutils/fileutils.go | 34 +- src/main_test.go | 4 +- ...ected_values_custom_cluster_overwrite.yaml | 42 +++ test/expected_values_global_overwrite.yaml | 14 + test/initial_makefile_overwrite | 6 + test/initial_makefile_pattern_overwrite | 6 + test/initial_pattern_sh_overwrite | 4 + ...itial_values_custom_cluster_overwrite.yaml | 12 + test/initial_values_global_overwrite.yaml | 14 + ...tial_values_secret_template_overwrite.yaml | 12 + test/integration_test.sh | 348 ++++++++++++++---- 17 files changed, 410 insertions(+), 102 deletions(-) create mode 100644 resources/Makefile rename Makefile-pattern => resources/Makefile-pattern (100%) rename pattern.sh => resources/pattern.sh (100%) rename values-secret.yaml.template => resources/values-secret.yaml.template (100%) create mode 100644 test/expected_values_custom_cluster_overwrite.yaml create mode 100644 test/expected_values_global_overwrite.yaml create mode 100644 test/initial_makefile_overwrite create mode 100644 test/initial_makefile_pattern_overwrite create mode 100644 test/initial_pattern_sh_overwrite create mode 100644 test/initial_values_custom_cluster_overwrite.yaml create mode 100644 test/initial_values_global_overwrite.yaml create mode 100644 test/initial_values_secret_template_overwrite.yaml 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/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..a1b99e2 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-pattern: %w", err) } } diff --git a/src/internal/fileutils/fileutils.go b/src/internal/fileutils/fileutils.go index ba51940..2786fab 100644 --- a/src/internal/fileutils/fileutils.go +++ b/src/internal/fileutils/fileutils.go @@ -47,44 +47,38 @@ 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) + // Check if the secrets template already exists + if _, err := os.Stat(secretsTemplateDst); err == nil { + // File already exists, don't overwrite it + return nil + } else if !os.IsNotExist(err) { + // Some other error occurred while checking if file exists + return fmt.Errorf("error checking if secrets template exists: %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) + // File doesn't exist, so copy it + if err = CopyFile(secretsTemplateSrc, secretsTemplateDst); err != nil { + return fmt.Errorf("error copying secrets template: %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() + path, err = filepath.Abs("resources") if err != nil { return "", fmt.Errorf("failed to get current directory: %w", err) } diff --git a/src/main_test.go b/src/main_test.go index 77b85ce..ecfcb18 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,7 +21,7 @@ func TestGetResourcePath(t *testing.T) { // Test with environment variable unset os.Unsetenv("PATTERNIZER_RESOURCES_DIR") - path, err = fileutils.GetResourcePath() + path, err = fileutils.GetResourcesPath() if err != nil { t.Fatalf("Expected no error, got %v", err) } 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..c5de7d8 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,29 +121,6 @@ except Exception as e: fi } -# Function to check file content -check_file_content() { - local file="$1" - local pattern="$2" - local description="$3" - - if [ ! -f "$file" ]; then - echo -e "${RED}FAIL: $description - file not found: $file${NC}" - return 1 - fi - - if grep -q "$pattern" "$file"; then - echo -e "${GREEN}PASS: $description${NC}" - return 0 - else - echo -e "${RED}FAIL: $description${NC}" - echo "Pattern '$pattern' not found in $file" - echo "File contents:" - cat "$file" - return 1 - fi -} - # Function to check file exists check_file_exists() { local file="$1" @@ -129,20 +135,6 @@ check_file_exists() { 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 - fi -} - # # Test 1: Basic initialization (without secrets) # @@ -153,7 +145,7 @@ 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 +"$PATTERNIZER_BINARY" init echo -e "${YELLOW}Running verification tests...${NC}" @@ -171,11 +163,21 @@ 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 +if diff "$EXPECTED_MAKEFILE" "Makefile" > /dev/null; then + echo -e "${GREEN}PASS: Makefile has expected content (init without secrets)${NC}" +else + echo -e "${RED}FAIL: Makefile content doesn't match expected (init without secrets)${NC}" + exit 1 +fi -# 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 +if diff "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" > /dev/null; then + echo -e "${GREEN}PASS: Makefile-pattern has expected content (init without secrets)${NC}" +else + echo -e "${RED}FAIL: Makefile-pattern content doesn't match expected (init without secrets)${NC}" + exit 1 +fi echo -e "${GREEN}=== Test 1: Basic initialization PASSED ===${NC}" @@ -190,7 +192,7 @@ 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 +"$PATTERNIZER_BINARY" init --with-secrets echo -e "${YELLOW}Running verification tests for secrets...${NC}" @@ -208,14 +210,29 @@ 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 +if diff "$EXPECTED_VALUES_SECRET_TEMPLATE" "values-secret.yaml.template" > /dev/null; then + echo -e "${GREEN}PASS: values-secret.yaml.template has expected content${NC}" +else + echo -e "${RED}FAIL: values-secret.yaml.template content doesn't match expected${NC}" + exit 1 +fi -# 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 +if diff "$EXPECTED_MAKEFILE" "Makefile" > /dev/null; then + echo -e "${GREEN}PASS: Makefile has expected content (init with secrets)${NC}" +else + echo -e "${RED}FAIL: Makefile content doesn't match expected (init with secrets)${NC}" + exit 1 +fi -# 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 +if diff "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" > /dev/null; then + echo -e "${GREEN}PASS: Makefile-pattern has expected content (init with secrets)${NC}" +else + echo -e "${RED}FAIL: Makefile-pattern content doesn't match expected (init with secrets)${NC}" + exit 1 +fi echo -e "${GREEN}=== Test 2: Initialization with secrets PASSED ===${NC}" @@ -233,7 +250,7 @@ echo -e "${YELLOW}Setting up initial values-global.yaml with custom names...${NC 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 +"$PATTERNIZER_BINARY" init --with-secrets echo -e "${YELLOW}Running verification tests for custom names...${NC}" @@ -251,14 +268,29 @@ 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 +if diff "$EXPECTED_VALUES_SECRET_TEMPLATE" "values-secret.yaml.template" > /dev/null; then + echo -e "${GREEN}PASS: values-secret.yaml.template has expected content (custom names)${NC}" +else + echo -e "${RED}FAIL: values-secret.yaml.template content doesn't match expected (custom names)${NC}" + exit 1 +fi -# 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 +if diff "$EXPECTED_MAKEFILE" "Makefile" > /dev/null; then + echo -e "${GREEN}PASS: Makefile has expected content (custom names with secrets)${NC}" +else + echo -e "${RED}FAIL: Makefile content doesn't match expected (custom names with secrets)${NC}" + exit 1 +fi -# 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 +if diff "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" > /dev/null; then + echo -e "${GREEN}PASS: Makefile-pattern has expected content (custom names with secrets)${NC}" +else + echo -e "${RED}FAIL: Makefile-pattern content doesn't match expected (custom names with secrets)${NC}" + exit 1 +fi echo -e "${GREEN}=== Test 3: Custom pattern and cluster group names (with secrets) PASSED ===${NC}" @@ -268,22 +300,16 @@ echo -e "${GREEN}=== Test 3: Custom pattern and cluster group names (with secret echo -e "${YELLOW}=== Test 4: Sequential execution (init + init --with-secrets) ===${NC}" 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}" 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 +"$PATTERNIZER_BINARY" init echo -e "${YELLOW}Running patternizer init --with-secrets (second)...${NC}" -PATTERNIZER_RESOURCES_DIR="$REPO_ROOT" "$PATTERNIZER_BINARY" init --with-secrets +"$PATTERNIZER_BINARY" init --with-secrets echo -e "${YELLOW}Running verification tests for sequential execution...${NC}" @@ -301,19 +327,193 @@ 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 +if diff "$EXPECTED_VALUES_SECRET_TEMPLATE" "values-secret.yaml.template" > /dev/null; then + echo -e "${GREEN}PASS: values-secret.yaml.template has expected content (sequential)${NC}" +else + echo -e "${RED}FAIL: values-secret.yaml.template content doesn't match expected (sequential)${NC}" + exit 1 +fi -# Test 4.5: Check Makefile exists (simple include-based Makefile) -check_file_exists "Makefile" "Makefile exists (sequential execution)" +# Test 4.5: Check Makefile has exact expected content +if diff "$EXPECTED_MAKEFILE" "Makefile" > /dev/null; then + echo -e "${GREEN}PASS: Makefile has expected content (sequential execution)${NC}" +else + echo -e "${RED}FAIL: Makefile content doesn't match expected (sequential execution)${NC}" + exit 1 +fi -# Test 4.6: Check Makefile-pattern exists (contains the actual targets) -check_file_exists "Makefile-pattern" "Makefile-pattern exists (sequential execution)" +# Test 4.6: Check Makefile-pattern has exact expected content +if diff "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" > /dev/null; then + echo -e "${GREEN}PASS: Makefile-pattern has expected content (sequential execution)${NC}" +else + echo -e "${RED}FAIL: Makefile-pattern content doesn't match expected (sequential execution)${NC}" + exit 1 +fi echo -e "${GREEN}=== Test 4: Sequential execution PASSED ===${NC}" +# +# Test 5: File overwrite behavior with existing custom files +# +echo -e "${YELLOW}=== Test 5: File overwrite behavior with existing custom files ===${NC}" + +cd "$REPO_ROOT" # Go back to repo root + +echo -e "${YELLOW}Cloning test repository for overwrite behavior test...${NC}" +git clone "$TEST_REPO_URL" "$TEST_DIR_OVERWRITE" +cd "$TEST_DIR_OVERWRITE" + +echo -e "${YELLOW}Setting up existing custom files...${NC}" + +# 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" + +# Make pattern.sh executable to match real scenarios +chmod +x "pattern.sh" + +echo -e "${YELLOW}Running patternizer init --with-secrets...${NC}" +"$PATTERNIZER_BINARY" init --with-secrets + +echo -e "${YELLOW}Verifying file overwrite behavior...${NC}" + +# 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 +if diff "$INITIAL_MAKEFILE_OVERWRITE" "Makefile" > /dev/null; then + echo -e "${GREEN}PASS: Makefile was not overwritten (content preserved)${NC}" +else + echo -e "${RED}FAIL: Makefile was overwritten but should have been preserved${NC}" + echo "Expected (initial):" + cat "$INITIAL_MAKEFILE_OVERWRITE" + echo "" + echo "Actual:" + cat "Makefile" + echo "" + exit 1 +fi + +# Test 5.4: Makefile-pattern SHOULD be overwritten with exact expected content +if diff "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" > /dev/null; then + echo -e "${GREEN}PASS: Makefile-pattern was overwritten with correct content${NC}" +else + echo -e "${RED}FAIL: Makefile-pattern doesn't have expected content after overwrite${NC}" + exit 1 +fi + +# Test 5.5: pattern.sh SHOULD be overwritten with exact expected content and be executable +if diff "$EXPECTED_PATTERN_SH" "pattern.sh" > /dev/null; then + echo -e "${GREEN}PASS: pattern.sh was overwritten with correct content${NC}" +else + echo -e "${RED}FAIL: pattern.sh doesn't have expected content after overwrite${NC}" + exit 1 +fi + +# Verify it's executable +if [ -x "pattern.sh" ]; then + echo -e "${GREEN}PASS: pattern.sh is executable${NC}" +else + echo -e "${RED}FAIL: pattern.sh is not executable${NC}" + exit 1 +fi + +# Test 5.6: values-secret.yaml.template should NOT be overwritten +if diff "$INITIAL_VALUES_SECRET_TEMPLATE_OVERWRITE" "values-secret.yaml.template" > /dev/null; then + echo -e "${GREEN}PASS: values-secret.yaml.template was not overwritten (content preserved)${NC}" +else + echo -e "${RED}FAIL: values-secret.yaml.template was overwritten but should have been preserved${NC}" + echo "Expected (initial):" + cat "$INITIAL_VALUES_SECRET_TEMPLATE_OVERWRITE" + echo "" + echo "Actual:" + cat "values-secret.yaml.template" + echo "" + exit 1 +fi + +echo -e "${GREEN}=== Test 5: File overwrite behavior PASSED ===${NC}" + +# +# Test 6: Mixed file overwrite behavior (some files exist, some don't) +# +echo -e "${YELLOW}=== Test 6: Mixed file overwrite behavior ===${NC}" + +cd "$REPO_ROOT" # Go back to repo root + +echo -e "${YELLOW}Cloning test repository for mixed scenario...${NC}" +git clone "$TEST_REPO_URL" "$TEST_DIR_MIXED" +cd "$TEST_DIR_MIXED" + +echo -e "${YELLOW}Setting up partial existing files...${NC}" + +# 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) + +echo -e "${YELLOW}Running patternizer init --with-secrets on mixed repository...${NC}" +"$PATTERNIZER_BINARY" init --with-secrets + +echo -e "${YELLOW}Verifying mixed overwrite behavior...${NC}" + +# 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" + +if diff "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" > /dev/null; then + echo -e "${GREEN}PASS: Makefile-pattern created with correct content${NC}" +else + echo -e "${RED}FAIL: Makefile-pattern doesn't have expected content${NC}" + exit 1 +fi + +if diff "$EXPECTED_PATTERN_SH" "pattern.sh" > /dev/null; then + echo -e "${GREEN}PASS: pattern.sh created with correct content${NC}" +else + echo -e "${RED}FAIL: pattern.sh doesn't have expected content${NC}" + exit 1 +fi + +# Test 6.2: Files that should be preserved +if diff "$INITIAL_MAKEFILE_OVERWRITE" "Makefile" > /dev/null; then + echo -e "${GREEN}PASS: Existing Makefile preserved in mixed scenario${NC}" +else + echo -e "${RED}FAIL: Existing Makefile was changed in mixed scenario${NC}" + exit 1 +fi + +if diff "$INITIAL_VALUES_SECRET_TEMPLATE_OVERWRITE" "values-secret.yaml.template" > /dev/null; then + echo -e "${GREEN}PASS: Existing values-secret.yaml.template preserved in mixed scenario${NC}" +else + echo -e "${RED}FAIL: Existing values-secret.yaml.template was changed in mixed scenario${NC}" + exit 1 +fi + +# Test 6.3: Verify pattern.sh is executable +if [ -x "pattern.sh" ]; then + echo -e "${GREEN}PASS: pattern.sh is executable in mixed scenario${NC}" +else + echo -e "${RED}FAIL: pattern.sh is not executable in mixed scenario${NC}" + exit 1 +fi + +echo -e "${GREEN}=== Test 6: Mixed file overwrite behavior PASSED ===${NC}" + echo -e "${GREEN}All integration tests passed!${NC}" # 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" From 905080efb0616ff72e52e10de7d14cf2690dfd60 Mon Sep 17 00:00:00 2001 From: Drew Minnear Date: Wed, 23 Jul 2025 20:57:22 -0400 Subject: [PATCH 5/9] DRY up integration test logic --- test/integration_test.sh | 273 +++++++++++++++------------------------ 1 file changed, 103 insertions(+), 170 deletions(-) diff --git a/test/integration_test.sh b/test/integration_test.sh index c5de7d8..d5f5602 100755 --- a/test/integration_test.sh +++ b/test/integration_test.sh @@ -121,33 +121,75 @@ except Exception as e: fi } +# 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 "$actual_file" ]; then + test_fail "$description - file not created: $actual_file" + fi + + 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 "Expected file: $expected_file" + echo "Actual file: $actual_file" + echo "Diff:" + diff "$expected_file" "$actual_file" || true + exit 1 + fi +} + # Function to check file exists check_file_exists() { local file="$1" 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 + 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}" +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" @@ -164,37 +206,27 @@ else fi # Test 1.4: Check Makefile has exact expected content -if diff "$EXPECTED_MAKEFILE" "Makefile" > /dev/null; then - echo -e "${GREEN}PASS: Makefile has expected content (init without secrets)${NC}" -else - echo -e "${RED}FAIL: Makefile content doesn't match expected (init without secrets)${NC}" - exit 1 -fi +compare_files "$EXPECTED_MAKEFILE" "Makefile" "Makefile has expected content (init without secrets)" # Test 1.5: Check Makefile-pattern has exact expected content -if diff "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" > /dev/null; then - echo -e "${GREEN}PASS: Makefile-pattern has expected content (init without secrets)${NC}" -else - echo -e "${RED}FAIL: Makefile-pattern content doesn't match expected (init without secrets)${NC}" - exit 1 -fi +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}" +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)" @@ -211,48 +243,33 @@ else fi # Test 2.4: Check values-secret.yaml.template has exact expected content -if diff "$EXPECTED_VALUES_SECRET_TEMPLATE" "values-secret.yaml.template" > /dev/null; then - echo -e "${GREEN}PASS: values-secret.yaml.template has expected content${NC}" -else - echo -e "${RED}FAIL: values-secret.yaml.template content doesn't match expected${NC}" - exit 1 -fi +compare_files "$EXPECTED_VALUES_SECRET_TEMPLATE" "values-secret.yaml.template" "values-secret.yaml.template has expected content" # Test 2.5: Check Makefile has exact expected content -if diff "$EXPECTED_MAKEFILE" "Makefile" > /dev/null; then - echo -e "${GREEN}PASS: Makefile has expected content (init with secrets)${NC}" -else - echo -e "${RED}FAIL: Makefile content doesn't match expected (init with secrets)${NC}" - exit 1 -fi +compare_files "$EXPECTED_MAKEFILE" "Makefile" "Makefile has expected content (init with secrets)" # Test 2.6: Check Makefile-pattern has exact expected content -if diff "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" > /dev/null; then - echo -e "${GREEN}PASS: Makefile-pattern has expected content (init with secrets)${NC}" -else - echo -e "${RED}FAIL: Makefile-pattern content doesn't match expected (init with secrets)${NC}" - exit 1 -fi +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}" +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)" @@ -269,49 +286,34 @@ else fi # Test 3.4: Check values-secret.yaml.template has exact expected content -if diff "$EXPECTED_VALUES_SECRET_TEMPLATE" "values-secret.yaml.template" > /dev/null; then - echo -e "${GREEN}PASS: values-secret.yaml.template has expected content (custom names)${NC}" -else - echo -e "${RED}FAIL: values-secret.yaml.template content doesn't match expected (custom names)${NC}" - exit 1 -fi +compare_files "$EXPECTED_VALUES_SECRET_TEMPLATE" "values-secret.yaml.template" "values-secret.yaml.template has expected content (custom names)" # Test 3.5: Check Makefile has exact expected content -if diff "$EXPECTED_MAKEFILE" "Makefile" > /dev/null; then - echo -e "${GREEN}PASS: Makefile has expected content (custom names with secrets)${NC}" -else - echo -e "${RED}FAIL: Makefile content doesn't match expected (custom names with secrets)${NC}" - exit 1 -fi +compare_files "$EXPECTED_MAKEFILE" "Makefile" "Makefile has expected content (custom names with secrets)" # Test 3.6: Check Makefile-pattern has exact expected content -if diff "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" > /dev/null; then - echo -e "${GREEN}PASS: Makefile-pattern has expected content (custom names with secrets)${NC}" -else - echo -e "${RED}FAIL: Makefile-pattern content doesn't match expected (custom names with secrets)${NC}" - exit 1 -fi +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 -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}" +test_header "Running patternizer init (first)..." "$PATTERNIZER_BINARY" init -echo -e "${YELLOW}Running patternizer init --with-secrets (second)...${NC}" +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)" @@ -328,43 +330,28 @@ else fi # Test 4.4: Check values-secret.yaml.template has exact expected content -if diff "$EXPECTED_VALUES_SECRET_TEMPLATE" "values-secret.yaml.template" > /dev/null; then - echo -e "${GREEN}PASS: values-secret.yaml.template has expected content (sequential)${NC}" -else - echo -e "${RED}FAIL: values-secret.yaml.template content doesn't match expected (sequential)${NC}" - exit 1 -fi +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 -if diff "$EXPECTED_MAKEFILE" "Makefile" > /dev/null; then - echo -e "${GREEN}PASS: Makefile has expected content (sequential execution)${NC}" -else - echo -e "${RED}FAIL: Makefile content doesn't match expected (sequential execution)${NC}" - exit 1 -fi +compare_files "$EXPECTED_MAKEFILE" "Makefile" "Makefile has expected content (sequential execution)" # Test 4.6: Check Makefile-pattern has exact expected content -if diff "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" > /dev/null; then - echo -e "${GREEN}PASS: Makefile-pattern has expected content (sequential execution)${NC}" -else - echo -e "${RED}FAIL: Makefile-pattern content doesn't match expected (sequential execution)${NC}" - exit 1 -fi +compare_files "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" "Makefile-pattern has expected content (sequential execution)" -echo -e "${GREEN}=== Test 4: Sequential execution PASSED ===${NC}" +test_pass "=== Test 4: Sequential execution PASSED ===" # # Test 5: File overwrite behavior with existing custom files # -echo -e "${YELLOW}=== Test 5: File overwrite behavior with existing custom files ===${NC}" +test_header "=== Test 5: File overwrite behavior with existing custom files ===" cd "$REPO_ROOT" # Go back to repo root -echo -e "${YELLOW}Cloning test repository for overwrite behavior test...${NC}" +test_header "Cloning test repository for overwrite behavior test..." git clone "$TEST_REPO_URL" "$TEST_DIR_OVERWRITE" cd "$TEST_DIR_OVERWRITE" -echo -e "${YELLOW}Setting up existing custom files...${NC}" +test_header "Setting up existing custom files..." # Copy initial files to set up the test scenario cp "$INITIAL_VALUES_GLOBAL_OVERWRITE" "values-global.yaml" @@ -377,10 +364,10 @@ cp "$INITIAL_VALUES_SECRET_TEMPLATE_OVERWRITE" "values-secret.yaml.template" # Make pattern.sh executable to match real scenarios chmod +x "pattern.sh" -echo -e "${YELLOW}Running patternizer init --with-secrets...${NC}" +test_header "Running patternizer init --with-secrets..." "$PATTERNIZER_BINARY" init --with-secrets -echo -e "${YELLOW}Verifying file overwrite behavior...${NC}" +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)" @@ -389,71 +376,38 @@ compare_yaml "$EXPECTED_VALUES_GLOBAL_OVERWRITE" "values-global.yaml" "values-gl 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 -if diff "$INITIAL_MAKEFILE_OVERWRITE" "Makefile" > /dev/null; then - echo -e "${GREEN}PASS: Makefile was not overwritten (content preserved)${NC}" -else - echo -e "${RED}FAIL: Makefile was overwritten but should have been preserved${NC}" - echo "Expected (initial):" - cat "$INITIAL_MAKEFILE_OVERWRITE" - echo "" - echo "Actual:" - cat "Makefile" - echo "" - exit 1 -fi +compare_files "$INITIAL_MAKEFILE_OVERWRITE" "Makefile" "Makefile was not overwritten (content preserved)" # Test 5.4: Makefile-pattern SHOULD be overwritten with exact expected content -if diff "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" > /dev/null; then - echo -e "${GREEN}PASS: Makefile-pattern was overwritten with correct content${NC}" -else - echo -e "${RED}FAIL: Makefile-pattern doesn't have expected content after overwrite${NC}" - exit 1 -fi +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 -if diff "$EXPECTED_PATTERN_SH" "pattern.sh" > /dev/null; then - echo -e "${GREEN}PASS: pattern.sh was overwritten with correct content${NC}" -else - echo -e "${RED}FAIL: pattern.sh doesn't have expected content after overwrite${NC}" - exit 1 -fi +compare_files "$EXPECTED_PATTERN_SH" "pattern.sh" "pattern.sh was overwritten with correct content" # Verify it's executable if [ -x "pattern.sh" ]; then - echo -e "${GREEN}PASS: pattern.sh is executable${NC}" + test_pass "pattern.sh is executable" else - echo -e "${RED}FAIL: pattern.sh is not executable${NC}" - exit 1 + test_fail "pattern.sh is not executable" fi # Test 5.6: values-secret.yaml.template should NOT be overwritten -if diff "$INITIAL_VALUES_SECRET_TEMPLATE_OVERWRITE" "values-secret.yaml.template" > /dev/null; then - echo -e "${GREEN}PASS: values-secret.yaml.template was not overwritten (content preserved)${NC}" -else - echo -e "${RED}FAIL: values-secret.yaml.template was overwritten but should have been preserved${NC}" - echo "Expected (initial):" - cat "$INITIAL_VALUES_SECRET_TEMPLATE_OVERWRITE" - echo "" - echo "Actual:" - cat "values-secret.yaml.template" - echo "" - exit 1 -fi +compare_files "$INITIAL_VALUES_SECRET_TEMPLATE_OVERWRITE" "values-secret.yaml.template" "values-secret.yaml.template was not overwritten (content preserved)" -echo -e "${GREEN}=== Test 5: File overwrite behavior PASSED ===${NC}" +test_pass "=== Test 5: File overwrite behavior PASSED ===" # # Test 6: Mixed file overwrite behavior (some files exist, some don't) # -echo -e "${YELLOW}=== Test 6: Mixed file overwrite behavior ===${NC}" +test_header "=== Test 6: Mixed file overwrite behavior ===" cd "$REPO_ROOT" # Go back to repo root -echo -e "${YELLOW}Cloning test repository for mixed scenario...${NC}" +test_header "Cloning test repository for mixed scenario..." git clone "$TEST_REPO_URL" "$TEST_DIR_MIXED" cd "$TEST_DIR_MIXED" -echo -e "${YELLOW}Setting up partial existing files...${NC}" +test_header "Setting up partial existing files..." # Only create some files to test mixed scenarios @@ -464,55 +418,34 @@ 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) -echo -e "${YELLOW}Running patternizer init --with-secrets on mixed repository...${NC}" +test_header "Running patternizer init --with-secrets on mixed repository..." "$PATTERNIZER_BINARY" init --with-secrets -echo -e "${YELLOW}Verifying mixed overwrite behavior...${NC}" +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" -if diff "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" > /dev/null; then - echo -e "${GREEN}PASS: Makefile-pattern created with correct content${NC}" -else - echo -e "${RED}FAIL: Makefile-pattern doesn't have expected content${NC}" - exit 1 -fi +compare_files "$EXPECTED_MAKEFILE_PATTERN" "Makefile-pattern" "Makefile-pattern created with correct content" -if diff "$EXPECTED_PATTERN_SH" "pattern.sh" > /dev/null; then - echo -e "${GREEN}PASS: pattern.sh created with correct content${NC}" -else - echo -e "${RED}FAIL: pattern.sh doesn't have expected content${NC}" - exit 1 -fi +compare_files "$EXPECTED_PATTERN_SH" "pattern.sh" "pattern.sh created with correct content" # Test 6.2: Files that should be preserved -if diff "$INITIAL_MAKEFILE_OVERWRITE" "Makefile" > /dev/null; then - echo -e "${GREEN}PASS: Existing Makefile preserved in mixed scenario${NC}" -else - echo -e "${RED}FAIL: Existing Makefile was changed in mixed scenario${NC}" - exit 1 -fi +compare_files "$INITIAL_MAKEFILE_OVERWRITE" "Makefile" "Existing Makefile preserved in mixed scenario" -if diff "$INITIAL_VALUES_SECRET_TEMPLATE_OVERWRITE" "values-secret.yaml.template" > /dev/null; then - echo -e "${GREEN}PASS: Existing values-secret.yaml.template preserved in mixed scenario${NC}" -else - echo -e "${RED}FAIL: Existing values-secret.yaml.template was changed in mixed scenario${NC}" - exit 1 -fi +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 - echo -e "${GREEN}PASS: pattern.sh is executable in mixed scenario${NC}" + test_pass "pattern.sh is executable in mixed scenario" else - echo -e "${RED}FAIL: pattern.sh is not executable in mixed scenario${NC}" - exit 1 + test_fail "pattern.sh is not executable in mixed scenario" fi -echo -e "${GREEN}=== Test 6: Mixed file overwrite behavior 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" From b67e7b5a11411a8e335cfff3773af2ffd3e59b80 Mon Sep 17 00:00:00 2001 From: Drew Minnear Date: Wed, 23 Jul 2025 21:11:54 -0400 Subject: [PATCH 6/9] add shellcheck for integration test script and update README with information about last 2 integration tests --- Makefile | 9 ++++++++- README.md | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) 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 b357571..d1f9106 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,8 @@ Patternizer has a comprehensive test suite to ensure stability and correctness. 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. ### Architecture From 90484ec3aa024e6852541324f1a4378372d4578b Mon Sep 17 00:00:00 2001 From: Drew Minnear Date: Wed, 23 Jul 2025 21:16:30 -0400 Subject: [PATCH 7/9] fix typo --- src/cmd/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmd/init.go b/src/cmd/init.go index a1b99e2..f010baf 100644 --- a/src/cmd/init.go +++ b/src/cmd/init.go @@ -59,7 +59,7 @@ func runInit(withSecrets bool) error { makefileDst := filepath.Join(repoRoot, "Makefile") if _, err := os.Stat(makefileDst); os.IsNotExist(err) { if err := fileutils.CopyFile(makefileSrc, makefileDst); err != nil { - return fmt.Errorf("error copying Makefile-pattern: %w", err) + return fmt.Errorf("error copying Makefile: %w", err) } } From e91ebf93a97cdae894fb8fe1427dad1194304921 Mon Sep 17 00:00:00 2001 From: Drew Minnear Date: Wed, 23 Jul 2025 21:23:25 -0400 Subject: [PATCH 8/9] make secret template copy logic consistent with other resource file copies --- src/internal/fileutils/fileutils.go | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/internal/fileutils/fileutils.go b/src/internal/fileutils/fileutils.go index 2786fab..6dd5ee6 100644 --- a/src/internal/fileutils/fileutils.go +++ b/src/internal/fileutils/fileutils.go @@ -51,18 +51,10 @@ func HandleSecretsSetup(resourcesDir, repoRoot string) (err error) { secretsTemplateSrc := filepath.Join(resourcesDir, "values-secret.yaml.template") secretsTemplateDst := filepath.Join(repoRoot, "values-secret.yaml.template") - // Check if the secrets template already exists - if _, err := os.Stat(secretsTemplateDst); err == nil { - // File already exists, don't overwrite it - return nil - } else if !os.IsNotExist(err) { - // Some other error occurred while checking if file exists - return fmt.Errorf("error checking if secrets template exists: %w", err) - } - - // File doesn't exist, so copy it - 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 From 81e918adcb48730001762314a74537c51ed86314 Mon Sep 17 00:00:00 2001 From: Drew Minnear Date: Wed, 23 Jul 2025 21:30:11 -0400 Subject: [PATCH 9/9] make PATTERNIZER_RESOURCES_DIR a required env var --- src/internal/fileutils/fileutils.go | 9 ++------- src/main_test.go | 9 ++++----- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/internal/fileutils/fileutils.go b/src/internal/fileutils/fileutils.go index 6dd5ee6..adc8246 100644 --- a/src/internal/fileutils/fileutils.go +++ b/src/internal/fileutils/fileutils.go @@ -69,11 +69,6 @@ func GetResourcesPath() (path string, err error) { return path, nil } - // Fall back to current directory - path, err = filepath.Abs("resources") - 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 ecfcb18..b48bda1 100644 --- a/src/main_test.go +++ b/src/main_test.go @@ -22,12 +22,11 @@ func TestGetResourcePath(t *testing.T) { // Test with environment variable unset os.Unsetenv("PATTERNIZER_RESOURCES_DIR") path, err = fileutils.GetResourcesPath() - if err != nil { - t.Fatalf("Expected no error, got %v", err) + 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) } }