Skip to content

Commit c4976fe

Browse files
committed
add update command to patternizer
1 parent df4de40 commit c4976fe

4 files changed

Lines changed: 281 additions & 3 deletions

File tree

README.md

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,15 @@ patternizer init
5353
# Initialize pattern files with secrets support
5454
patternizer init --with-secrets
5555

56-
# Show help for the init command
56+
# Update existing pattern to use patternizer workflow (with secrets by default)
57+
patternizer update
58+
59+
# Update existing pattern without secrets
60+
patternizer update --no-secrets
61+
62+
# Show help for specific commands
5763
patternizer init help
64+
patternizer update help
5865
```
5966

6067
### Output Files
@@ -69,6 +76,13 @@ When using `--with-secrets`:
6976
- `values-secret.yaml.template` - Template for secrets configuration
7077
- Modified `pattern.sh` with `USE_SECRETS=true` as default
7178

79+
The `patternizer update` command modifies existing patterns:
80+
81+
- **Removes** `common/` directory (now handled by utility container)
82+
- **Removes** existing `Makefile` (utility container provides its own)
83+
- **Replaces** `pattern.sh` symlink/file with patternizer version
84+
- **Sets** `USE_SECRETS=true` by default (use `--no-secrets` for `USE_SECRETS=false`)
85+
7286
---
7387

7488
## Container Usage
@@ -115,6 +129,75 @@ podman run --rm -it -v .:/repo:z quay.io/dminnear/patternizer init --with-secret
115129

116130
---
117131

132+
## Updating Existing Patterns
133+
134+
If you have an existing Validated Pattern that uses the traditional structure (with `common/` directory and symlinked `pattern.sh`), you can modernize it to use the patternizer workflow:
135+
136+
### Update Workflow
137+
138+
1. **Navigate to your existing pattern repository:**
139+
```bash
140+
cd /path/to/your/existing-pattern
141+
```
142+
143+
2. **Update the pattern (with secrets by default):**
144+
```bash
145+
podman run --rm -it -v .:/repo:z quay.io/dminnear/patternizer update
146+
```
147+
148+
3. **Or update without secrets:**
149+
```bash
150+
podman run --rm -it -v .:/repo:z quay.io/dminnear/patternizer update --no-secrets
151+
```
152+
153+
4. **Verify the changes:**
154+
```bash
155+
# Check that old files are removed
156+
ls -la common/ # Should not exist
157+
ls -la Makefile # Should not exist
158+
159+
# Check that pattern.sh is now a real file
160+
ls -la pattern.sh # Should be a regular file, not a symlink
161+
162+
# Verify USE_SECRETS setting
163+
grep "USE_SECRETS" pattern.sh
164+
```
165+
166+
### What Gets Updated
167+
168+
The `update` command modernizes your pattern by:
169+
-**Removing** the `common/` directory (functionality moved to utility container)
170+
-**Removing** the top-level `Makefile` (utility container provides its own)
171+
-**Replacing** the symlinked `pattern.sh` with the patternizer version
172+
-**Configuring** secrets support (`USE_SECRETS=true` by default)
173+
-**Preserving** all your existing values files and custom configurations
174+
175+
### Example: Updating multicloud-gitops
176+
177+
```bash
178+
# Clone an existing pattern
179+
git clone https://github.com/validatedpatterns/multicloud-gitops.git
180+
cd multicloud-gitops
181+
182+
# Before: traditional structure
183+
ls -la common/ # Directory exists
184+
ls -la Makefile # File exists
185+
ls -la pattern.sh # Symlink to ./common/scripts/pattern-util.sh
186+
187+
# Update to patternizer workflow
188+
podman run --rm -it -v .:/repo:z quay.io/dminnear/patternizer update
189+
190+
# After: modernized structure
191+
ls -la common/ # Directory removed
192+
ls -la Makefile # File removed
193+
ls -la pattern.sh # Now a regular file with USE_SECRETS=true
194+
195+
# Use the updated pattern
196+
./pattern.sh make install
197+
```
198+
199+
---
200+
118201
## Development
119202

120203
### Prerequisites
@@ -216,7 +299,7 @@ The project includes comprehensive unit tests across multiple packages:
216299

217300
### Integration Tests
218301

219-
The integration test suite (`test/integration_test.sh`) validates the complete CLI workflow with four comprehensive test scenarios:
302+
The integration test suite (`test/integration_test.sh`) validates the complete CLI workflow with five comprehensive test scenarios:
220303

221304
**Test 1: Basic Initialization (Without Secrets)**
222305
- Clones the [trivial-pattern](https://github.com/dminnear-rh/trivial-pattern) repository
@@ -241,6 +324,13 @@ The integration test suite (`test/integration_test.sh`) validates the complete C
241324
- Validates that the second command properly upgrades the configuration
242325
- Ensures final state matches direct `--with-secrets` execution
243326

327+
**Test 5: Update Existing Pattern**
328+
- Clones the [multicloud-gitops](https://github.com/validatedpatterns/multicloud-gitops) repository (real-world existing pattern)
329+
- Verifies initial traditional pattern structure (`common/` directory, `Makefile`, symlinked `pattern.sh`)
330+
- Runs `patternizer update` to modernize the pattern
331+
- Validates complete cleanup: `common/` and `Makefile` removal, `pattern.sh` symlink replacement
332+
- Ensures new `pattern.sh` has `USE_SECRETS=true` and executable permissions
333+
244334
Run integration tests locally:
245335
```bash
246336
# Run integration tests (automatically builds binary first)

