logo
0
0
WeChat Login
docs: update README.zh.md with new Deno installation and run commands, removing short link section

deno-manifest

A GitOps tool that aggregates all default TypeScript exports from a directory into a Kubernetes resource list (JSON format) for streamlined GitOps workflows.

WARNING

Due to the limitation of deno's dynamic import, this project uses child process to evaluate .ts files.

Features

  • 🔄 Automatic Discovery: Recursively finds all .ts and .mts files in your project
  • 📦 Smart Aggregation: Combines default exports into a single Kubernetes List resource
  • 🚀 Function Support: Automatically calls exported functions to generate dynamic resources
  • 🎯 GitOps Ready: Outputs valid Kubernetes manifests in JSON format for direct use in GitOps workflows
  • 🛡️ Safe Filtering: Excludes test files, definition files, and hidden files automatically

Why TypeScript?

Unlike other manifest generation approaches like Jsonnet, Kustomize, or plain YAML templating, our TypeScript-based solution offers significant advantages:

🎯 Type Safety with Official Kubernetes Types

  • Leverage official Kubernetes NPM packages (e.g., @kubernetes/client-node) for complete type definitions
  • Get compile-time validation of resource schemas
  • Catch configuration errors before deployment, not during runtime

💡 Superior Developer Experience

  • IDE Intelligence: Full IntelliSense support with auto-completion for all Kubernetes resource fields
  • Immediate Feedback: See errors as you type - misspelled fields, wrong types, or invalid configurations are highlighted instantly
  • Documentation at Your Fingertips: Hover over any field to see its documentation from the official Kubernetes API

🔧 Powerful Language Features

  • Use the full power of TypeScript: conditionals, loops, functions, and modules
  • Import and reuse common configurations across manifests
  • Leverage npm ecosystem for utility libraries
  • Write unit tests for your manifest generation logic

📝 Example: Type Safety in Action

With TypeScript and Kubernetes types:

import { V1Deployment } from "@kubernetes/client-node"; // TypeScript catches errors immediately! const deployment: V1Deployment = { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "my-app", // TypeScript error: 'lables' does not exist. Did you mean 'labels'? lables: { app: "my-app" }, }, spec: { // TypeScript error: Type 'string' is not assignable to type 'number' replicas: "3", selector: { matchLabels: { app: "my-app" }, }, template: { // Full auto-completion for all nested fields! metadata: { labels: { app: "my-app" } }, spec: { containers: [ { name: "app", image: "my-app:latest", // TypeScript knows this should be an array of objects ports: [{ containerPort: 8080 }], }, ], }, }, }, };

Compare this to Jsonnet or plain YAML where:

  • ❌ No immediate feedback on typos or wrong field names
  • ❌ No type checking for values (strings vs numbers vs objects)
  • ❌ No auto-completion or IntelliSense
  • ❌ Errors only discovered at deployment time

Installation

# Install directly from source deno install -A --name deno-manifest https://raw.githubusercontent.com/yankeguo/deno-manifest/main/main.ts # Or run directly without installation deno run -A https://raw.githubusercontent.com/yankeguo/deno-manifest/main/main.ts # Or use my short link deno run -A https://gyk.me/r/deno-manifest.ts

Usage

Basic Usage

Navigate to your directory containing TypeScript files and run:

# Output JSON to stdout deno-manifest # Save JSON output to file deno-manifest > manifests.json # Convert to YAML if needed (requires yq) deno-manifest | yq eval -P > manifests.yaml

Example Project Structure

my-app/ ├── deployment.ts ├── service.ts ├── configmap.ts ├── components/ │ ├── redis.ts │ └── postgres.ts └── _test.ts # Ignored (starts with _)

Example TypeScript Files

deployment.ts:

export default { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "my-app", namespace: "default", }, spec: { replicas: 3, selector: { matchLabels: { app: "my-app" }, }, template: { metadata: { labels: { app: "my-app" }, }, spec: { containers: [ { name: "app", image: "my-app:latest", ports: [{ containerPort: 8080 }], }, ], }, }, }, };

service.ts:

export default { apiVersion: "v1", kind: "Service", metadata: { name: "my-app-service", namespace: "default", }, spec: { selector: { app: "my-app" }, ports: [ { port: 80, targetPort: 8080, }, ], type: "ClusterIP", }, };

configmap.ts (Dynamic generation):

export default function () { return { apiVersion: "v1", kind: "ConfigMap", metadata: { name: "my-app-config", namespace: "default", }, data: { "config.yaml": ` env: ${Deno.env.get("NODE_ENV") || "development"} timestamp: ${new Date().toISOString()} `.trim(), }, }; }

components/redis.ts (Multiple resources):

const namespace = "default"; export default [ { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: "redis", namespace, }, spec: { replicas: 1, selector: { matchLabels: { app: "redis" } }, template: { metadata: { labels: { app: "redis" } }, spec: { containers: [ { name: "redis", image: "redis:7-alpine", ports: [{ containerPort: 6379 }], }, ], }, }, }, }, { apiVersion: "v1", kind: "Service", metadata: { name: "redis", namespace, }, spec: { selector: { app: "redis" }, ports: [{ port: 6379, targetPort: 6379 }], }, }, ];

Generated Output

Running deno-manifest on the above structure produces:

{ "apiVersion": "v1", "kind": "List", "items": [ { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "my-app", "namespace": "default" }, "spec": { "replicas": 3, "selector": { "matchLabels": { "app": "my-app" } }, "template": { "metadata": { "labels": { "app": "my-app" } }, "spec": { "containers": [ { "name": "app", "image": "my-app:latest", "ports": [{ "containerPort": 8080 }] } ] } } } }, { "apiVersion": "v1", "kind": "Service", "metadata": { "name": "my-app-service", "namespace": "default" }, "spec": { "selector": { "app": "my-app" }, "ports": [ { "port": 80, "targetPort": 8080 } ], "type": "ClusterIP" } }, { "apiVersion": "v1", "kind": "ConfigMap", "metadata": { "name": "my-app-config", "namespace": "default" }, "data": { "config.yaml": "env: development\ntimestamp: 2024-01-15T10:30:00.000Z" } }, { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "redis", "namespace": "default" }, "spec": { "replicas": 1, "selector": { "matchLabels": { "app": "redis" } }, "template": { "metadata": { "labels": { "app": "redis" } }, "spec": { "containers": [ { "name": "redis", "image": "redis:7-alpine", "ports": [{ "containerPort": 6379 }] } ] } } } }, { "apiVersion": "v1", "kind": "Service", "metadata": { "name": "redis", "namespace": "default" }, "spec": { "selector": { "app": "redis" }, "ports": [{ "port": 6379, "targetPort": 6379 }] } } ] }

Use Cases

1. GitOps Workflows

# Generate manifests and apply to cluster (JSON format) deno-manifest | kubectl apply -f - # Save JSON to file for GitOps repository deno-manifest > k8s-manifests.json git add k8s-manifests.json git commit -m "feat: update kubernetes manifests" # Convert to YAML if your GitOps workflow prefers YAML deno-manifest | yq eval -P > k8s-manifests.yaml

2. Environment-Specific Configurations

Create different directories for different environments:

environments/ ├── development/ │ ├── deployment.ts │ └── service.ts ├── staging/ │ ├── deployment.ts │ └── service.ts └── production/ ├── deployment.ts └── service.ts
# Generate manifests for specific environment cd environments/production deno-manifest > ../../manifests/production.json

3. Dynamic Resource Generation

Use TypeScript's power for dynamic configurations:

// scaling.ts export default function () { const replicas = Deno.env.get("REPLICAS") || "3"; const environment = Deno.env.get("ENVIRONMENT") || "development"; return { apiVersion: "apps/v1", kind: "Deployment", metadata: { name: `app-${environment}`, labels: { environment, "managed-by": "deno-manifest", }, }, spec: { replicas: parseInt(replicas), // ... rest of deployment spec }, }; }

File Patterns

