Skip to content

About

Kubernetes Aggregated API Server where CRUD operations are implemented as shell scripts

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Shell API Server

A Kubernetes Aggregated API Server where CRUD operations are implemented as shell scripts. No etcd, no in-memory store — scripts read from and write to reality directly.

kubectl get dockercontainer my-app
        ↓
   shell-apiserver
        ↓
   read.sh my-app
        ↓
   docker inspect my-app
        ↓
   JSON response

Features

  • No storage backend — reality is the source of truth
  • Shell-based handlers — CRUD operations are plain shell scripts
  • Standard kubectl — get, describe, delete, watch, -o json/yaml
  • Full RBAC — standard ClusterRole/RoleBinding
  • OpenAPI/SSA — generated from CRD schemas
  • Watch support — streaming events via long-running scripts

Quick Start

Prerequisites

  • Go 1.23+
  • Docker (for the example provider)

Run Locally

# Build and start the server
./hack/run-local.sh

# In another terminal, use the generated kubeconfig
export KUBECONFIG=$(pwd)/.kubeconfig

# List Docker containers as Kubernetes resources
kubectl get dockercontainers

# Get a specific container
kubectl get dockercontainer <container-name> -o yaml

Example Output

$ kubectl get dockercontainers
NAME                   CREATED AT
my-nginx               2025-01-15T10:30:00Z
my-postgres            2025-01-15T09:00:00Z

$ kubectl get dockercontainer my-nginx -o yaml
apiVersion: shell.io/v1
kind: DockerContainer
metadata:
  name: my-nginx
  namespace: default
spec:
  image: nginx:latest
  command: ["nginx", "-g", "daemon off;"]
status:
  phase: Running
  containerId: abc123...

Architecture

┌─────────────────────────────────────────────────┐
│               kube-apiserver                    │
│         (aggregation layer)                     │
└──────────────────┬──────────────────────────────┘
                   │  APIService registration
                   ▼
┌─────────────────────────────────────────────────┐
│           shell-apiserver (Go)                  │
│                                                 │
│  HTTP verb → shell executor → runtime.Object    │
│                                                 │
│  GET    /resource/:name  →  read.sh   <name>   │
│  GET    /resource        →  list.sh            │
│  POST   /resource        →  create.sh  (stdin) │
│  PUT    /resource/:name  →  update.sh  (stdin) │
│  DELETE /resource/:name  →  delete.sh <name>   │
│  GET    ?watch=true      →  watch.sh           │
└──────────────────┬──────────────────────────────┘
                   │
        ┌──────────▼──────────┐
        │   provider scripts  │
        └─────────────────────┘

Creating a Provider

Each provider is a directory containing a CRD schema and shell scripts:

providers/myresource/
├── schema.yaml     # CRD manifest (defines the resource)
├── create.sh       # stdin: JSON → stdout: JSON
├── read.sh         # args: <name> → stdout: JSON
├── list.sh         # stdout: JSON list
├── update.sh       # args: <name>, stdin: JSON → stdout: JSON
├── delete.sh       # args: <name> → exit 0 or error
└── watch.sh        # stdout: stream of JSON events (optional)

schema.yaml

Standard Kubernetes CRD format:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: myresources.example.io
spec:
  group: example.io
  names:
    kind: MyResource
    plural: myresources
    singular: myresource
  scope: Namespaced
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                field1:
                  type: string
            status:
              type: object
              properties:
                phase:
                  type: string

Exit Codes

Code HTTP Status Meaning
0 200/201 Success
1 500 Generic error
2 404 Not found
3 409 Conflict
4 422 Invalid input

Environment Variables

Scripts receive these environment variables:

Variable Description
NAMESPACE Kubernetes namespace (for namespaced resources)

Example: read.sh

#!/bin/bash
NAME="$1"
docker inspect "$NAME" --format '{{json .}}' | jq '{
  apiVersion: "shell.io/v1",
  kind: "DockerContainer",
  metadata: {
    name: .Name[1:],
    namespace: "default",
    creationTimestamp: .Created
  },
  spec: {
    image: .Config.Image,
    command: .Config.Cmd,
    env: (.Config.Env | to_entries | map({key: .value | split("=")[0], value: .value | split("=")[1:] | join("=")}))
  },
  status: {
    phase: (if .State.Running then "Running" else "Stopped" end),
    containerId: .Id
  }
}'

Testing

# Run all tests
go test ./...

# Run with verbose output
go test ./... -v

Configuration

Flag Default Description
--providers-dir /providers Directory containing shell providers
--secure-port 443 HTTPS port
--bind-address 0.0.0.0 Listen address

Design Documents

Comparison

shell-apiserver shell-operator Crossplane Provider
State backend None (reality) etcd (CRs) etcd (CRs)
Language Shell Shell Go
Owns API Yes (aggregated) No (CRD) Yes (aggregated)
Reconcile loop No Yes Yes
Complexity Minimal Low High

Non-Goals

  • No storage — scripts may use their own persistence, apiserver does not
  • No reconcile loop — use shell-operator if you need eventual consistency
  • No GitOps — this is imperative, not declarative
  • No auto-generated CRDs — schema defined manually

License

Apache License 2.0. See LICENSE for details.

About

Kubernetes Aggregated API Server where CRUD operations are implemented as shell scripts

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages