Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmd/cli/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,13 @@ func init() {
createClusterCmd.Flags().String("provider", "kind", "Cluster provider (only 'kind' is supported)")

// Shadow global flags
createCmd.PersistentFlags().StringSlice("file", nil, "")
createCmd.PersistentFlags().Bool("debug", false, "")
createCmd.PersistentFlags().String("kubeconfig", "", "")
createCmd.PersistentFlags().Bool("verbose", false, "")

// Hide them from help output
createCmd.PersistentFlags().MarkHidden("file")
createCmd.PersistentFlags().MarkHidden("debug")
createCmd.PersistentFlags().MarkHidden("kubeconfig")
createCmd.PersistentFlags().MarkHidden("verbose")
Expand Down
115 changes: 115 additions & 0 deletions cmd/cli/create_pattern.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//go:build !runtime && !gateway

package cli

import (
"fmt"
"path/filepath"

"github.com/orkspace/orkestra/pkg/generate"
"github.com/spf13/cobra"
)

var createPatternCmd = &cobra.Command{
Use: "pattern",
Short: "Scaffold a new Orkestra pattern: katalog.yaml, simulate.yaml, e2e.yaml, README.md",
Long: `Creates the files needed to build, test, and publish an Orkestra pattern.

Always written:
katalog.yaml — operator declaration
simulate.yaml — in-memory test scaffold (ork simulate)
e2e.yaml — real-cluster integration test scaffold (ork e2e)
README.md — actionable steps from edit to release

Also written when --typed, --add-hook, or --add-constructor:
values.yaml — runtime image (set before ork e2e)
Makefile — registry, build, build-runtime, docker, push, release
Dockerfile — production container image (distroless, runtime binary only)

Typed mode flags are forwarded to katalog generation:
--add-hook Include a hooks section
--add-constructor Include a constructor section
--typed Include both hooks and constructor (commented)

Examples:
ork create pattern
ork create pattern --add-hook -o ./my-operator/
ork create pattern --typed`,
RunE: func(cmd *cobra.Command, args []string) error {
addHook, _ := cmd.Flags().GetBool("add-hook")
addConstructor, _ := cmd.Flags().GetBool("add-constructor")
typed, _ := cmd.Flags().GetBool("typed")
outputDir, _ := cmd.Flags().GetString("output")

if outputDir == "" {
outputDir = "."
}

isTyped := typed || addHook || addConstructor

katalogOpts := generate.KatalogScaffoldOptions{
AddHook: addHook,
AddConstructor: addConstructor,
Typed: typed,
OutputFile: filepath.Join(outputDir, fileKatalog),
}
if err := katalogOpts.Validate(); err != nil {
return err
}

fmt.Printf("generating pattern scaffold → %s/\n", outputDir)

if _, err := generate.KatalogScaffold(katalogOpts); err != nil {
return fmt.Errorf("generating %s: %w", fileKatalog, err)
}

if err := generate.WriteSimulateScaffold(filepath.Join(outputDir, fileSimulate)); err != nil {
return fmt.Errorf("generating %s: %w", fileSimulate, err)
}

if err := generate.WriteE2EScaffold(filepath.Join(outputDir, fileE2e), isTyped); err != nil {
return fmt.Errorf("generating %s: %w", fileE2e, err)
}

if err := generate.WriteREADME(filepath.Join(outputDir, fileReadMe), isTyped); err != nil {
return fmt.Errorf("generating %s: %w", fileReadMe, err)
}

if isTyped {
if err := generate.WriteValuesYAML(filepath.Join(outputDir, fileValues)); err != nil {
return fmt.Errorf("generating %s: %w", fileValues, err)
}
if err := generate.WriteMakefile(filepath.Join(outputDir, fileMakeFile)); err != nil {
return fmt.Errorf("generating %s: %w", fileMakeFile, err)
}
if err := generate.WriteDockerfile(filepath.Join(outputDir, fileDockerfile)); err != nil {
return fmt.Errorf("generating %s: %w", fileDockerfile, err)
}
}

fmt.Printf("\n→ pattern scaffold written to %s\n", bold(outputDir+"/"))
fmt.Printf(" %s %-16s %s\n", successMark(), fileKatalog, dim("declare your CRD(s) and resources"))
fmt.Printf(" %s %-16s %s\n", successMark(), fileSimulate, dim("ork simulate"))
fmt.Printf(" %s %-16s %s\n", successMark(), fileE2e, dim("ork e2e"))
fmt.Printf(" %s %-16s %s\n", successMark(), fileReadMe, dim("start here"))
if isTyped {
fmt.Printf(" %s %-16s %s\n", successMark(), fileValues, dim("set runtime.image before ork e2e"))
fmt.Printf(" %s %-16s %s\n", successMark(), fileMakeFile, dim("make registry, make build, make release"))
fmt.Printf(" %s %-16s %s\n", successMark(), fileDockerfile, dim("production container image"))
}
fmt.Println()
return nil
},
}

func init() {
createCmd.AddCommand(createPatternCmd)
createPatternCmd.Flags().Bool("add-hook", false,
"Typed mode: include a hooks section in katalog.yaml (also writes Makefile + Dockerfile)")
createPatternCmd.Flags().Bool("add-constructor", false,
"Typed mode: include a constructor section in katalog.yaml (also writes Makefile + Dockerfile)")
createPatternCmd.Flags().Bool("typed", false,
"Typed mode: include both hooks and constructor commented (also writes Makefile + Dockerfile)")
createPatternCmd.Flags().StringP("output", "o", "",
"Output directory (default: current directory)")
}
16 changes: 10 additions & 6 deletions cmd/cli/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ import (
)

const (
fileKatalog = "katalog.yaml"
fileKomposer = "komposer.yaml"
fileE2e = "e2e.yaml"
fileSimulate = "simulate.yaml"
fileCrd = "crd.yaml"
fileCr = "cr.yaml"
fileKatalog = "katalog.yaml"
fileKomposer = "komposer.yaml"
fileE2e = "e2e.yaml"
fileSimulate = "simulate.yaml"
fileCrd = "crd.yaml"
fileCr = "cr.yaml"
fileReadMe = "README.md"
fileMakeFile = "Makefile"
fileDockerfile = "Dockerfile"
fileValues = "values.yaml"
)

// resolveKatalogPaths resolves the katalog file paths in the following order:
Expand Down
10 changes: 10 additions & 0 deletions documentation/concepts/patterns/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,14 @@ This is the same shift containers made for applications. Patterns make operator

YAML files are documents. Patterns are something more specific: they encode a solution to a named problem that recurs in the operator world. The name reflects intent — not format.

---

## Pages

| Page | What it covers |
|------|---------------|
| [Scaffolding a pattern](scaffold.md) | `ork create pattern` — generate the full file set to build, test, and publish |

---

→ See the [Pattern kinds in the registry](../../orkestra-registry/index.md) — examples of Katalogs, Motifs, and Komposers in practice.
141 changes: 141 additions & 0 deletions documentation/concepts/patterns/scaffold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Scaffolding a Pattern

`ork create pattern` generates the full file set needed to build, test, and publish an Orkestra pattern. It surfaces the same katalog scaffolding as `ork generate katalog` and adds the testing layer — `simulate.yaml` and `e2e.yaml` — so the operator suite is complete from the start.

```bash
ork create pattern
ork create pattern -o ./my-operator/
ork create pattern --add-hook -o ./my-operator/
```

---

## What gets generated

```text
my-operator/
katalog.yaml — operator declaration
simulate.yaml — in-memory test (ork simulate)
e2e.yaml — real-cluster test (ork e2e)
README.md — actionable steps from edit to release
```

In typed mode (`--add-hook`, `--add-constructor`, `--typed`):

```text
values.yaml — runtime image (set before ork e2e)
Makefile — registry, build, build-runtime, docker, push, release
Dockerfile — production container image (distroless, runtime binary only)
```

---

## Dynamic vs typed

The typed flags are forwarded directly to katalog generation — they behave identically to `ork generate katalog`.

| Flag | Katalog mode | Extra files |
|------|-------------|-------------|
| *(none)* | Dynamic — declarative templates only | — |
| `--add-hook` | Typed — commented `hooks` declaration | `values.yaml`, `Makefile`, `Dockerfile` |
| `--add-constructor` | Typed — commented `constructor` declaration | `values.yaml`, `Makefile`, `Dockerfile` |
| `--typed` | Typed — both sections commented, pick one | `values.yaml`, `Makefile`, `Dockerfile` |

---

## From scaffold to running operator

### Dynamic mode

```bash
# 1. Generate
ork create pattern -o ./my-operator/
cd my-operator/

# 2. Edit katalog.yaml — fill in spec.crds, declare resources
# 3. Validate
ork validate

# 4. Simulate — no cluster needed
ork simulate

# 5. Run locally
ork run --dev

# 6. E2E test
ork e2e

# 7. Observe
ork control
```

### Typed mode

```bash
# 1. Generate
ork create pattern --add-hook -o ./my-operator/
cd my-operator/

# 2. Edit katalog.yaml — fill in apiTypes
# 3. Generate type registry
make registry

# 4. Write your hook function

# 5. Release the image
make release IMAGE=ghcr.io/myorg/my-operator:v0.1.0

# 6. Set the image in values.yaml
# runtime.image.repository: ghcr.io/myorg/my-operator
# runtime.image.tag: v0.1.0

# 7. Validate
ork validate

# 8. Simulate
ork simulate

# 9. Run locally
ork run --dev

# 10. E2E test — passes values.yaml to the Orkestra Helm chart
ork e2e

# 11. Push
ork push
```

---

## The testing layer

`simulate.yaml` and `e2e.yaml` are starters — they reference `./katalog.yaml` and `./cr.yaml` but have no assertions yet.

Add assertions before running, or generate them from a live run:

```bash
ork simulate init
```

See [Simulate](../simulate/index.md) and [E2E](../e2e/index.md) for the full test authoring guide.

---

## Publishing

Once your tests pass, push to the registry:

```bash
ork push
```

In typed mode, release the image first, then update `values.yaml` so `ork e2e` can pass it to the Orkestra Helm chart, then push the katalog:

```bash
make release IMAGE=ghcr.io/myorg/my-operator:v0.1.0
# edit values.yaml: set runtime.image.repository and runtime.image.tag
ork e2e
ork push
```

→ See [`ork create pattern` CLI reference](../../reference/cli/create.md#ork-create-pattern)
67 changes: 67 additions & 0 deletions documentation/reference/cli/create.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,73 @@ ork create cluster --name ork-e2e

---

### `ork create pattern`

Scaffold a complete Orkestra pattern directory: `katalog.yaml`, `simulate.yaml`, `e2e.yaml`, and `README.md`. In typed mode, also writes `values.yaml`, `Makefile`, and `Dockerfile`.

```bash
ork create pattern [flags]
```

#### Flags

| Flag | Description |
|------|-------------|
| `--add-hook` | Typed mode: include a `hooks` section in `katalog.yaml` (also writes `values.yaml`, `Makefile`, `Dockerfile`) |
| `--add-constructor` | Typed mode: include a `constructor` section in `katalog.yaml` (also writes `values.yaml`, `Makefile`, `Dockerfile`) |
| `--typed` | Typed mode: include both sections commented (also writes `values.yaml`, `Makefile`, `Dockerfile`) |
| `-o, --output <dir>` | Output directory (default: current directory) |

#### Files written

| File | Always | Typed mode only |
|------|--------|-----------------|
| `katalog.yaml` | Yes | — |
| `simulate.yaml` | Yes | — |
| `e2e.yaml` | Yes | — |
| `README.md` | Yes | — |
| `values.yaml` | — | Yes |
| `Makefile` | — | Yes |
| `Dockerfile` | — | Yes |

#### Examples

Scaffold a dynamic-mode pattern in the current directory:

```bash
ork create pattern
```

Scaffold into a new directory:

```bash
ork create pattern -o ./my-operator/
```

Typed mode with hooks:

```bash
ork create pattern --add-hook -o ./my-operator/
```

Typed mode with both sections (choose one after generation):

```bash
ork create pattern --typed
```

#### Behavior

- `katalog.yaml` is generated by the same engine as `ork generate katalog` — the typed flags behave identically.
- `simulate.yaml` and `e2e.yaml` are minimal starters that reference `./katalog.yaml` and `./cr.yaml`. Edit assertions before running.
- `e2e.yaml` in typed mode includes `valuesFiles: [./values.yaml]` — the values file is how `ork e2e` passes the runtime image to the Orkestra Helm chart.
- `values.yaml` sets `runtime.image.repository` and `runtime.image.tag`. Update these after `make release` before running `ork e2e`.
- `README.md` contains numbered, actionable steps from edit to release. Typed mode adds the registry generation and build steps.
- `Makefile` targets: `registry`, `build`, `build-runtime`, `validate`, `simulate`, `e2e`, `docker`, `push`, `release`, `clean`.
- `Dockerfile` builds a production image using the runtime binary only (distroless base, no shell).

---

## Notes

- `ork create cluster` is intended for local development and CI environments, not production.
Expand Down
Loading
Loading