Included Files

  • *.ts - TypeScript files
  • *.mts - TypeScript module files

Excluded Files

  • Files starting with . or _ (hidden/private files)
  • *.d.ts, *.d.mts - TypeScript definition files
  • *_test.ts, *_test.mts - Test files
  • Files in directories starting with ., _, or node_modules

Export Types

Object Export

export default { /* Kubernetes resource */ };

Function Export (Evaluated at runtime)

export default function () { return { /* Kubernetes resource */ }; }

Array Export (Items are spread into the list)

export default [ { /* Resource 1 */ }, { /* Resource 2 */ }, ];

ArgoCD Integration

deno-manifest integrates seamlessly with ArgoCD as a Config Management Plugin (CMP), enabling you to use TypeScript for generating Kubernetes manifests in your GitOps workflows.

How It Works

ArgoCD automatically detects TypeScript files in your repository and uses deno-manifest to generate Kubernetes resources. No manual configuration or plugin name specification required - it just works!

Setup

  1. Create the plugin configuration:
# argocd-cm-plugin.yaml apiVersion: v1 kind: ConfigMap metadata: name: deno-manifest-plugin namespace: argocd data: plugin.yaml: | apiVersion: argoproj.io/v1alpha1 kind: ConfigManagementPlugin metadata: name: deno-manifest spec: discover: find: glob: "**/*.ts" generate: command: - deno - run - -A - https://raw.githubusercontent.com/yankeguo/deno-manifest/main/main.ts
  1. Patch the ArgoCD repo server to add the plugin sidecar:
# argocd-repo-server-patch.yaml apiVersion: apps/v1 kind: Deployment metadata: name: argocd-repo-server namespace: argocd spec: template: spec: containers: - name: deno-manifest-plugin image: denoland/deno:latest command: [/var/run/argocd/argocd-cmp-server] securityContext: runAsNonRoot: true runAsUser: 999 allowPrivilegeEscalation: false capabilities: drop: - ALL readOnlyRootFilesystem: true seccompProfile: type: RuntimeDefault volumeMounts: - mountPath: /var/run/argocd name: var-files - mountPath: /home/argocd/cmp-server/plugins name: plugins - mountPath: /home/argocd/cmp-server/config/plugin.yaml subPath: plugin.yaml name: deno-manifest-plugin - mountPath: /tmp name: tmp volumes: - configMap: name: deno-manifest-plugin name: deno-manifest-plugin - emptyDir: {} name: tmp
  1. Apply the configuration:
kubectl apply -f argocd-cm-plugin.yaml kubectl apply -f argocd-repo-server-patch.yaml # Restart repo server to load the plugin kubectl rollout restart deployment/argocd-repo-server -n argocd

Usage with ArgoCD Applications

Once the plugin is installed, ArgoCD will automatically use deno-manifest for any repository containing TypeScript files:

# example-app.yaml apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: my-app namespace: argocd spec: project: default source: repoURL: https://github.com/yourorg/your-repo targetRevision: main path: manifests/ # Directory containing your .ts files # No plugin specification needed - auto-discovered! destination: server: https://kubernetes.default.svc namespace: default syncPolicy: automated: prune: true selfHeal: true

Environment Variables

You can pass environment variables to your TypeScript manifests through ArgoCD:

apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: my-app spec: source: plugin: env: - name: ENVIRONMENT value: production - name: REPLICAS value: "5"

Advanced: Using a Short URL

For convenience, you can use the short URL in your plugin configuration:

generate: command: - deno - run - -A - https://gyk.me/r/deno-manifest.ts

Benefits

  • Zero Configuration: Automatic discovery of TypeScript files
  • Native Integration: Uses official Deno image, no custom builds required
  • Dynamic Manifests: Full TypeScript runtime for generating manifests
  • GitOps Ready: Outputs standard Kubernetes resources compatible with ArgoCD
  • Environment Support: Access environment variables for dynamic configurations

Credits

GUO YANKE, MIT License

About

No description, topics, or website provided.
Language
Markdown88.9%
TypeScript11.1%