From 74928623b2ca94c09aacd07bc42e2385763a8bed Mon Sep 17 00:00:00 2001 From: Zhongwei Li Date: Sun, 30 Nov 2025 09:05:12 +0800 Subject: [PATCH] Initial commit --- .claude-plugin/plugin.json | 12 + README.md | 3 + plugin.lock.json | 129 +++++ skills/helm-scaffold/README.md | 321 +++++++++++ skills/helm-scaffold/SKILL.md | 400 +++++++++++++ .../assets/templates/.helmignore | 30 + .../helm-scaffold/assets/templates/Chart.yaml | 15 + .../helm-scaffold/assets/templates/NOTES.txt | 22 + .../assets/templates/_helpers.tpl | 60 ++ .../assets/templates/configmap/configmap.yaml | 12 + .../assets/templates/cronjob/cronjob.yaml | 66 +++ .../templates/deployment/deployment.yaml | 88 +++ .../assets/templates/hpa/hpa.yaml | 32 ++ .../assets/templates/ingress/ingress.yaml | 41 ++ .../assets/templates/job/job.yaml | 57 ++ .../assets/templates/rbac/serviceaccount.yaml | 12 + .../assets/templates/service/service.yaml | 25 + .../templates/statefulset/statefulset.yaml | 82 +++ .../assets/templates/values.yaml | 148 +++++ .../references/best-practices.md | 282 +++++++++ skills/helm-scaffold/references/examples.md | 507 +++++++++++++++++ skills/helm-scaffold/references/templates.md | 306 ++++++++++ .../helm-scaffold/references/testing-guide.md | 535 ++++++++++++++++++ .../references/workload-types.md | 322 +++++++++++ .../helm-scaffold/scripts/scaffold_chart.py | 234 ++++++++ 25 files changed, 3741 insertions(+) create mode 100644 .claude-plugin/plugin.json create mode 100644 README.md create mode 100644 plugin.lock.json create mode 100644 skills/helm-scaffold/README.md create mode 100644 skills/helm-scaffold/SKILL.md create mode 100644 skills/helm-scaffold/assets/templates/.helmignore create mode 100644 skills/helm-scaffold/assets/templates/Chart.yaml create mode 100644 skills/helm-scaffold/assets/templates/NOTES.txt create mode 100644 skills/helm-scaffold/assets/templates/_helpers.tpl create mode 100644 skills/helm-scaffold/assets/templates/configmap/configmap.yaml create mode 100644 skills/helm-scaffold/assets/templates/cronjob/cronjob.yaml create mode 100644 skills/helm-scaffold/assets/templates/deployment/deployment.yaml create mode 100644 skills/helm-scaffold/assets/templates/hpa/hpa.yaml create mode 100644 skills/helm-scaffold/assets/templates/ingress/ingress.yaml create mode 100644 skills/helm-scaffold/assets/templates/job/job.yaml create mode 100644 skills/helm-scaffold/assets/templates/rbac/serviceaccount.yaml create mode 100644 skills/helm-scaffold/assets/templates/service/service.yaml create mode 100644 skills/helm-scaffold/assets/templates/statefulset/statefulset.yaml create mode 100644 skills/helm-scaffold/assets/templates/values.yaml create mode 100644 skills/helm-scaffold/references/best-practices.md create mode 100644 skills/helm-scaffold/references/examples.md create mode 100644 skills/helm-scaffold/references/templates.md create mode 100644 skills/helm-scaffold/references/testing-guide.md create mode 100644 skills/helm-scaffold/references/workload-types.md create mode 100755 skills/helm-scaffold/scripts/scaffold_chart.py diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..abe9b21 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "devops", + "description": "devops skills such as generate Helm chart, Terraform script, CI/CD pipeline, GitOps, Docker.", + "version": "2.0.1", + "author": { + "name": "Truong Vu", + "email": "vukhanhtruong@gmail.com" + }, + "skills": [ + "./skills" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0f88a17 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# devops + +devops skills such as generate Helm chart, Terraform script, CI/CD pipeline, GitOps, Docker. diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..74ba404 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,129 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:vukhanhtruong/claude-rock:plugins/devops", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "5b8e6a1556735021f6f1617cfa6a2b9e718eb781", + "treeHash": "8ebd3ce282125128e5dd7b5dfe96d5d3366179ea9de22732e7fa5fe2dc7f12fd", + "generatedAt": "2025-11-28T10:28:57.557373Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "devops", + "description": "devops skills such as generate Helm chart, Terraform script, CI/CD pipeline, GitOps, Docker.", + "version": "2.0.1" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "7fd3be474421f517785f323e98c2e4a20e4b5c988697b30768395caace62533e" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "4385afe9f01cc9d71ee4ffce1becead8e79dbf6f7cbd6f1ae89cf7feb0e47e95" + }, + { + "path": "skills/helm-scaffold/README.md", + "sha256": "87a5713b7d2360e682e43a85921cd099951fd79b5582ffd7f2e9438edda12cae" + }, + { + "path": "skills/helm-scaffold/SKILL.md", + "sha256": "ca739aa0f145db9cbc4ab1fdb23f4d233db6fd1b95e278c12f97dbebef022560" + }, + { + "path": "skills/helm-scaffold/references/workload-types.md", + "sha256": "f164f9e6409f61245bdcf0ad950e48f340db287a8460507edbe834c9874cde1a" + }, + { + "path": "skills/helm-scaffold/references/examples.md", + "sha256": "340a35bf0ba8fe170af336c8d314418524feac00b4eef54f053cb105ee33bbd5" + }, + { + "path": "skills/helm-scaffold/references/templates.md", + "sha256": "572ee0547a4363e7bc0a8b7ab5b7328cb5788e09f69d75fcbd3395e5202d7057" + }, + { + "path": "skills/helm-scaffold/references/best-practices.md", + "sha256": "7a0f1d6031a518fc23fb7988f50af8176712d91306c6aa34c8f7694ae06506a8" + }, + { + "path": "skills/helm-scaffold/references/testing-guide.md", + "sha256": "42f6d38afab347f6e235a92bffa743cc8bb4a2071bcde70a194f169a4ebc8944" + }, + { + "path": "skills/helm-scaffold/scripts/scaffold_chart.py", + "sha256": "0bbd72d34b9809722efcdd79f04ecbe5d032d74ce20f56381c43b9cbc1911d59" + }, + { + "path": "skills/helm-scaffold/assets/templates/NOTES.txt", + "sha256": "e393b03a68958e9f8aa7e6a305480617d6f0e0c0850c8dcf97ad68e0d0d89679" + }, + { + "path": "skills/helm-scaffold/assets/templates/Chart.yaml", + "sha256": "5739a25d5f079bd85eca264748754e5ed084ac9c7648d133412b997420b749e7" + }, + { + "path": "skills/helm-scaffold/assets/templates/.helmignore", + "sha256": "2cac77c7531299fbb1622f56975bfb91327d983c917bf97726e8743fdd8e018c" + }, + { + "path": "skills/helm-scaffold/assets/templates/_helpers.tpl", + "sha256": "476c3b20b442e6cdf497b04c08db1df209c4d8f35b629b3732ef64778507d6e0" + }, + { + "path": "skills/helm-scaffold/assets/templates/values.yaml", + "sha256": "0450ae9b640b228f5be74f033ffd7e555e9b159e0716dceaf35246cb45d895fa" + }, + { + "path": "skills/helm-scaffold/assets/templates/rbac/serviceaccount.yaml", + "sha256": "b9d7aaa323cbe7c113607977d502ac1724908201ed9fe19b08cb18c3f7611996" + }, + { + "path": "skills/helm-scaffold/assets/templates/ingress/ingress.yaml", + "sha256": "715d18b56b6ea2a69ab0effeca1ab3dbd316916eeb6b9f9e98943dd7990763b4" + }, + { + "path": "skills/helm-scaffold/assets/templates/statefulset/statefulset.yaml", + "sha256": "d2fec67691c697d002d51eb8cbfd6c0c45b255af3d70c51d1bc04de3840aae62" + }, + { + "path": "skills/helm-scaffold/assets/templates/cronjob/cronjob.yaml", + "sha256": "eca716b667e3dcf262c0ba1cc89aa3227734970492ab03b7497fa568cefc9d7b" + }, + { + "path": "skills/helm-scaffold/assets/templates/deployment/deployment.yaml", + "sha256": "c80f1e745381d598bdde06568eaad6ebf0102f0b8aabca1cdd7d7b47ebb994a4" + }, + { + "path": "skills/helm-scaffold/assets/templates/job/job.yaml", + "sha256": "ac4ce80033b8aa8da1b371220b4e5c0afb48be6f7777dcbddc5ec34bfc752291" + }, + { + "path": "skills/helm-scaffold/assets/templates/configmap/configmap.yaml", + "sha256": "0f8aec4bd3ec46b6c3fdf108f0f13d26f7b1f915b37e0936cab1f1a751a4d232" + }, + { + "path": "skills/helm-scaffold/assets/templates/service/service.yaml", + "sha256": "ef0fcfa4afefb536b6661e8278309fc2a1ab9f885c4e150ec66b10c6668fddfe" + }, + { + "path": "skills/helm-scaffold/assets/templates/hpa/hpa.yaml", + "sha256": "50b686774a0acce4e056fb0680a9cd706c22d166c0b4ba1adbe23755b5140f30" + } + ], + "dirSha256": "8ebd3ce282125128e5dd7b5dfe96d5d3366179ea9de22732e7fa5fe2dc7f12fd" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/helm-scaffold/README.md b/skills/helm-scaffold/README.md new file mode 100644 index 0000000..70a6476 --- /dev/null +++ b/skills/helm-scaffold/README.md @@ -0,0 +1,321 @@ +# Helm Scaffold Skill + +A Claude Skill that generates production-ready Helm charts following Kubernetes and Helm best practices. + +## Overview + +This skill enables Claude to automatically generate complete, well-structured Helm charts for various application types including: + +- **Deployments** - Stateless applications (web apps, APIs, microservices) +- **StatefulSets** - Stateful applications (databases, message queues) +- **Jobs** - One-time batch processing tasks +- **CronJobs** - Scheduled recurring tasks + +## Features + +- **Production-Ready Charts** - Follows Kubernetes and CNCF best practices +- **Security Defaults** - Includes security contexts, read-only filesystems, non-root users +- **Resource Management** - Sensible CPU/memory limits and requests +- **Health Checks** - Liveness and readiness probes configured +- **Standard Labels** - Uses app.kubernetes.io/\* label namespace +- **Multi-Environment** - Generate dev, staging, and production value files +- **Complete Documentation** - Includes README, NOTES.txt, and inline comments +- **Dry Run Instructions** - Step-by-step testing and validation guide + +## Usage Examples + +### Example 1: Simple Web Application + +``` +Create a Helm chart for my Node.js API called "user-service" +running on port 3000 with image myregistry/user-service:1.0.0 +``` + +Claude will: + +1. Ask clarifying questions (replicas, ingress, autoscaling) +2. Generate complete chart structure +3. Include Deployment, Service, ServiceAccount, and optional resources +4. Provide testing instructions + +### Example 2: Database with Persistent Storage + +``` +Create a Helm chart for PostgreSQL with 20Gi of storage +``` + +Claude will: + +1. Generate StatefulSet instead of Deployment +2. Include PersistentVolumeClaim configuration +3. Add headless service for stable network identities +4. Configure appropriate security contexts + +### Example 3: Scheduled Job + +``` +Create a Helm chart for a data backup job that runs every night at 2 AM +``` + +Claude will: + +1. Generate CronJob template +2. Configure schedule: "0 2 \* \* \*" +3. Include job-specific settings (backoff, restart policy) +4. Provide dry run testing instructions + +### Example 4: Multi-Environment Setup + +``` +Create a Helm chart for my Python web app with dev, staging, +and production configurations +``` + +Claude will: + +1. Generate base values.yaml +2. Create values-dev.yaml with minimal resources +3. Create values-staging.yaml with moderate resources +4. Create values-prod.yaml with HA configuration, autoscaling, and security hardening +5. Provide deployment commands for each environment + +## Chart Structure + +Generated charts follow this structure: + +``` +/ +├── Chart.yaml # Chart metadata +├── values.yaml # Default configuration values +├── values-dev.yaml # Development overrides (optional) +├── values-staging.yaml # Staging overrides (optional) +├── values-prod.yaml # Production overrides (optional) +├── .helmignore # Files to ignore when packaging +├── README.md # Chart documentation +└── templates/ + ├── _helpers.tpl # Template helper functions + ├── NOTES.txt # Post-installation notes + ├── deployment.yaml # Deployment/StatefulSet/Job/CronJob + ├── service.yaml # Kubernetes Service + ├── serviceaccount.yaml # Service Account + ├── ingress.yaml # Ingress (optional) + ├── hpa.yaml # Horizontal Pod Autoscaler (optional) + ├── configmap.yaml # ConfigMap (optional) + └── secret.yaml # Secret (optional) +``` + +## Best Practices Included + +The skill automatically incorporates: + +### Security + +- Read-only root filesystem +- Non-root user execution +- Dropped capabilities +- Security contexts for pods and containers +- Automatic service account token mounting control + +### Resource Management + +- CPU and memory limits +- CPU and memory requests +- Sensible defaults based on application type + +### Labels and Selectors + +- Standard Kubernetes labels (app.kubernetes.io/\*) +- Proper selector label configuration +- Consistent labeling across resources + +### Health Checks + +- Liveness probes for container health +- Readiness probes for traffic routing +- Configurable probe parameters + +### Scalability + +- Horizontal Pod Autoscaler support +- Pod anti-affinity for high availability +- Node selector and toleration support + +## Testing Your Charts + +Every generated chart includes comprehensive testing instructions: + +1. **Lint** - Validate chart structure and syntax + + ```bash + helm lint . + ``` + +2. **Template** - Render all Kubernetes manifests + + ```bash + helm template my-app . + ``` + +3. **Dry Run** - Simulate installation + + ```bash + helm install my-app . --dry-run --debug + ``` + +4. **Test Environment** - Deploy to test namespace + ```bash + kubectl create namespace test + helm install my-app . -n test + ``` + +## Customization + +All generated values can be customized: + +### Via Command Line + +```bash +helm install my-app . \ + --set replicaCount=3 \ + --set image.tag=2.0.0 \ + --set resources.limits.memory=512Mi +``` + +### Via Values File + +```bash +helm install my-app . -f custom-values.yaml +``` + +### Via Multiple Values Files + +```bash +helm install my-app . \ + -f values.yaml \ + -f values-prod.yaml \ + -f overrides.yaml +``` + +## Requirements + +To use generated charts, you need: + +- **Kubernetes** 1.24+ cluster +- **Helm** 3.0+ CLI tool +- **kubectl** configured to access your cluster + +## Supported Application Types + +| Type | Use Case | Key Features | +| --------------- | ---------------------------------- | --------------------------------------- | +| **Deployment** | Stateless apps, APIs, web services | Rolling updates, multiple replicas | +| **StatefulSet** | Databases, caches, message queues | Stable network IDs, persistent storage | +| **Job** | Data migration, batch processing | One-time execution, completion tracking | +| **CronJob** | Scheduled tasks, backups, reports | Recurring schedule, job history | + +## Common Use Cases + +### Microservices Architecture + +Generate consistent charts for all microservices with standard labels, security settings, and observability configuration. + +### Database Deployment + +Create StatefulSet-based charts with persistent storage, proper backup strategies, and initialization scripts. + +## Troubleshooting + +### Chart Fails Lint + +- Check YAML indentation (use 2 spaces, no tabs) +- Verify all template variables exist in values.yaml +- Ensure Chart.yaml has valid semantic version + +### Pods Won't Start + +- Check resource limits (may be too low) +- Verify image pull secrets are configured +- Review security context settings +- Check persistent volume claims (for StatefulSets) + +### Health Check Failures + +- Increase `initialDelaySeconds` for slow-starting apps +- Verify health check endpoint paths +- Ensure application exposes health endpoints + +### Template Errors + +- Validate Go template syntax +- Check for nil pointer errors (missing values) +- Use `helm template` to debug rendering + +## Advanced Features + +### Custom Resource Definitions (CRDs) + +For CRDs, create them in a separate chart and add as a dependency. + +### Subchart Management + +```yaml +# Chart.yaml +dependencies: + - name: postgresql + version: "12.x.x" + repository: "https://charts.bitnami.com/bitnami" +``` + +### Values Schema Validation + +Add `values.schema.json` for IDE autocomplete and validation: + +```json +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "replicaCount": { + "type": "integer", + "minimum": 1 + } + } +} +``` + +### Hooks for Lifecycle Management + +```yaml +apiVersion: batch/v1 +kind: Job +metadata: + annotations: + "helm.sh/hook": pre-install + "helm.sh/hook-weight": "-5" +``` + +## Contributing + +To extend this skill: + +1. Add new template patterns to the SKILL.md +2. Include additional best practices +3. Add examples for specific use cases +4. Update testing procedures + +## Support + +For issues or questions: + +- Review the generated README.md in your chart +- Check Helm documentation: https://helm.sh/docs/ +- Review Kubernetes best practices: https://kubernetes.io/docs/concepts/configuration/overview/ + +## Version History + +- **v1.0.0** (2025-10-23) - Initial release + - Support for Deployment, StatefulSet, Job, CronJob + - Multi-environment configuration + - Comprehensive security defaults + - Dry run testing instructions + - Production-ready templates diff --git a/skills/helm-scaffold/SKILL.md b/skills/helm-scaffold/SKILL.md new file mode 100644 index 0000000..3f23339 --- /dev/null +++ b/skills/helm-scaffold/SKILL.md @@ -0,0 +1,400 @@ +--- +name: helm-scaffold +description: Generate production-ready Helm charts for Kubernetes applications. Use when users need to create new Helm charts, convert Kubernetes manifests to Helm templates, scaffold charts for Deployments/StatefulSets/Jobs/CronJobs, create multi-environment configurations, or standardize organizational chart templates with CNCF/Helm best practices. Uses Python scaffolding script and template assets for automated generation. +--- + +# Helm Scaffold + +Automate production-ready Helm chart generation using template assets, Python scaffolding scripts, and Kubernetes best practices through conversational interaction. + +## Core Capabilities + +- **Automated scaffolding** using Python script (`scripts/scaffold_chart.py`) +- **Template-based generation** from `assets/templates/` directory +- **Convert Kubernetes manifests** to Helm templates +- **Multi-environment configurations** (dev, staging, prod) +- **Organizational standardization** with team policies +- **Comprehensive testing** with dry-run workflows + +## Quick Start + +For simple chart generation, use the Python scaffolding script directly: + +```bash +python3 scripts/scaffold_chart.py \ + --workload-type deployment \ + --output /mnt/user-data/outputs \ + --ingress \ + --hpa +``` + +## Workflow Decision Tree + +``` +User Request +├─ Simple chart (name + type provided) +│ └─ Use scaffold_chart.py directly +├─ Complex chart (many options) +│ └─ Ask clarifying questions, then use scaffold_chart.py +├─ Manifest conversion +│ └─ Manual templatization process +└─ Custom requirements + └─ Manual generation with template references +``` + +## Interactive Workflow + +### Step 1: Understand Use Case + +Identify the scenario: +- **Simple new chart**: Use `scaffold_chart.py` with user-provided params +- **Complex chart**: Ask questions, build command +- **Manifest conversion**: Extract variables, templatize +- **Team template**: Apply organizational standards + +### Step 2: Gather Requirements + +**Minimum required:** +- Chart name +- Workload type (deployment, statefulset, job, cronjob) + +**Optional (ask if not provided):** +- Container image repository and tag +- Application port +- Include Ingress? (--ingress flag) +- Include HPA? (--hpa flag) +- Include ConfigMap? (--configmap flag) +- Multi-environment configs? + +### Step 3: Generate Chart + +#### Option A: Using scaffold_chart.py (Recommended) + +For straightforward charts, execute the Python script: + +```python +import subprocess +cmd = [ + 'python3', 'scripts/scaffold_chart.py', + chart_name, + '--workload-type', workload_type, + '--output', '/mnt/user-data/outputs' +] +if include_ingress: + cmd.append('--ingress') +if include_hpa: + cmd.append('--hpa') +if include_configmap: + cmd.append('--configmap') + +subprocess.run(cmd, check=True) +``` + +**The script automatically:** +- Creates chart directory structure +- Copies template files from `assets/templates/` +- Replaces `CHARTNAME` placeholder with actual chart name +- Includes only requested resources +- Applies best practices + +#### Option B: Manual Generation + +For custom requirements, manually copy and modify templates: + +1. Read template from `assets/templates//.yaml` +2. Replace `CHARTNAME` with actual chart name +3. Customize based on user requirements +4. Write to `/home/claude//templates/` + +### Step 4: Multi-Environment Configuration + +If user requests dev/staging/prod configs, create additional values files: + +**values-dev.yaml:** +```yaml +replicaCount: 1 +resources: + limits: {cpu: 200m, memory: 128Mi} + requests: {cpu: 25m, memory: 32Mi} +env: + - name: LOG_LEVEL + value: "debug" +``` + +**values-prod.yaml:** +```yaml +replicaCount: 3 +resources: + limits: {cpu: 1000m, memory: 512Mi} + requests: {cpu: 100m, memory: 128Mi} +autoscaling: + enabled: true + minReplicas: 3 + maxReplicas: 10 +``` + +### Step 5: Testing Instructions + +**Always provide comprehensive testing workflow:** + +```bash +# Navigate to chart directory +cd + +# 1. Validate chart structure +helm lint . + +# 2. Render templates locally +helm template . + +# 3. Dry run installation +helm install . --dry-run --debug + +# 4. Test with specific values +helm template . -f values-dev.yaml + +# 5. Deploy to test namespace +kubectl create namespace test +helm install . -n test + +# 6. Verify deployment +kubectl get all -n test +helm status -n test + +# 7. Cleanup +helm uninstall -n test +kubectl delete namespace test +``` + +### Step 6: Deliver Output + +- Chart is created in `/mnt/user-data/outputs//` +- Provide download link +- Include testing instructions +- Explain customization options + +## Workload Types + +Load `references/workload-types.md` for detailed decision tree and characteristics. + +**Quick reference:** +- **Deployment**: Stateless apps (web, API, microservices) +- **StatefulSet**: Stateful apps (databases, caches) - stable IDs, persistent storage +- **Job**: One-time tasks (migrations, ETL) +- **CronJob**: Scheduled tasks (backups, reports) + +**Template locations:** +- `assets/templates/deployment/deployment.yaml` +- `assets/templates/statefulset/statefulset.yaml` +- `assets/templates/job/job.yaml` +- `assets/templates/cronjob/cronjob.yaml` + +## Converting Manifests to Helm + +When user provides raw Kubernetes YAML: + +1. **Analyze manifests**: Identify resources and configurable values +2. **Extract variables**: Images, replicas, ports, resources, env-specific settings +3. **Create values.yaml**: Organize extracted values logically +4. **Templatize YAML**: + - Replace hardcoded values with `{{ .Values.* }}` + - Use `{{ include "CHARTNAME.fullname" . }}` for names + - Use `{{ include "CHARTNAME.labels" . }}` for labels +5. **Add helpers**: Copy `assets/templates/_helpers.tpl` and customize +6. **Document**: Explain what was parameterized + +## Template Assets Structure + +``` +assets/templates/ +├── Chart.yaml # Base chart metadata +├── values.yaml # Complete values with all options +├── .helmignore # Files to ignore +├── _helpers.tpl # Helper functions (CHARTNAME placeholder) +├── NOTES.txt # Post-install instructions +├── deployment/ +│ └── deployment.yaml # Deployment template +├── statefulset/ +│ └── statefulset.yaml # StatefulSet template +├── job/ +│ └── job.yaml # Job template +├── cronjob/ +│ └── cronjob.yaml # CronJob template +├── service/ +│ └── service.yaml # Service template +├── ingress/ +│ └── ingress.yaml # Ingress template +├── hpa/ +│ └── hpa.yaml # HPA template +├── configmap/ +│ └── configmap.yaml # ConfigMap template +└── rbac/ + └── serviceaccount.yaml # ServiceAccount template +``` + +All templates use `CHARTNAME` placeholder which is replaced by the script. + +## Scripts + +### scaffold_chart.py + +**Purpose**: Automated chart generation from templates + +**Usage**: +```bash +python3 scripts/scaffold_chart.py CHART_NAME [OPTIONS] + +Arguments: + CHART_NAME Name of the Helm chart + +Options: + -w, --workload-type Type: deployment, statefulset, job, cronjob (default: deployment) + -o, --output Output directory (default: current directory) + --ingress Include Ingress resource + --hpa Include HorizontalPodAutoscaler + --configmap Include ConfigMap +``` + +**What it does:** +- Creates chart directory structure +- Copies relevant templates from `assets/templates/` +- Replaces `CHARTNAME` placeholder +- Includes only requested optional resources +- Applies best practices automatically + +## Best Practices (Auto-Applied) + +The templates in `assets/templates/` already include: +- ✅ Standard Kubernetes labels (`app.kubernetes.io/*`) +- ✅ Security contexts (readOnlyRootFilesystem, runAsNonRoot, dropped capabilities) +- ✅ Resource limits and requests +- ✅ Health checks (liveness and readiness probes) +- ✅ Helper functions for naming and labels +- ✅ Proper selector labels +- ✅ Service account configuration + +## Organizational Standardization + +For platform teams needing consistent charts: + +1. **Capture standards**: Ask about required labels, policies, security requirements +2. **Modify templates**: Update `assets/templates/_helpers.tpl` with org labels +3. **Generate**: Use scaffold_chart.py with modified templates +4. **Document**: Explain customization points + +Example org-specific helper addition: +```yaml +{{- define "CHARTNAME.orgLabels" -}} +org.example.com/cost-center: {{ .Values.org.costCenter | required "Cost center required" }} +org.example.com/team: {{ .Values.org.team | required "Team name required" }} +{{- end }} +``` + +## Success Criteria + +Generated charts must: +- ✅ Pass `helm lint` without errors (95%+ target) +- ✅ Follow CNCF/Helm best practices +- ✅ Include security contexts by default +- ✅ Have resource limits configured +- ✅ Use standard Kubernetes labels +- ✅ Be deployment-ready within <15 minutes + +## References + +Load as needed based on use case: + +**For workload selection:** +- `references/workload-types.md` - Decision tree, characteristics, when to use each type + +**For testing:** +- `references/testing-guide.md` - Comprehensive dry-run workflows, mock values, validation commands + +**For best practices:** +- `references/best-practices.md` - CNCF/Helm standards, security guidelines + +**For examples:** +- `references/examples.md` - Real-world chart scenarios + +**For template reference:** +- `references/templates.md` - Inline template documentation + +## Example Usage + +### Example 1: Simple Web App + +``` +User: "Create a Helm chart for my Node.js API" + +Claude: I'll create a chart for your Node.js API. A few quick questions: +1. What's the chart name? (e.g., "user-api") +2. What's your container image? (e.g., "myorg/user-api:1.0.0") +3. Do you need an Ingress? (yes/no) + +User: Name is user-api, image is myorg/user-api:1.0.0, yes to ingress + +Claude: [Executes] +python3 scripts/scaffold_chart.py user-api \ + --workload-type deployment \ + --output /mnt/user-data/outputs \ + --ingress + +[Chart generated successfully] +[Provides testing instructions] +``` + +### Example 2: Database + +``` +User: "Create a Helm chart for PostgreSQL with persistent storage" + +Claude: I'll create a StatefulSet chart for PostgreSQL. +What storage size do you need? (e.g., 10Gi, 20Gi) + +User: 20Gi + +Claude: [Executes] +python3 scripts/scaffold_chart.py postgresql \ + --workload-type statefulset \ + --output /mnt/user-data/outputs + +[Then manually adds persistence config to values.yaml] +persistence: + enabled: true + size: 20Gi + storageClass: standard +``` + +### Example 3: Scheduled Backup + +``` +User: "Create a CronJob chart for nightly backups" + +Claude: [Executes] +python3 scripts/scaffold_chart.py db-backup \ + --workload-type cronjob \ + --output /mnt/user-data/outputs + +[Then updates values.yaml with schedule] +cronjob: + schedule: "0 2 * * *" +``` + +## Troubleshooting + +**Issue**: Script fails to find templates +**Solution**: Ensure running from skill directory or provide full path to script + +**Issue**: CHARTNAME not replaced +**Solution**: Script automatically handles this; if manual, use find/replace + +**Issue**: Generated chart fails lint +**Solution**: Check values.yaml for required fields, review templates + +## Notes + +- Always use `scaffold_chart.py` when possible - it's faster and consistent +- Templates use `CHARTNAME` placeholder - script replaces automatically +- For complex customization, read templates from assets and modify manually +- Multi-environment configs are created separately after initial generation +- Testing instructions are critical - always include them diff --git a/skills/helm-scaffold/assets/templates/.helmignore b/skills/helm-scaffold/assets/templates/.helmignore new file mode 100644 index 0000000..047d930 --- /dev/null +++ b/skills/helm-scaffold/assets/templates/.helmignore @@ -0,0 +1,30 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ +# CI/CD +.gitlab-ci.yml +.travis.yml +.github/ +# Documentation +README.md +CONTRIBUTING.md diff --git a/skills/helm-scaffold/assets/templates/Chart.yaml b/skills/helm-scaffold/assets/templates/Chart.yaml new file mode 100644 index 0000000..3e4f9b0 --- /dev/null +++ b/skills/helm-scaffold/assets/templates/Chart.yaml @@ -0,0 +1,15 @@ +apiVersion: v2 +name: CHARTNAME +description: A Helm chart for Kubernetes +type: application +version: 0.1.0 +appVersion: "1.0.0" +keywords: + - kubernetes + - helm +home: https://github.com/yourorg/CHARTNAME +sources: + - https://github.com/yourorg/CHARTNAME +maintainers: + - name: Your Name + email: your.email@example.com diff --git a/skills/helm-scaffold/assets/templates/NOTES.txt b/skills/helm-scaffold/assets/templates/NOTES.txt new file mode 100644 index 0000000..74990f1 --- /dev/null +++ b/skills/helm-scaffold/assets/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "CHARTNAME.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "CHARTNAME.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "CHARTNAME.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "CHARTNAME.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/skills/helm-scaffold/assets/templates/_helpers.tpl b/skills/helm-scaffold/assets/templates/_helpers.tpl new file mode 100644 index 0000000..831c82f --- /dev/null +++ b/skills/helm-scaffold/assets/templates/_helpers.tpl @@ -0,0 +1,60 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "CHARTNAME.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "CHARTNAME.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "CHARTNAME.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "CHARTNAME.labels" -}} +helm.sh/chart: {{ include "CHARTNAME.chart" . }} +{{ include "CHARTNAME.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "CHARTNAME.selectorLabels" -}} +app.kubernetes.io/name: {{ include "CHARTNAME.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "CHARTNAME.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "CHARTNAME.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/skills/helm-scaffold/assets/templates/configmap/configmap.yaml b/skills/helm-scaffold/assets/templates/configmap/configmap.yaml new file mode 100644 index 0000000..739a66e --- /dev/null +++ b/skills/helm-scaffold/assets/templates/configmap/configmap.yaml @@ -0,0 +1,12 @@ +{{- if .Values.configMap.enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "CHARTNAME.fullname" . }} + labels: + {{- include "CHARTNAME.labels" . | nindent 4 }} +data: + {{- with .Values.configMap.data }} + {{- toYaml . | nindent 2 }} + {{- end }} +{{- end }} diff --git a/skills/helm-scaffold/assets/templates/cronjob/cronjob.yaml b/skills/helm-scaffold/assets/templates/cronjob/cronjob.yaml new file mode 100644 index 0000000..6a6e84c --- /dev/null +++ b/skills/helm-scaffold/assets/templates/cronjob/cronjob.yaml @@ -0,0 +1,66 @@ +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{ include "CHARTNAME.fullname" . }} + labels: + {{- include "CHARTNAME.labels" . | nindent 4 }} +spec: + schedule: {{ .Values.cronJob.schedule | quote }} + {{- if .Values.cronJob.concurrencyPolicy }} + concurrencyPolicy: {{ .Values.cronJob.concurrencyPolicy }} + {{- end }} + {{- if .Values.cronJob.successfulJobsHistoryLimit }} + successfulJobsHistoryLimit: {{ .Values.cronJob.successfulJobsHistoryLimit }} + {{- end }} + {{- if .Values.cronJob.failedJobsHistoryLimit }} + failedJobsHistoryLimit: {{ .Values.cronJob.failedJobsHistoryLimit }} + {{- end }} + jobTemplate: + spec: + {{- if .Values.cronJob.backoffLimit }} + backoffLimit: {{ .Values.cronJob.backoffLimit }} + {{- end }} + {{- if .Values.cronJob.activeDeadlineSeconds }} + activeDeadlineSeconds: {{ .Values.cronJob.activeDeadlineSeconds }} + {{- end }} + template: + metadata: + labels: + {{- include "CHARTNAME.selectorLabels" . | nindent 12 }} + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 12 }} + {{- end }} + spec: + restartPolicy: {{ .Values.cronJob.restartPolicy | default "OnFailure" }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 12 }} + {{- end }} + serviceAccountName: {{ include "CHARTNAME.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 12 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 14 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.cronJob.command }} + command: + {{- toYaml .Values.cronJob.command | nindent 14 }} + {{- end }} + {{- if .Values.cronJob.args }} + args: + {{- toYaml .Values.cronJob.args | nindent 14 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 14 }} + {{- with .Values.env }} + env: + {{- toYaml . | nindent 14 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 12 }} + {{- end }} diff --git a/skills/helm-scaffold/assets/templates/deployment/deployment.yaml b/skills/helm-scaffold/assets/templates/deployment/deployment.yaml new file mode 100644 index 0000000..51851d9 --- /dev/null +++ b/skills/helm-scaffold/assets/templates/deployment/deployment.yaml @@ -0,0 +1,88 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "CHARTNAME.fullname" . }} + labels: + {{- include "CHARTNAME.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "CHARTNAME.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "CHARTNAME.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "CHARTNAME.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: {{ .Values.livenessProbe.path }} + port: http + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }} + successThreshold: {{ .Values.livenessProbe.successThreshold }} + failureThreshold: {{ .Values.livenessProbe.failureThreshold }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: {{ .Values.readinessProbe.path }} + port: http + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }} + successThreshold: {{ .Values.readinessProbe.successThreshold }} + failureThreshold: {{ .Values.readinessProbe.failureThreshold }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.env }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.volumeMounts }} + volumeMounts: + {{- toYaml .Values.volumeMounts | nindent 12 }} + {{- end }} + {{- if .Values.volumes }} + volumes: + {{- toYaml .Values.volumes | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/skills/helm-scaffold/assets/templates/hpa/hpa.yaml b/skills/helm-scaffold/assets/templates/hpa/hpa.yaml new file mode 100644 index 0000000..fa7db15 --- /dev/null +++ b/skills/helm-scaffold/assets/templates/hpa/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "CHARTNAME.fullname" . }} + labels: + {{- include "CHARTNAME.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "CHARTNAME.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/skills/helm-scaffold/assets/templates/ingress/ingress.yaml b/skills/helm-scaffold/assets/templates/ingress/ingress.yaml new file mode 100644 index 0000000..2d00656 --- /dev/null +++ b/skills/helm-scaffold/assets/templates/ingress/ingress.yaml @@ -0,0 +1,41 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "CHARTNAME.fullname" . }} + labels: + {{- include "CHARTNAME.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "CHARTNAME.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/skills/helm-scaffold/assets/templates/job/job.yaml b/skills/helm-scaffold/assets/templates/job/job.yaml new file mode 100644 index 0000000..d1a7afa --- /dev/null +++ b/skills/helm-scaffold/assets/templates/job/job.yaml @@ -0,0 +1,57 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "CHARTNAME.fullname" . }} + labels: + {{- include "CHARTNAME.labels" . | nindent 4 }} +spec: + {{- if .Values.job.backoffLimit }} + backoffLimit: {{ .Values.job.backoffLimit }} + {{- end }} + {{- if .Values.job.activeDeadlineSeconds }} + activeDeadlineSeconds: {{ .Values.job.activeDeadlineSeconds }} + {{- end }} + {{- if .Values.job.ttlSecondsAfterFinished }} + ttlSecondsAfterFinished: {{ .Values.job.ttlSecondsAfterFinished }} + {{- end }} + template: + metadata: + labels: + {{- include "CHARTNAME.selectorLabels" . | nindent 8 }} + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + restartPolicy: {{ .Values.job.restartPolicy | default "OnFailure" }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "CHARTNAME.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.job.command }} + command: + {{- toYaml .Values.job.command | nindent 12 }} + {{- end }} + {{- if .Values.job.args }} + args: + {{- toYaml .Values.job.args | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.env }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/skills/helm-scaffold/assets/templates/rbac/serviceaccount.yaml b/skills/helm-scaffold/assets/templates/rbac/serviceaccount.yaml new file mode 100644 index 0000000..a452d8c --- /dev/null +++ b/skills/helm-scaffold/assets/templates/rbac/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "CHARTNAME.serviceAccountName" . }} + labels: + {{- include "CHARTNAME.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/skills/helm-scaffold/assets/templates/service/service.yaml b/skills/helm-scaffold/assets/templates/service/service.yaml new file mode 100644 index 0000000..086d12b --- /dev/null +++ b/skills/helm-scaffold/assets/templates/service/service.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "CHARTNAME.fullname" . }} + labels: + {{- include "CHARTNAME.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + {{- if and (eq .Values.service.type "LoadBalancer") .Values.service.loadBalancerIP }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} + {{- end }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) .Values.service.nodePort }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + selector: + {{- include "CHARTNAME.selectorLabels" . | nindent 4 }} diff --git a/skills/helm-scaffold/assets/templates/statefulset/statefulset.yaml b/skills/helm-scaffold/assets/templates/statefulset/statefulset.yaml new file mode 100644 index 0000000..14fd1d9 --- /dev/null +++ b/skills/helm-scaffold/assets/templates/statefulset/statefulset.yaml @@ -0,0 +1,82 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "CHARTNAME.fullname" . }} + labels: + {{- include "CHARTNAME.labels" . | nindent 4 }} +spec: + serviceName: {{ include "CHARTNAME.fullname" . }}-headless + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "CHARTNAME.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "CHARTNAME.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "CHARTNAME.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + {{- if .Values.livenessProbe.enabled }} + livenessProbe: + httpGet: + path: {{ .Values.livenessProbe.path }} + port: http + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.livenessProbe.periodSeconds }} + {{- end }} + {{- if .Values.readinessProbe.enabled }} + readinessProbe: + httpGet: + path: {{ .Values.readinessProbe.path }} + port: http + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }} + periodSeconds: {{ .Values.readinessProbe.periodSeconds }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumeMounts: + - name: data + mountPath: /data + {{- if .Values.volumeMounts }} + {{- toYaml .Values.volumeMounts | nindent 12 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: [ "ReadWriteOnce" ] + {{- if .Values.persistence.storageClass }} + storageClassName: {{ .Values.persistence.storageClass }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size }} diff --git a/skills/helm-scaffold/assets/templates/values.yaml b/skills/helm-scaffold/assets/templates/values.yaml new file mode 100644 index 0000000..0975785 --- /dev/null +++ b/skills/helm-scaffold/assets/templates/values.yaml @@ -0,0 +1,148 @@ +# Default values for CHARTNAME. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: nginx + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: + fsGroup: 2000 + +securityContext: + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + allowPrivilegeEscalation: false + +service: + type: ClusterIP + port: 80 + annotations: {} + # For LoadBalancer type + # loadBalancerIP: "" + # For NodePort type + # nodePort: 30000 + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # cert-manager.io/cluster-issuer: letsencrypt-prod + hosts: + - host: chart-example.local + paths: + - path: / + pathType: Prefix + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: + limits: + cpu: 500m + memory: 256Mi + requests: + cpu: 50m + memory: 64Mi + +livenessProbe: + enabled: true + path: /healthz + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + +readinessProbe: + enabled: true + path: /ready + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 3 + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +env: [] +# - name: ENVIRONMENT +# value: "production" + +volumeMounts: [] +# - name: config +# mountPath: /etc/config + +volumes: [] +# - name: config +# configMap: +# name: my-config + +configMap: + enabled: false + data: {} + # key1: value1 + # key2: value2 + +# For StatefulSet deployments +persistence: + enabled: false + storageClass: "" + size: 8Gi + +# For Job workloads +job: + backoffLimit: 4 + activeDeadlineSeconds: 600 + ttlSecondsAfterFinished: 86400 + restartPolicy: OnFailure + command: [] + args: [] + +# For CronJob workloads +cronJob: + schedule: "0 0 * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 1 + backoffLimit: 4 + activeDeadlineSeconds: 600 + restartPolicy: OnFailure + command: [] + args: [] diff --git a/skills/helm-scaffold/references/best-practices.md b/skills/helm-scaffold/references/best-practices.md new file mode 100644 index 0000000..0ce81c2 --- /dev/null +++ b/skills/helm-scaffold/references/best-practices.md @@ -0,0 +1,282 @@ +# Helm Chart Best Practices + +CNCF and Helm community standards for production-ready charts. + +## Chart Metadata Standards + +### Chart.yaml Requirements +- `apiVersion: v2` (Helm 3) +- Semantic versioning (version, appVersion) +- Meaningful description +- Keywords for discoverability +- Maintainer information + +### Naming Conventions +- Chart names: lowercase, hyphens (no underscores) +- Resource names: `{{ template "name.fullname" . }}` +- Avoid hardcoding names + +## Kubernetes Label Standards + +**Required labels (app.kubernetes.io/* namespace):** +```yaml +labels: + app.kubernetes.io/name: {{ include "chart.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} + app.kubernetes.io/managed-by: {{ .Release.Service }} + helm.sh/chart: {{ include "chart.chart" . }} +``` + +**Selector labels (must be immutable):** +```yaml +selector: + matchLabels: + app.kubernetes.io/name: {{ include "chart.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} +``` + +## Security Best Practices + +### Pod Security Context +```yaml +podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault # Production +``` + +### Container Security Context +```yaml +securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + capabilities: + drop: + - ALL +``` + +### Security Guidelines +- Never run as root (UID 0) +- Drop all Linux capabilities by default +- Use read-only root filesystem when possible +- Apply seccomp profiles in production +- Avoid privileged containers +- Don't expose host ports or namespaces + +## Resource Management + +### Always Define Resources +```yaml +resources: + limits: + cpu: 500m + memory: 256Mi + requests: + cpu: 50m + memory: 64Mi +``` + +### Resource Sizing Guidelines +- **Small apps**: 50m CPU / 64Mi memory (requests) +- **Medium apps**: 100m CPU / 128Mi memory (requests) +- **Large apps**: 250m+ CPU / 256Mi+ memory (requests) +- Limits should be 2-10x requests +- Monitor and adjust based on actual usage + +## Health Checks + +### Liveness Probe +Detects when container needs restart: +```yaml +livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 +``` + +### Readiness Probe +Detects when container can accept traffic: +```yaml +readinessProbe: + httpGet: + path: /ready + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 +``` + +### Probe Best Practices +- Always define both liveness and readiness +- Use appropriate initialDelaySeconds for slow-starting apps +- Health endpoints should be lightweight +- Don't use same endpoint for liveness and readiness if startup is slow + +## Values.yaml Organization + +### Structure +```yaml +# 1. Replica configuration +replicaCount: 1 + +# 2. Image configuration +image: + repository: example/app + pullPolicy: IfNotPresent + tag: "" # Defaults to Chart.appVersion + +# 3. Service account +serviceAccount: + create: true + name: "" + +# 4. Security contexts +podSecurityContext: {} +securityContext: {} + +# 5. Service configuration +service: + type: ClusterIP + port: 80 + +# 6. Resources +resources: {} + +# 7. Autoscaling +autoscaling: + enabled: false + +# 8. Additional features (Ingress, ConfigMaps, etc.) +``` + +### Documentation +- Comment every major section +- Provide examples for complex values +- Document accepted value types +- Explain default behavior + +## Template Best Practices + +### Use Helper Functions +```yaml +# _helpers.tpl +{{- define "app.fullname" -}} +{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }} +{{- end }} +``` + +### Conditional Resources +```yaml +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +... +{{- end }} +``` + +### Checksum Annotations +Force pod restart on config changes: +```yaml +annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} +``` + +## NOTES.txt Guidelines + +Provide clear post-installation instructions: +``` +1. How to access the application +2. Default credentials (if any) +3. Next steps for configuration +4. Links to documentation +5. Troubleshooting commands +``` + +## Multi-Environment Patterns + +### Base + Override Pattern +- `values.yaml`: Base defaults +- `values-dev.yaml`: Development overrides +- `values-prod.yaml`: Production overrides + +### Environment-Specific Settings +- **Dev**: Debug enabled, minimal resources, verbose logging +- **Staging**: Production-like, moderate resources +- **Prod**: HA, autoscaling, security hardening, monitoring + +## Common Pitfalls to Avoid + +❌ **Don't:** +- Hardcode values in templates +- Forget resource limits +- Run containers as root +- Skip health checks +- Use `latest` image tag +- Expose secrets in values.yaml +- Create resources without labels +- Ignore security contexts + +✅ **Do:** +- Use template functions +- Define all resources +- Use non-root users +- Configure probes +- Pin specific versions +- Reference external secrets +- Apply standard labels +- Enable security contexts + +## Testing Checklist + +Before deploying: +- [ ] `helm lint` passes +- [ ] `helm template` renders correctly +- [ ] All required labels present +- [ ] Security contexts configured +- [ ] Resource limits defined +- [ ] Health checks configured +- [ ] NOTES.txt provides clear instructions +- [ ] README documents all values +- [ ] Dry run succeeds +- [ ] Test deployment in dev environment + +## Validation Commands + +```bash +# Lint chart +helm lint . + +# Template rendering +helm template myrelease . + +# Dry run +helm install myrelease . --dry-run --debug + +# Install to test namespace +kubectl create ns test +helm install myrelease . -n test + +# Verify +kubectl get all -n test +helm test myrelease -n test + +# Cleanup +helm uninstall myrelease -n test +kubectl delete ns test +``` + +## References + +- [Helm Best Practices](https://helm.sh/docs/chart_best_practices/) +- [Kubernetes Labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/) +- [CNCF Security Whitepaper](https://github.com/cncf/tag-security) +- [Pod Security Standards](https://kubernetes.io/docs/concepts/security/pod-security-standards/) diff --git a/skills/helm-scaffold/references/examples.md b/skills/helm-scaffold/references/examples.md new file mode 100644 index 0000000..8661845 --- /dev/null +++ b/skills/helm-scaffold/references/examples.md @@ -0,0 +1,507 @@ +# Helm Chart Examples + +Real-world chart examples for common use cases. + +## Example 1: Simple Web Application + +**Scenario**: Node.js API microservice + +**Chart.yaml**: +```yaml +apiVersion: v2 +name: user-api +description: User management API service +type: application +version: 0.1.0 +appVersion: "1.0.0" +``` + +**values.yaml** (key sections): +```yaml +replicaCount: 2 + +image: + repository: myorg/user-api + tag: "1.0.0" + +service: + type: ClusterIP + port: 80 + targetPort: 3000 + +ingress: + enabled: true + className: nginx + hosts: + - host: api.example.com + paths: + - path: /users + pathType: Prefix + +resources: + limits: {cpu: 500m, memory: 256Mi} + requests: {cpu: 100m, memory: 128Mi} + +autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 70 +``` + +**Usage**: +```bash +helm install user-api . -n production +``` + +--- + +## Example 2: Database (StatefulSet) + +**Scenario**: PostgreSQL with persistent storage + +**Chart.yaml**: +```yaml +apiVersion: v2 +name: postgresql +description: PostgreSQL database +type: application +version: 0.1.0 +appVersion: "15" +``` + +**values.yaml** (key sections): +```yaml +replicaCount: 1 + +image: + repository: postgres + tag: "15" + +persistence: + enabled: true + storageClass: "standard" + accessMode: ReadWriteOnce + size: 20Gi + +resources: + limits: {cpu: 1000m, memory: 2Gi} + requests: {cpu: 250m, memory: 512Mi} + +env: + - name: POSTGRES_DB + value: myapp + - name: POSTGRES_USER + value: postgres + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: postgres-secret + key: password +``` + +**StatefulSet template** (key parts): +```yaml +spec: + serviceName: postgresql-headless + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: ["ReadWriteOnce"] + storageClassName: standard + resources: + requests: + storage: 20Gi +``` + +--- + +## Example 3: Scheduled Job (CronJob) + +**Scenario**: Nightly database backup + +**Chart.yaml**: +```yaml +apiVersion: v2 +name: db-backup +description: Automated database backup job +type: application +version: 0.1.0 +appVersion: "1.0.0" +``` + +**values.yaml** (key sections): +```yaml +cronjob: + schedule: "0 2 * * *" # 2 AM daily + successfulJobsHistoryLimit: 7 + failedJobsHistoryLimit: 3 + concurrencyPolicy: Forbid + +image: + repository: myorg/backup-tool + tag: "latest" + +resources: + limits: {cpu: 500m, memory: 512Mi} + requests: {cpu: 100m, memory: 128Mi} + +env: + - name: BACKUP_RETENTION_DAYS + value: "30" + - name: S3_BUCKET + value: "my-backups" +``` + +--- + +## Example 4: Multi-Environment Configuration + +**Scenario**: Application deployed to dev, staging, prod + +**values.yaml** (base): +```yaml +replicaCount: 1 + +image: + repository: myorg/myapp + tag: "" + +resources: + limits: {cpu: 500m, memory: 256Mi} + requests: {cpu: 50m, memory: 64Mi} + +env: + - name: LOG_LEVEL + value: "info" +``` + +**values-dev.yaml**: +```yaml +replicaCount: 1 + +image: + tag: "dev" + +resources: + limits: {cpu: 200m, memory: 128Mi} + requests: {cpu: 25m, memory: 32Mi} + +env: + - name: LOG_LEVEL + value: "debug" + - name: DEBUG + value: "true" + +ingress: + enabled: true + hosts: + - host: dev.myapp.example.com +``` + +**values-prod.yaml**: +```yaml +replicaCount: 3 + +image: + tag: "1.0.0" + +resources: + limits: {cpu: 1000m, memory: 512Mi} + requests: {cpu: 100m, memory: 128Mi} + +env: + - name: LOG_LEVEL + value: "warn" + +autoscaling: + enabled: true + minReplicas: 3 + maxReplicas: 10 + +affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: [myapp] + topologyKey: kubernetes.io/hostname + +ingress: + enabled: true + className: nginx + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + hosts: + - host: myapp.example.com + tls: + - secretName: myapp-tls + hosts: + - myapp.example.com +``` + +**Deployment commands**: +```bash +# Development +helm install myapp . -f values-dev.yaml -n dev + +# Production +helm install myapp . -f values-prod.yaml -n production +``` + +--- + +## Example 5: Converting Manifest to Helm + +**Original Kubernetes manifest**: +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend +spec: + replicas: 2 + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + spec: + containers: + - name: frontend + image: myorg/frontend:1.0.0 + ports: + - containerPort: 8080 + resources: + limits: + cpu: "500m" + memory: "256Mi" +``` + +**Converted Helm template** (deployment.yaml): +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "frontend.fullname" . }} + labels: + {{- include "frontend.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "frontend.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "frontend.labels" . | nindent 8 }} + spec: + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + ports: + - name: http + containerPort: {{ .Values.service.targetPort }} + resources: + {{- toYaml .Values.resources | nindent 12 }} +``` + +**Extracted values.yaml**: +```yaml +replicaCount: 2 + +image: + repository: myorg/frontend + tag: "1.0.0" + +service: + targetPort: 8080 + +resources: + limits: + cpu: 500m + memory: 256Mi +``` + +**What was parameterized**: +- Replica count → `.Values.replicaCount` +- Image name/tag → `.Values.image.*` +- Port → `.Values.service.targetPort` +- Resources → `.Values.resources` +- Labels → Helper templates +- Resource name → Template function + +--- + +## Example 6: Organizational Standard Template + +**Scenario**: Platform team creates standard chart for all services + +**Required organizational standards**: +- All services must have cost center label +- Security scanning required +- Must use org-wide naming convention +- Mandatory resource limits +- Required network policies + +**Modified _helpers.tpl**: +```yaml +{{- define "org.labels" -}} +app.kubernetes.io/name: {{ include "chart.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +org.example.com/cost-center: {{ .Values.org.costCenter | required "Cost center is required" }} +org.example.com/team: {{ .Values.org.team | required "Team name is required" }} +org.example.com/security-scan: "required" +{{- end }} +``` + +**Required values**: +```yaml +org: + costCenter: "" # MUST be provided + team: "" # MUST be provided + +# Resource limits are mandatory +resources: + limits: + cpu: 500m + memory: 256Mi + requests: + cpu: 50m + memory: 64Mi + +# Security context is required +securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + capabilities: + drop: [ALL] +``` + +**Usage**: +```bash +helm install myapp . \ + --set org.costCenter=eng-001 \ + --set org.team=platform +``` + +--- + +## Example 7: With Subchart Dependency + +**Scenario**: Application that needs PostgreSQL database + +**Chart.yaml**: +```yaml +apiVersion: v2 +name: myapp +version: 0.1.0 +appVersion: "1.0.0" + +dependencies: + - name: postgresql + version: "12.1.0" + repository: "https://charts.bitnami.com/bitnami" + condition: postgresql.enabled +``` + +**values.yaml**: +```yaml +# Application values +replicaCount: 2 +image: + repository: myorg/myapp + tag: "1.0.0" + +# Subchart values +postgresql: + enabled: true + auth: + database: myapp + username: myapp + primary: + persistence: + size: 10Gi +``` + +**Install**: +```bash +# Download dependencies +helm dependency update + +# Install with subchart +helm install myapp . +``` + +--- + +## Common Patterns + +### ConfigMap from Files +```yaml +# values.yaml +config: + app.conf: | + server_port=8080 + log_level=info + db.conf: | + host=postgres + port=5432 + +# templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "app.fullname" . }} +data: + {{- range $key, $value := .Values.config }} + {{ $key }}: | + {{- $value | nindent 4 }} + {{- end }} +``` + +### External Secret Reference +```yaml +env: + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.externalSecret.name }} + key: password +``` + +### Horizontal Pod Autoscaler +```yaml +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "app.fullname" . }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "app.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} +{{- end }} +``` + +These examples demonstrate common patterns and can be adapted for specific use cases. diff --git a/skills/helm-scaffold/references/templates.md b/skills/helm-scaffold/references/templates.md new file mode 100644 index 0000000..6e7e5f5 --- /dev/null +++ b/skills/helm-scaffold/references/templates.md @@ -0,0 +1,306 @@ +# Helm Chart Templates + +Complete template library. Load sections as needed. {{ CN }} = {{ CHART_NAME }} (abbreviated). + +## Chart.yaml +```yaml +apiVersion: v2 +name: {{ CHART_NAME }} +description: A Helm chart for {{ APP_DESCRIPTION }} +type: application +version: 0.1.0 +appVersion: "1.0.0" +``` + +## values.yaml +```yaml +replicaCount: 1 +image: + repository: {{ IMAGE_REPO }} + pullPolicy: IfNotPresent + tag: "{{ IMAGE_TAG }}" +serviceAccount: + create: true + automount: true +podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 +securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: [ALL] +service: + type: ClusterIP + port: {{ SERVICE_PORT }} + targetPort: {{ CONTAINER_PORT }} +resources: + limits: {cpu: 500m, memory: 256Mi} + requests: {cpu: 50m, memory: 64Mi} +livenessProbe: + httpGet: {path: /health, port: http} + initialDelaySeconds: 30 +readinessProbe: + httpGet: {path: /ready, port: http} + initialDelaySeconds: 5 +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 +``` + +## _helpers.tpl +```yaml +{{- define "{{ CN }}.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "{{ CN }}.fullname" -}} +{{- if .Values.fullnameOverride }}{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }}{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }}{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }}{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}{{- end }}{{- end }}{{- end }} + +{{- define "{{ CN }}.labels" -}} +helm.sh/chart: {{ include "{{ CN }}.chart" . }} +{{ include "{{ CN }}.selectorLabels" . }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{- define "{{ CN }}.selectorLabels" -}} +app.kubernetes.io/name: {{ include "{{ CN }}.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{- define "{{ CN }}.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }}{{- default (include "{{ CN }}.fullname" .) .Values.serviceAccount.name }} +{{- else }}{{- default "default" .Values.serviceAccount.name }}{{- end }}{{- end }} +``` + +## Deployment +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "{{ CN }}.fullname" . }} + labels: {{- include "{{ CN }}.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }}replicas: {{ .Values.replicaCount }}{{- end }} + selector: + matchLabels: {{- include "{{ CN }}.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: {{- include "{{ CN }}.labels" . | nindent 8 }} + spec: + serviceAccountName: {{ include "{{ CN }}.serviceAccountName" . }} + securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.targetPort }} + livenessProbe: {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: {{- toYaml .Values.readinessProbe | nindent 12 }} + resources: {{- toYaml .Values.resources | nindent 12 }} +``` + +## StatefulSet +```yaml +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "{{ CN }}.fullname" . }} + labels: {{- include "{{ CN }}.labels" . | nindent 4 }} +spec: + serviceName: {{ include "{{ CN }}.fullname" . }}-headless + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: {{- include "{{ CN }}.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: {{- include "{{ CN }}.labels" . | nindent 8 }} + spec: + serviceAccountName: {{ include "{{ CN }}.serviceAccountName" . }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + volumeMounts: + - name: data + mountPath: /data + volumeClaimTemplates: + - metadata: {name: data} + spec: + accessModes: [{{ .Values.persistence.accessMode | quote }}] + storageClassName: {{ .Values.persistence.storageClass | quote }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} +``` +Add to values: `persistence: {storageClass: standard, accessMode: ReadWriteOnce, size: 10Gi}` + +## Job +```yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "{{ CN }}.fullname" . }} + labels: {{- include "{{ CN }}.labels" . | nindent 4 }} +spec: + backoffLimit: {{ .Values.job.backoffLimit | default 3 }} + completions: {{ .Values.job.completions | default 1 }} + template: + metadata: + labels: {{- include "{{ CN }}.labels" . | nindent 8 }} + spec: + restartPolicy: OnFailure + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" +``` +Add to values: `job: {backoffLimit: 3, completions: 1, parallelism: 1}` + +## CronJob +```yaml +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{ include "{{ CN }}.fullname" . }} + labels: {{- include "{{ CN }}.labels" . | nindent 4 }} +spec: + schedule: {{ .Values.cronjob.schedule | quote }} + successfulJobsHistoryLimit: {{ .Values.cronjob.successfulJobsHistoryLimit | default 3 }} + failedJobsHistoryLimit: {{ .Values.cronjob.failedJobsHistoryLimit | default 1 }} + jobTemplate: + spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" +``` +Add to values: `cronjob: {schedule: "0 2 * * *", successfulJobsHistoryLimit: 3, failedJobsHistoryLimit: 1}` + +## Service +```yaml +apiVersion: v1 +kind: Service +metadata: + name: {{ include "{{ CN }}.fullname" . }} + labels: {{- include "{{ CN }}.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + name: http + selector: {{- include "{{ CN }}.selectorLabels" . | nindent 4 }} +``` + +## ServiceAccount +```yaml +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "{{ CN }}.serviceAccountName" . }} + labels: {{- include "{{ CN }}.labels" . | nindent 4 }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} +``` + +## Ingress +```yaml +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "{{ CN }}.fullname" . }} + labels: {{- include "{{ CN }}.labels" . | nindent 4 }} +spec: + {{- if .Values.ingress.className }}ingressClassName: {{ .Values.ingress.className }}{{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: {name: {{ include "{{ CN }}.fullname" $ }}, port: {number: {{ $.Values.service.port }}}} + {{- end }} + {{- end }} +{{- end }} +``` + +## HPA +```yaml +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "{{ CN }}.fullname" . }} +spec: + scaleTargetRef: {apiVersion: apps/v1, kind: Deployment, name: {{ include "{{ CN }}.fullname" . }}} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: {name: cpu, target: {type: Utilization, averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}}} +{{- end }} +``` + +## NOTES.txt +``` +Thank you for installing {{ .Chart.Name }}! +Release: {{ .Release.Name }} +{{- if .Values.ingress.enabled }} +URL: http{{ if $.Values.ingress.tls }}s{{ end }}://{{ (index .Values.ingress.hosts 0).host }} +{{- else }} +Port forward: kubectl port-forward svc/{{ include "{{ CN }}.fullname" . }} {{ .Values.service.port }} +{{- end }} +``` + +## .helmignore +``` +.DS_Store +.git/ +*.swp +.idea/ +.vscode/ +``` + +## README.md +```markdown +# {{ CHART_NAME }} + +## Installation +helm install {{ CN }} . + +## Testing +helm lint . +helm template {{ CN }} . +helm install {{ CN }} . --dry-run +``` + +## Environment Values + +values-dev.yaml: +```yaml +replicaCount: 1 +resources: {limits: {cpu: 200m, memory: 128Mi}, requests: {cpu: 25m, memory: 32Mi}} +``` + +values-prod.yaml: +```yaml +replicaCount: 3 +resources: {limits: {cpu: 1000m, memory: 512Mi}, requests: {cpu: 100m, memory: 128Mi}} +autoscaling: {enabled: true, minReplicas: 3, maxReplicas: 10} +``` diff --git a/skills/helm-scaffold/references/testing-guide.md b/skills/helm-scaffold/references/testing-guide.md new file mode 100644 index 0000000..e18e3c2 --- /dev/null +++ b/skills/helm-scaffold/references/testing-guide.md @@ -0,0 +1,535 @@ +# Helm Chart Testing Guide + +## Overview + +This guide provides testing templates and commands for validating Helm charts before deployment. Always test charts with dry-run and template rendering before actual deployment. + +## Testing Workflow + +``` +1. Helm Lint → Validate chart structure +2. Helm Template → Preview rendered manifests +3. Dry-run Install → Test against cluster API +4. Kubectl Dry-run → Final validation +5. Actual Install → Deploy to cluster +``` + +## Mock Values by Workload Type + +### Deployment (Web Application) + +```yaml +# values-test.yaml +image: + repository: nginx + tag: "1.25" + pullPolicy: IfNotPresent + +replicaCount: 2 + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: true + className: "nginx" + hosts: + - host: test.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: test-tls + hosts: + - test.example.com + +resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + +livenessProbe: + enabled: true + path: /healthz + initialDelaySeconds: 30 + periodSeconds: 10 + +readinessProbe: + enabled: true + path: /ready + initialDelaySeconds: 5 + periodSeconds: 10 + +env: + - name: ENVIRONMENT + value: "test" + - name: LOG_LEVEL + value: "debug" + - name: PORT + value: "80" + +autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 5 + targetCPUUtilizationPercentage: 80 + +nodeSelector: {} +tolerations: [] +affinity: {} +``` + +### StatefulSet (Database) + +```yaml +# values-test.yaml +image: + repository: postgres + tag: "15" + pullPolicy: IfNotPresent + +replicaCount: 3 + +service: + type: ClusterIP + port: 5432 + +persistence: + enabled: true + storageClass: "standard" + size: 10Gi + +resources: + requests: + cpu: 250m + memory: 512Mi + limits: + cpu: 1000m + memory: 2Gi + +livenessProbe: + enabled: true + path: / + initialDelaySeconds: 60 + periodSeconds: 30 + +readinessProbe: + enabled: true + path: / + initialDelaySeconds: 10 + periodSeconds: 10 + +env: + - name: POSTGRES_DB + value: "testdb" + - name: POSTGRES_USER + value: "testuser" + - name: POSTGRES_PASSWORD + value: "testpass123" + - name: PGDATA + value: "/data/pgdata" + +podManagementPolicy: OrderedReady + +volumeMounts: + - name: data + mountPath: /data + +nodeSelector: {} +tolerations: [] +affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - postgres + topologyKey: kubernetes.io/hostname +``` + +### Job (Batch Processing) + +```yaml +# values-test.yaml +image: + repository: busybox + tag: "1.36" + pullPolicy: IfNotPresent + +job: + backoffLimit: 3 + activeDeadlineSeconds: 600 + ttlSecondsAfterFinished: 86400 + restartPolicy: OnFailure + parallelism: 1 + completions: 1 + command: + - /bin/sh + args: + - -c + - | + echo "Starting batch job..." + echo "Processing data..." + sleep 30 + echo "Job completed successfully!" + +resources: + requests: + cpu: 200m + memory: 256Mi + limits: + cpu: 1000m + memory: 1Gi + +env: + - name: JOB_TYPE + value: "batch-process" + - name: BATCH_SIZE + value: "1000" + - name: OUTPUT_PATH + value: "/output" + +nodeSelector: {} +tolerations: [] +``` + +### CronJob (Scheduled Task) + +```yaml +# values-test.yaml +image: + repository: busybox + tag: "1.36" + pullPolicy: IfNotPresent + +cronJob: + schedule: "*/5 * * * *" # Every 5 minutes for testing + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 1 + startingDeadlineSeconds: 200 + suspend: false + backoffLimit: 2 + activeDeadlineSeconds: 300 + restartPolicy: OnFailure + command: + - /bin/sh + args: + - -c + - | + echo "Running scheduled task at $(date)" + echo "Performing backup/cleanup/sync..." + sleep 10 + echo "Task completed at $(date)" + +resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + +env: + - name: TASK_NAME + value: "scheduled-backup" + - name: RETENTION_DAYS + value: "7" + - name: TARGET + value: "/backup" + +nodeSelector: {} +tolerations: [] +``` + +## Testing Commands + +### 1. Validate Chart Structure + +```bash +# Basic lint +helm lint my-chart + +# Lint with custom values +helm lint my-chart -f values-test.yaml + +# Lint with value overrides +helm lint my-chart --set image.tag=latest +``` + +**Expected output:** +``` +==> Linting my-chart +[INFO] Chart.yaml: icon is recommended +1 chart(s) linted, 0 chart(s) failed +``` + +### 2. Render Templates Locally + +```bash +# Render all templates +helm template my-release my-chart + +# Render with test values +helm template my-release my-chart -f values-test.yaml + +# Render with inline overrides +helm template my-release my-chart \ + --set image.tag=latest \ + --set replicaCount=3 + +# Show only specific resource +helm template my-release my-chart | grep -A 30 "kind: Deployment" + +# Save rendered manifests to file +helm template my-release my-chart -f values-test.yaml > rendered.yaml + +# Render for specific namespace +helm template my-release my-chart --namespace production +``` + +### 3. Dry-run Install + +```bash +# Dry-run against cluster API (requires cluster access) +helm install my-release my-chart --dry-run --debug + +# Dry-run with custom values +helm install my-release my-chart \ + --dry-run \ + --debug \ + -f values-test.yaml + +# Dry-run with namespace +helm install my-release my-chart \ + --dry-run \ + --debug \ + --namespace test \ + --create-namespace +``` + +**Expected behavior:** +- Validates against cluster API +- Shows what would be installed +- Catches API version issues +- Does NOT create resources + +### 4. Validate with Kubectl + +```bash +# Validate rendered manifests +helm template my-release my-chart | kubectl apply --dry-run=client -f - + +# Validate with specific values +helm template my-release my-chart -f values-test.yaml | kubectl apply --dry-run=client -f - + +# Server-side dry-run (requires cluster) +helm template my-release my-chart | kubectl apply --dry-run=server -f - +``` + +### 5. Diff Against Existing Release + +```bash +# Install helm diff plugin first +helm plugin install https://github.com/databus23/helm-diff + +# Compare with existing release +helm diff upgrade my-release my-chart -f values-test.yaml + +# Show only changes +helm diff upgrade my-release my-chart -f values-test.yaml --suppress-secrets +``` + +## Testing Checklist + +### Pre-deployment Validation + +- [ ] Chart passes `helm lint` without warnings +- [ ] Templates render successfully with `helm template` +- [ ] Dry-run install completes without errors +- [ ] Image names are valid and accessible +- [ ] Resource limits are appropriate +- [ ] Labels and selectors match correctly +- [ ] Health check paths are correct +- [ ] Environment variables are set +- [ ] Secrets are not in values.yaml +- [ ] Ingress hostnames are valid +- [ ] Service ports match container ports + +### Workload-Specific Checks + +#### Deployment +- [ ] Replica count is appropriate +- [ ] Rolling update strategy configured +- [ ] HPA settings (if enabled) are sensible +- [ ] Pod disruption budget considered + +#### StatefulSet +- [ ] Persistence configuration correct +- [ ] Storage class exists +- [ ] Headless service defined +- [ ] Pod management policy appropriate +- [ ] Volume claim templates valid + +#### Job +- [ ] Backoff limit reasonable +- [ ] Active deadline set +- [ ] TTL for cleanup configured +- [ ] Restart policy appropriate +- [ ] Command/args correct + +#### CronJob +- [ ] Schedule syntax valid +- [ ] Concurrency policy appropriate +- [ ] History limits set +- [ ] Starting deadline configured +- [ ] Job template valid + +## Common Testing Scenarios + +### Test 1: Minimal Values (Defaults) + +```bash +# Test with only required values +helm template test my-chart --set image.repository=nginx +``` + +### Test 2: Production-like Values + +```bash +# Test with production configuration +helm template test my-chart -f values-prod.yaml +``` + +### Test 3: Multiple Environments + +```bash +# Test dev environment +helm template test my-chart -f values-dev.yaml + +# Test staging environment +helm template test my-chart -f values-staging.yaml + +# Test production environment +helm template test my-chart -f values-prod.yaml +``` + +### Test 4: Value Overrides + +```bash +# Test with inline overrides +helm template test my-chart \ + --set image.tag=v2.0.0 \ + --set replicaCount=5 \ + --set ingress.enabled=true +``` + +### Test 5: Resource Validation + +```bash +# Check resource limits +helm template test my-chart | grep -A 5 "resources:" + +# Check security contexts +helm template test my-chart | grep -A 10 "securityContext:" + +# Check probes +helm template test my-chart | grep -A 5 "Probe:" +``` + +## Troubleshooting + +### Issue: Template rendering fails + +```bash +# Debug with verbose output +helm template test my-chart --debug + +# Check specific template +helm template test my-chart --show-only templates/deployment.yaml +``` + +### Issue: Validation errors + +```bash +# Validate individual resources +helm template test my-chart | kubectl apply --dry-run=client -f - --validate=strict + +# Check for deprecated APIs +helm template test my-chart | kubectl apply --dry-run=server -f - +``` + +### Issue: Values not applied + +```bash +# Verify values are loaded +helm template test my-chart -f values-test.yaml --debug | grep -A 5 "USER-SUPPLIED VALUES" + +# Check final computed values +helm template test my-chart -f values-test.yaml --debug | grep -A 20 "COMPUTED VALUES" +``` + +## Best Practices + +1. **Always test before deploying** - Use dry-run and template rendering +2. **Use realistic test data** - Mock values should resemble production +3. **Test all environments** - Validate dev, staging, prod configurations +4. **Validate security** - Check security contexts and RBAC +5. **Check resource limits** - Ensure requests/limits are appropriate +6. **Test failure scenarios** - Invalid values, missing fields +7. **Document test process** - Share testing commands with team +8. **Automate testing** - Include in CI/CD pipeline + +## Automated Testing Example + +```bash +#!/bin/bash +# test-chart.sh + +set -e + +CHART_DIR="my-chart" +VALUES_FILE="values-test.yaml" + +echo "Testing Helm chart: $CHART_DIR" + +# Test 1: Lint +echo "1. Running helm lint..." +helm lint $CHART_DIR -f $VALUES_FILE + +# Test 2: Template rendering +echo "2. Rendering templates..." +helm template test $CHART_DIR -f $VALUES_FILE > /tmp/rendered.yaml + +# Test 3: Kubectl validation +echo "3. Validating with kubectl..." +kubectl apply --dry-run=client -f /tmp/rendered.yaml + +# Test 4: Check for secrets in values +echo "4. Checking for secrets in values..." +if grep -i "password\|secret\|token" $VALUES_FILE; then + echo "WARNING: Potential secrets found in values file!" +fi + +echo "✅ All tests passed!" +``` + +## Summary + +Always follow this testing sequence: +1. **Lint** the chart structure +2. **Render** templates to preview +3. **Dry-run** to validate against API +4. **Review** rendered manifests +5. **Deploy** to test environment first +6. **Monitor** after deployment + +Never skip testing, even for "small changes"! diff --git a/skills/helm-scaffold/references/workload-types.md b/skills/helm-scaffold/references/workload-types.md new file mode 100644 index 0000000..81ec18d --- /dev/null +++ b/skills/helm-scaffold/references/workload-types.md @@ -0,0 +1,322 @@ +# Helm Chart Workload Types + +## Overview +Different application types require different Kubernetes workload resources. This guide helps choose the right workload type and understand its specific requirements. + +## Workload Type Decision Tree + +``` +Is it a long-running process? +├─ YES: Does it need stable network identity or persistent storage? +│ ├─ YES: Use StatefulSet +│ └─ NO: Use Deployment +└─ NO: Is it scheduled to run repeatedly? + ├─ YES: Use CronJob + └─ NO: Use Job +``` + +## Deployment + +**Use for:** Stateless applications, microservices, web servers, APIs + +### Characteristics +- No persistent identity +- Pods are interchangeable +- Can be scaled horizontally +- Rolling updates and rollbacks +- No guaranteed ordering + +### Best For +- REST APIs +- Web applications +- Microservices +- Stateless workers +- Frontend applications + +### values.yaml Specific Fields +```yaml +replicaCount: 3 +strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + +autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 +``` + +### Example Use Cases +- Node.js API server +- Nginx web server +- Python Flask application +- React/Vue frontend +- Stateless background workers + +## StatefulSet + +**Use for:** Stateful applications requiring stable identity or persistent storage + +### Characteristics +- Stable, unique network identifiers +- Stable, persistent storage +- Ordered, graceful deployment and scaling +- Ordered, automated rolling updates +- Requires headless service + +### Best For +- Databases (MySQL, PostgreSQL, MongoDB) +- Message queues (RabbitMQ, Kafka) +- Distributed systems (Elasticsearch, Cassandra) +- Applications requiring stable hostnames + +### values.yaml Specific Fields +```yaml +replicaCount: 3 +updateStrategy: + type: RollingUpdate + +persistence: + enabled: true + storageClass: "fast-ssd" + size: 10Gi + accessMode: ReadWriteOnce + +podManagementPolicy: OrderedReady # or Parallel +``` + +### Template Additions +```yaml +serviceName: {{ include "chart.fullname" . }}-headless +volumeClaimTemplates: +- metadata: + name: data + spec: + accessModes: [ "ReadWriteOnce" ] + storageClassName: {{ .Values.persistence.storageClass }} + resources: + requests: + storage: {{ .Values.persistence.size }} +``` + +### Headless Service Required +```yaml +apiVersion: v1 +kind: Service +metadata: + name: {{ include "chart.fullname" . }}-headless +spec: + clusterIP: None + selector: + {{- include "chart.selectorLabels" . | nindent 4 }} +``` + +### Example Use Cases +- PostgreSQL cluster +- Redis with persistent storage +- Elasticsearch cluster +- Kafka broker +- ZooKeeper ensemble + +## Job + +**Use for:** Run-to-completion tasks, one-time executions + +### Characteristics +- Runs until completion +- Pods are not restarted after successful completion +- Can run multiple pods in parallel +- Automatic cleanup options +- Suitable for batch processing + +### Best For +- Database migrations +- Batch processing +- Data imports/exports +- One-time setup tasks +- Report generation + +### values.yaml Specific Fields +```yaml +job: + backoffLimit: 4 # Retry attempts + activeDeadlineSeconds: 600 # Timeout + ttlSecondsAfterFinished: 86400 # Cleanup after 24h + restartPolicy: OnFailure # or Never + parallelism: 1 # Parallel pods + completions: 1 # Required completions + command: [] + args: [] +``` + +### Template Specifics +```yaml +spec: + backoffLimit: {{ .Values.job.backoffLimit }} + ttlSecondsAfterFinished: {{ .Values.job.ttlSecondsAfterFinished }} + template: + spec: + restartPolicy: {{ .Values.job.restartPolicy }} +``` + +### Example Use Cases +- Database schema migration +- Data ETL job +- Image processing batch +- Cache warming +- Backup operations + +## CronJob + +**Use for:** Scheduled, recurring tasks + +### Characteristics +- Scheduled execution (cron syntax) +- Creates Jobs on schedule +- Concurrency control +- History management +- Automatic cleanup + +### Best For +- Scheduled backups +- Report generation +- Data synchronization +- Cache clearing +- Periodic cleanup tasks + +### values.yaml Specific Fields +```yaml +cronJob: + schedule: "0 2 * * *" # Daily at 2 AM + concurrencyPolicy: Forbid # Forbid, Allow, or Replace + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 1 + startingDeadlineSeconds: 200 # Deadline for missed runs + suspend: false # Pause scheduling + backoffLimit: 4 + activeDeadlineSeconds: 600 + restartPolicy: OnFailure + command: [] + args: [] +``` + +### Cron Schedule Examples +```yaml +"*/5 * * * *" # Every 5 minutes +"0 * * * *" # Every hour +"0 0 * * *" # Daily at midnight +"0 2 * * *" # Daily at 2 AM +"0 0 * * 0" # Weekly on Sunday +"0 0 1 * *" # Monthly on 1st +"0 0 1 1 *" # Yearly on Jan 1st +``` + +### Concurrency Policies +- **Forbid**: Don't start new job if previous still running (recommended) +- **Allow**: Allow concurrent jobs +- **Replace**: Cancel running job and start new one + +### Example Use Cases +- Nightly database backup +- Daily report generation +- Hourly cache refresh +- Weekly cleanup tasks +- Monthly billing runs + +## Comparison Matrix + +| Feature | Deployment | StatefulSet | Job | CronJob | +|---------|-----------|-------------|-----|---------| +| **Replicas** | Yes | Yes | Parallelism | Parallelism | +| **Persistent Identity** | No | Yes | No | No | +| **Persistent Storage** | Optional | Yes | Optional | Optional | +| **Ordered Operations** | No | Yes | No | No | +| **Auto-restart** | Yes | Yes | Optional | Optional | +| **Scaling** | Easy | Ordered | N/A | N/A | +| **Updates** | Rolling | Rolling | N/A | N/A | +| **Service** | Yes | Headless | Optional | Optional | +| **Typical Replicas** | 2-100+ | 1-10 | 1-1000 | 1-100 | + +## When to Use Each Type + +### Use Deployment When: +- Application is stateless +- Pods are interchangeable +- No need for stable network identity +- Need rapid scaling +- Standard web application pattern + +### Use StatefulSet When: +- Need stable, unique network identifiers +- Require persistent storage per pod +- Need ordered deployment/scaling +- Running clustered databases +- Pods need to discover each other + +### Use Job When: +- Task runs to completion +- One-time execution needed +- Batch processing work +- Database migration +- Don't need scheduling + +### Use CronJob When: +- Need scheduled execution +- Recurring tasks +- Time-based triggers +- Periodic maintenance +- Regular backups or reports + +## Migration Considerations + +### Deployment → StatefulSet +**Required Changes:** +- Add `serviceName` pointing to headless service +- Create headless service +- Add `volumeClaimTemplates` +- Update selector labels (StatefulSet selector is immutable) +- Plan for ordered rollout + +### StatefulSet → Deployment +**Consider:** +- Loss of stable network identity +- Need to externalize persistent data +- Pods become interchangeable +- No ordered operations + +## Resource Recommendations by Type + +### Deployment +```yaml +resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "512Mi" + cpu: "500m" +``` + +### StatefulSet +```yaml +resources: + requests: + memory: "512Mi" # Higher for databases + cpu: "250m" + limits: + memory: "2Gi" + cpu: "1000m" +``` + +### Job/CronJob +```yaml +resources: + requests: + memory: "256Mi" + cpu: "200m" + limits: + memory: "1Gi" + cpu: "1000m" +``` diff --git a/skills/helm-scaffold/scripts/scaffold_chart.py b/skills/helm-scaffold/scripts/scaffold_chart.py new file mode 100755 index 0000000..e146a16 --- /dev/null +++ b/skills/helm-scaffold/scripts/scaffold_chart.py @@ -0,0 +1,234 @@ +#!/usr/bin/env python3 +""" +Helm Chart Scaffolding Script +Generates production-ready Helm charts with best practices +""" + +import os +import shutil +import argparse +from pathlib import Path +from typing import Dict, List, Optional + + +def replace_placeholder(content: str, chart_name: str) -> str: + """Replace CHARTNAME placeholder with actual chart name""" + return content.replace("CHARTNAME", chart_name) + + +def create_chart_structure( + chart_name: str, + output_dir: str, + workload_type: str = "deployment", + include_ingress: bool = False, + include_hpa: bool = False, + include_configmap: bool = False, +) -> None: + """ + Create Helm chart directory structure and files + + Args: + chart_name: Name of the Helm chart + output_dir: Directory where chart will be created + workload_type: Type of workload (deployment, statefulset, job, cronjob) + include_ingress: Whether to include Ingress resource + include_hpa: Whether to include HorizontalPodAutoscaler + include_configmap: Whether to include ConfigMap + """ + # Get templates directory + script_dir = Path(__file__).parent.parent + templates_dir = script_dir / "assets" / "templates" + + # Create chart directory + chart_dir = Path(output_dir) / chart_name + chart_dir.mkdir(parents=True, exist_ok=True) + + # Create templates subdirectory + templates_output_dir = chart_dir / "templates" + templates_output_dir.mkdir(exist_ok=True) + + print(f"📦 Creating Helm chart: {chart_name}") + print(f"📂 Output directory: {chart_dir}") + + # Copy Chart.yaml + chart_yaml_src = templates_dir / "Chart.yaml" + chart_yaml_dst = chart_dir / "Chart.yaml" + with open(chart_yaml_src, 'r') as f: + content = replace_placeholder(f.read(), chart_name) + with open(chart_yaml_dst, 'w') as f: + f.write(content) + print("✅ Created Chart.yaml") + + # Copy values.yaml + values_yaml_src = templates_dir / "values.yaml" + values_yaml_dst = chart_dir / "values.yaml" + shutil.copy2(values_yaml_src, values_yaml_dst) + print("✅ Created values.yaml") + + # Copy .helmignore + helmignore_src = templates_dir / ".helmignore" + helmignore_dst = chart_dir / ".helmignore" + shutil.copy2(helmignore_src, helmignore_dst) + print("✅ Created .helmignore") + + # Copy _helpers.tpl + helpers_src = templates_dir / "_helpers.tpl" + helpers_dst = templates_output_dir / "_helpers.tpl" + with open(helpers_src, 'r') as f: + content = replace_placeholder(f.read(), chart_name) + with open(helpers_dst, 'w') as f: + f.write(content) + print("✅ Created templates/_helpers.tpl") + + # Copy NOTES.txt + notes_src = templates_dir / "NOTES.txt" + notes_dst = templates_output_dir / "NOTES.txt" + with open(notes_src, 'r') as f: + content = replace_placeholder(f.read(), chart_name) + with open(notes_dst, 'w') as f: + f.write(content) + print("✅ Created templates/NOTES.txt") + + # Copy workload type template + workload_templates = { + "deployment": "deployment/deployment.yaml", + "statefulset": "statefulset/statefulset.yaml", + "job": "job/job.yaml", + "cronjob": "cronjob/cronjob.yaml", + } + + workload_src = templates_dir / workload_templates[workload_type] + workload_dst = templates_output_dir / f"{workload_type}.yaml" + with open(workload_src, 'r') as f: + content = replace_placeholder(f.read(), chart_name) + with open(workload_dst, 'w') as f: + f.write(content) + print(f"✅ Created templates/{workload_type}.yaml") + + # Copy Service (not for jobs) + if workload_type in ["deployment", "statefulset"]: + service_src = templates_dir / "service" / "service.yaml" + service_dst = templates_output_dir / "service.yaml" + with open(service_src, 'r') as f: + content = replace_placeholder(f.read(), chart_name) + with open(service_dst, 'w') as f: + f.write(content) + print("✅ Created templates/service.yaml") + + # Copy ServiceAccount + sa_src = templates_dir / "rbac" / "serviceaccount.yaml" + sa_dst = templates_output_dir / "serviceaccount.yaml" + with open(sa_src, 'r') as f: + content = replace_placeholder(f.read(), chart_name) + with open(sa_dst, 'w') as f: + f.write(content) + print("✅ Created templates/serviceaccount.yaml") + + # Optionally copy Ingress + if include_ingress: + ingress_src = templates_dir / "ingress" / "ingress.yaml" + ingress_dst = templates_output_dir / "ingress.yaml" + with open(ingress_src, 'r') as f: + content = replace_placeholder(f.read(), chart_name) + with open(ingress_dst, 'w') as f: + f.write(content) + print("✅ Created templates/ingress.yaml") + + # Optionally copy HPA (only for deployment) + if include_hpa and workload_type == "deployment": + hpa_src = templates_dir / "hpa" / "hpa.yaml" + hpa_dst = templates_output_dir / "hpa.yaml" + with open(hpa_src, 'r') as f: + content = replace_placeholder(f.read(), chart_name) + with open(hpa_dst, 'w') as f: + f.write(content) + print("✅ Created templates/hpa.yaml") + + # Optionally copy ConfigMap + if include_configmap: + cm_src = templates_dir / "configmap" / "configmap.yaml" + cm_dst = templates_output_dir / "configmap.yaml" + with open(cm_src, 'r') as f: + content = replace_placeholder(f.read(), chart_name) + with open(cm_dst, 'w') as f: + f.write(content) + print("✅ Created templates/configmap.yaml") + + print(f"\n🎉 Chart '{chart_name}' created successfully at {chart_dir}") + print(f"\nNext steps:") + print(f"1. cd {chart_dir}") + print(f"2. Edit values.yaml to configure your application") + print(f"3. Run: helm lint .") + print(f"4. Run: helm install {chart_name} .") + + +def main(): + parser = argparse.ArgumentParser( + description="Generate production-ready Helm charts", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + # Create basic deployment chart + %(prog)s my-app -o ./charts + + # Create statefulset with ingress + %(prog)s my-db -o ./charts -t statefulset --ingress + + # Create job with configmap + %(prog)s my-job -o ./charts -t job --configmap + + # Create deployment with HPA + %(prog)s my-api -o ./charts --hpa + """ + ) + + parser.add_argument( + "chart_name", + help="Name of the Helm chart to create" + ) + + parser.add_argument( + "-o", "--output", + default=".", + help="Output directory (default: current directory)" + ) + + parser.add_argument( + "-t", "--type", + choices=["deployment", "statefulset", "job", "cronjob"], + default="deployment", + help="Workload type (default: deployment)" + ) + + parser.add_argument( + "--ingress", + action="store_true", + help="Include Ingress resource" + ) + + parser.add_argument( + "--hpa", + action="store_true", + help="Include HorizontalPodAutoscaler (deployment only)" + ) + + parser.add_argument( + "--configmap", + action="store_true", + help="Include ConfigMap resource" + ) + + args = parser.parse_args() + + create_chart_structure( + chart_name=args.chart_name, + output_dir=args.output, + workload_type=args.type, + include_ingress=args.ingress, + include_hpa=args.hpa, + include_configmap=args.configmap, + ) + + +if __name__ == "__main__": + main()