src/cmd/root.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
// This is called by main.main(). It only needs to happen once to the rootCmd.
1111
func Execute() {
1212
var withSecrets bool
13+
var noSecrets bool
1314

1415
var rootCmd = &cobra.Command{
1516
Use: "patternizer",
@@ -36,9 +37,27 @@ configures the pattern.sh script for secrets usage.`,
3637
},
3738
}
3839

40+
var updateCmd = &cobra.Command{
41+
Use: "update",
42+
Short: "Update existing Validated Pattern to use patternizer workflow",
43+
Long: `Update existing Validated Pattern removes the common/ directory and replaces the pattern.sh script
44+
with the patternizer version. By default, it configures the pattern to use secrets (USE_SECRETS=true).
45+
46+
Use --no-secrets flag to disable secrets usage (USE_SECRETS=false).`,
47+
RunE: func(cmd *cobra.Command, args []string) error {
48+
// Check if "help" is passed as an argument
49+
if len(args) > 0 && args[0] == "help" {
50+
return cmd.Help()
51+
}
52+
return runUpdate(noSecrets)
53+
},
54+
}
55+
3956
initCmd.Flags().BoolVar(&withSecrets, "with-secrets", false, "Include secrets template and configure pattern for secrets usage")
57+
updateCmd.Flags().BoolVar(&noSecrets, "no-secrets", false, "Disable secrets usage (USE_SECRETS=false)")
4058

4159
rootCmd.AddCommand(initCmd)
60+
rootCmd.AddCommand(updateCmd)
4261

4362
if err := rootCmd.Execute(); err != nil {
4463
os.Exit(1)

src/cmd/update.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
8+
"github.com/dminnear-rh/patternizer/internal/fileutils"
9+
"github.com/dminnear-rh/patternizer/internal/pattern"
10+
)
11+
12+
// runUpdate handles the update logic for the update command.
13+
func runUpdate(noSecrets bool) error {
14+
// Get pattern name and repository root
15+
patternName, repoRoot, err := pattern.GetPatternNameAndRepoRoot()
16+
if err != nil {
17+
return fmt.Errorf("error getting pattern information: %w", err)
18+
}
19+
20+
// Remove the existing pattern.sh file if it exists (it might be a symlink to common/)
21+
patternShDst := filepath.Join(repoRoot, "pattern.sh")
22+
if _, err := os.Stat(patternShDst); err == nil {
23+
fmt.Printf("Removing existing pattern.sh: %s\n", patternShDst)
24+
if err := os.Remove(patternShDst); err != nil {
25+
return fmt.Errorf("error removing existing pattern.sh: %w", err)
26+
}
27+
} else if !os.IsNotExist(err) {
28+
return fmt.Errorf("error checking existing pattern.sh: %w", err)
29+
}
30+
31+
// Remove the existing Makefile if it exists (utility container provides its own)
32+
makefilePath := filepath.Join(repoRoot, "Makefile")
33+
if _, err := os.Stat(makefilePath); err == nil {
34+
fmt.Printf("Removing existing Makefile: %s\n", makefilePath)
35+
if err := os.Remove(makefilePath); err != nil {
36+
return fmt.Errorf("error removing existing Makefile: %w", err)
37+
}
38+
} else if !os.IsNotExist(err) {
39+
return fmt.Errorf("error checking existing Makefile: %w", err)
40+
}
41+
42+
// Delete the common/ directory if it exists
43+
commonDir := filepath.Join(repoRoot, "common")
44+
if _, err := os.Stat(commonDir); err == nil {
45+
fmt.Printf("Removing common/ directory: %s\n", commonDir)
46+
if err := os.RemoveAll(commonDir); err != nil {
47+
return fmt.Errorf("error removing common/ directory: %w", err)
48+
}
49+
} else if !os.IsNotExist(err) {
50+
return fmt.Errorf("error checking common/ directory: %w", err)
51+
}
52+
53+
// Copy pattern.sh from resources
54+
resourcesDir, err := fileutils.GetResourcePath()
55+
if err != nil {
56+
return fmt.Errorf("error getting resource path: %w", err)
57+
}
58+
59+
patternShSrc := filepath.Join(resourcesDir, "pattern.sh")
60+
if err := fileutils.CopyFile(patternShSrc, patternShDst); err != nil {
61+
return fmt.Errorf("error copying pattern.sh: %w", err)
62+
}
63+
64+
// Set USE_SECRETS in pattern.sh based on the flag
65+
// By default, update uses secrets (opposite of init)
66+
useSecrets := !noSecrets
67+
if err := fileutils.ModifyPatternShScript(patternShDst, useSecrets); err != nil {
68+
return fmt.Errorf("error modifying pattern.sh: %w", err)
69+
}
70+
71+
fmt.Printf("Successfully updated pattern '%s' in %s\n", patternName, repoRoot)
72+
if useSecrets {
73+
fmt.Println("Secrets configuration is enabled (USE_SECRETS=true).")
74+
} else {
75+
fmt.Println("Secrets configuration is disabled (USE_SECRETS=false).")
76+
}
77+
78+
return nil
79+
}

test/integration_test.sh

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ NC='\033[0m' # No Color
1111
# Test configuration
1212
PATTERNIZER_BINARY="${PATTERNIZER_BINARY:-./src/patternizer}"
1313
TEST_REPO_URL="https://github.com/dminnear-rh/trivial-pattern.git"
14+
UPDATE_TEST_REPO_URL="https://github.com/validatedpatterns/multicloud-gitops.git"
1415
TEST_DIR="/tmp/patternizer-integration-test"
1516
TEST_DIR_SECRETS="/tmp/patternizer-integration-test-secrets"
1617
TEST_DIR_CUSTOM="/tmp/patternizer-integration-test-custom"
18+
TEST_DIR_UPDATE="/tmp/patternizer-integration-test-update"
1719

1820
echo -e "${YELLOW}Starting patternizer integration tests...${NC}"
1921

@@ -27,6 +29,9 @@ fi
2729
if [ -d "$TEST_DIR_CUSTOM" ]; then
2830
rm -rf "$TEST_DIR_CUSTOM"
2931
fi
32+
if [ -d "$TEST_DIR_UPDATE" ]; then
33+
rm -rf "$TEST_DIR_UPDATE"
34+
fi
3035

3136
# Convert PATTERNIZER_BINARY to absolute path before changing directories
3237
PATTERNIZER_BINARY=$(realpath "$PATTERNIZER_BINARY")
@@ -128,6 +133,20 @@ check_file_exists() {
128133
fi
129134
}
130135

136+
# Function to check file/directory doesn't exist
137+
check_not_exists() {
138+
local path="$1"
139+
local description="$2"
140+
141+
if [ ! -e "$path" ]; then
142+
echo -e "${GREEN}PASS: $description${NC}"
143+
return 0
144+
else
145+
echo -e "${RED}FAIL: $description - path still exists: $path${NC}"
146+
return 1
147+
fi
148+
}
149+
131150
#
132151
# Test 1: Basic initialization (without secrets)
133152
#
@@ -285,8 +304,79 @@ check_file_exists "values-secret.yaml.template" "values-secret.yaml.template fil
285304

286305
echo -e "${GREEN}=== Test 4: Sequential execution PASSED ===${NC}"
287306

307+
#
308+
# Test 5: Update existing pattern (multicloud-gitops)
309+
#
310+
echo -e "${YELLOW}=== Test 5: Update existing pattern (multicloud-gitops) ===${NC}"
311+
312+
cd "$REPO_ROOT" # Go back to repo root
313+
echo -e "${YELLOW}Cloning multicloud-gitops repository for update test...${NC}"
314+
git clone "$UPDATE_TEST_REPO_URL" "$TEST_DIR_UPDATE"
315+
cd "$TEST_DIR_UPDATE"
316+
317+
echo -e "${YELLOW}Verifying initial state (before update)...${NC}"
318+
# Verify the typical old pattern structure exists
319+
if [ -d "common" ]; then
320+
echo -e "${GREEN}INFO: common/ directory exists (as expected)${NC}"
321+
else
322+
echo -e "${YELLOW}WARNING: common/ directory not found - pattern may already be updated${NC}"
323+
fi
324+
325+
if [ -f "Makefile" ]; then
326+
echo -e "${GREEN}INFO: Makefile exists (as expected)${NC}"
327+
else
328+
echo -e "${YELLOW}WARNING: Makefile not found - pattern may already be updated${NC}"
329+
fi
330+
331+
if [ -L "pattern.sh" ]; then
332+
echo -e "${GREEN}INFO: pattern.sh is a symlink (as expected)${NC}"
333+
echo -e "${GREEN}INFO: pattern.sh points to: $(readlink pattern.sh)${NC}"
334+
elif [ -f "pattern.sh" ]; then
335+
echo -e "${YELLOW}WARNING: pattern.sh exists but is not a symlink${NC}"
336+
else
337+
echo -e "${RED}ERROR: pattern.sh not found at all${NC}"
338+
exit 1
339+
fi
340+
341+
echo -e "${YELLOW}Running patternizer update...${NC}"
342+
PATTERNIZER_RESOURCES_DIR="$REPO_ROOT" "$PATTERNIZER_BINARY" update
343+
344+
echo -e "${YELLOW}Running verification tests for update...${NC}"
345+
346+
# Test 5.1: Verify common/ directory was removed
347+
check_not_exists "common" "common/ directory was removed"
348+
349+
# Test 5.2: Verify Makefile was removed
350+
check_not_exists "Makefile" "Makefile was removed"
351+
352+
# Test 5.3: Verify pattern.sh exists and is a real file (not symlink)
353+
if [ -f "pattern.sh" ] && [ ! -L "pattern.sh" ]; then
354+
echo -e "${GREEN}PASS: pattern.sh exists and is a regular file (not symlink)${NC}"
355+
else
356+
echo -e "${RED}FAIL: pattern.sh is not a regular file${NC}"
357+
if [ -L "pattern.sh" ]; then
358+
echo "pattern.sh is still a symlink pointing to: $(readlink pattern.sh)"
359+
elif [ ! -f "pattern.sh" ]; then
360+
echo "pattern.sh does not exist"
361+
fi
362+
exit 1
363+
fi
364+
365+
# Test 5.4: Check pattern.sh has USE_SECRETS=true (default for update)
366+
check_file_content "pattern.sh" 'USE_SECRETS:=true' "pattern.sh contains USE_SECRETS=true (update default)"
367+
368+
# Test 5.5: Verify pattern.sh is executable
369+
if [ -x "pattern.sh" ]; then
370+
echo -e "${GREEN}PASS: pattern.sh is executable (update)${NC}"
371+
else
372+
echo -e "${RED}FAIL: pattern.sh is not executable (update)${NC}"
373+
exit 1
374+
fi
375+
376+
echo -e "${GREEN}=== Test 5: Update existing pattern PASSED ===${NC}"
377+
288378
echo -e "${GREEN}All integration tests passed!${NC}"
289379

290380
# Clean up
291381
cd "$REPO_ROOT"
292-
rm -rf "$TEST_DIR" "$TEST_DIR_SECRETS" "$TEST_DIR_CUSTOM" "$TEST_DIR_SEQUENTIAL"
382+
rm -rf "$TEST_DIR" "$TEST_DIR_SECRETS" "$TEST_DIR_CUSTOM" "$TEST_DIR_SEQUENTIAL" "$TEST_DIR_UPDATE"

0 commit comments

Comments
 (0)