doctor

package
v0.4.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 9, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

README

pkg/doctor

The doctor package turns a project directory into a production-grade Kubernetes deployment — without the developer writing a single pod spec.

It reads what the developer already has (Dockerfile, .env, .git) and produces everything Kubernetes needs: a Katalog, an application ConfigMap, and a bundle of Secrets and ConfigMaps derived from the environment file.

What the developer already has

my-app/
  Dockerfile        ← how to build
  .env              ← config and secrets
  .git/             ← what version to deploy

That is enough. pkg/doctor produces the rest.

What the package produces

Output File How
Orkestra Katalog .orkestra/katalog.yaml Init()buildKatalog()
Application ConfigMap .orkestra/app.yaml Init()buildCR()
Kubernetes Secret .orkestra/bundle/app-secrets.yaml GenerateBundle()
Kubernetes ConfigMap .orkestra/bundle/app-config.yaml GenerateBundle()

Package structure

File Responsibility
detect.go Read the project directory — language, port, git commit, frontend
envfile.go Parse .env, split into secrets and config vars
generate.go Write .orkestra/katalog.yaml and .orkestra/app.yaml; GenerateOptions.OutDir enables per-app subdirectory output for multi-app projects
bundle.go Write .orkestra/bundle/app-secrets.yaml and app-config.yaml
compose.go Parse docker-compose.yaml; classify services as stateless (Deployments) or stateful (Motif-backed); extract build context and Dockerfile paths
ingress.go Detect ingress controller and Orkestra presence on the cluster
helm.go helm install / helm upgrade wrappers for the Orkestra chart
kind.go Create and manage a local orkestra-playground kind cluster for --dev
runtime.go CheckRuntimeHealth, FetchRuntimeLogs, KatalogChanged, RestartOrkestra
state.go ~/.orkestra/deploy/state.json — per-machine deploy history, rollback support
komposer.go ~/.orkestra/deploy/komposer.yaml — aggregate Katalog paths from all deployed projects

Image building and pushing moved to pkg/buildx — builder auto-detection (Docker / Podman / Buildah), Dockerfile selection, and compose-based builds.

The .env contract

Variables in .env become Kubernetes resources — never baked into the image.

DATABASE_URL=postgres://user:pass@host/db   # → Secret
JWT_SECRET=abc123xyz                         # → Secret

PORT=8080       # ork:cfg → ConfigMap (not a secret)
LOG_LEVEL=info  # ork:cfg → ConfigMap

Variables tagged # ork:cfg on the same line become a ConfigMap. All others become a Secret.

Developer documentation

I want to… Go to
Understand project detection docs/01-detection.md
Understand .env parsing docs/02-envfile.md
Understand Katalog generation (incl. multi-app, compose, Role/RoleBinding) docs/03-generation.md
Understand bundle generation docs/04-bundle.md
Understand cluster operations, health checks, deploy state docs/05-deploy.md
Understand image building and builder selection pkg/buildx

Documentation

Overview

pkg/doctor/compose.go

Reads docker-compose.yaml and extracts service definitions. Classifies services as stateless (Deployments) or stateful (Motif candidates).

Index

Constants

View Source
const (
	BundleFile      = "bundle.yaml"
	AppSecretFile   = "app-secrets.yaml"
	AppConfigFile   = "app-config.yaml"
	ApplicationFile = "app.yaml"

	Orkestra          = "orkestra"
	OrkestraRuntime   = "orkestra-runtime"
	OrkestraNamespace = "orkestra-system"
	OrkestraChartRepo = "https://orkspace.github.io/orkestra"
	OrkestraChartName = "orkestra"

	// Tunnelling
	OrkestraControlCenter     = "orkestra-cc"
	OrkestraControlCenterPort = "8081"
)
View Source
const (
	NotificationSecretName = "orkestra-notification"
	NotificationSecretFile = "orkestra-notification-secret.yaml"
)
View Source
const (
	// KindClusterName is the default kind cluster created by `ork deploy --dev`.
	KindClusterName = "orkestra-playground"
)
View Source
const (
	RuntimeKatalogPath = "__runtime_katalog_do_not_edit.yml"
)

Variables

View Source
var ComposeFileVariants = []string{
	"docker-compose.yaml",
	"docker-compose.yml",
	"compose.yaml",
	"compose.yml",
}

ComposeFileVariants stores common docker compose variants

Functions

func AugmentWithComposeService

func AugmentWithComposeService(info *orktypes.ProjectInfo, svc ComposeService, composeDir string)

AugmentWithComposeService overlays compose-derived port and env vars onto info.

Port: if the service declares ports:, the container port (right-hand side of the mapping) replaces info.Port — it is more authoritative than the .env PORT or language default because it reflects the actual published container port.

Env vars: only replaced when the service has env_file: or environment: entries. The resolved set merges root .env → env_file → environment: in priority order (see resolveServiceEnvVars). If neither source is present, info.EnvVars is left as-is so that vars detected by Detect() are preserved.

composeDir should be the directory containing the docker-compose.yaml file; all relative env_file paths are resolved against it.

func BuildNotificationSecret

func BuildNotificationSecret(envMap map[string]string) string

BuildNotificationSecret generates the orkestra-notification Kubernetes Secret YAML. When applied to orkestra-system and referenced by runtime.extraEnvFrom, it injects SMTP/Slack credentials into the Orkestra runtime so pkg/konfig can pick them up as normal env vars.

func ClusterReachable

func ClusterReachable() bool

ClusterReachable reports whether kubectl can reach the cluster configured in the current kubeconfig (KUBECONFIG env, ~/.kube/config, or --kubeconfig flag resolved by root.go). Times out after 5 seconds.

func CurrentContext

func CurrentContext() string

CurrentContext returns the active kubectl context name.

func DeduplicateKatalogGVKs

func DeduplicateKatalogGVKs(path string) error

DeduplicateKatalogGVKs reads the merged katalog at path, collapses all CRDs that share the same GVK into one, and writes the result back.

When ork kompose merges multiple single-app katalogs, each produces a CRD with apiTypes.kind: ConfigMap. Orkestra forbids duplicate GVKs at runtime, so this must be called before ork generate bundle in the multi-app flow.

Merge strategy for each duplicate-GVK group:

  • operatorBox lifecycle hooks (onCreate, onReconcile, …): list fields concatenated
  • labelSelector: union of all key-value pairs (first value wins on collision)
  • allowedNamespaces: union of all namespace strings
  • status: kept from the first (canonical) CRD — all duplicates are identical

func Detect

func Detect(dir string) (*orktypes.ProjectInfo, error)

Detect scans a project directory and returns a populated ProjectInfo describing everything ork doctor can infer about the application.

Detection includes:

  • Dockerfile / Containerfile presence
  • Git commit (short SHA)
  • Primary programming language and marker file
  • .env parsing, including config vs secret variables
  • SMTP/Slack environment variable hints
  • Application port (from .env or language defaults)
  • Frontend detection via static dirs or JS frameworks
  • License detection from common license files
  • docker-compose.yaml discovery

Missing .env files are not treated as errors. The function returns an error only when parsing .env fails or when filesystem access is not possible.

func DetectComposeFile

func DetectComposeFile(dir string) string

DetectComposeFile looks for docker-compose.yaml in dir and returns the path. Returns empty string when no compose file is found.

func DetectLicense

func DetectLicense(dir string) string

DetectLicense scans the project directory for common license files (LICENSE, LICENSE.txt, LICENSE.md, COPYING, etc). It returns a normalized SPDX-style license name based on the first line of the file. If no license file is found or cannot be read, an empty string is returned.

func DockerfileTemplate

func DockerfileTemplate(l orktypes.Language) string

DockerfileTemplate returns a starter Dockerfile snippet for the detected language. If the language is unknown, it returns an empty string.

func EnsureDependencies

func EnsureDependencies() error

EnsureDependencies installs kubectl and helm if they are missing. Uses a spinner for each installation step.

func EnsureIngressController added in v0.4.0

func EnsureIngressController() error

EnsureIngressController installs nginx-ingress if no ingress controller is found on the current cluster. Uses the kind-specific manifest when the current context is a kind cluster; otherwise installs via Helm.

func EnsureKindCluster

func EnsureKindCluster(name string) error

EnsureKindCluster creates a kind cluster named `name` if it does not already exist, then switches kubectl to its context. Downloads the kind binary from GitHub releases if not found in PATH or ~/.orkestra/bin — Go is not required.

func EnsureMetricsServer added in v0.4.0

func EnsureMetricsServer() error

EnsureMetricsServer ensures the Kubernetes metrics-server is installed.

It checks for the metrics-server deployment using `kubectl get -o json` for unambiguous detection via exit status. If not present, it attempts installation via Helm, using a kind-specific flag when needed.

Any failure (check or install) is treated as non-fatal: the error is logged and the user is instructed to install metrics-server manually.

func FetchRuntimeLogs

func FetchRuntimeLogs() (tail string, err error)

FetchRuntimeLogs saves the last 100 log lines from the Orkestra runtime to /tmp/orkestra/runtime.log. If the control-center deployment exists but has no ready replicas, its logs are also saved to /tmp/orkestra/controlcenter.log. Returns the last 10 lines of the runtime log for inline display.

func GenerateBundle

func GenerateBundle(name, namespace string, secrets, config []orktypes.EnvVar, outputDir string) error

GenerateBundle writes the app Secret and ConfigMap YAML files into outputDir. The caller is responsible for creating outputDir before calling.

func GetEnvValue

func GetEnvValue(vars []orktypes.EnvVar, key string) (string, bool)

GetEnvValue returns the value of an ENV var or false if not exists

func GlobalKomposerPath

func GlobalKomposerPath() (string, error)

GlobalKomposerPath returns ~/.orkestra/deploy/komposer.yaml.

func GoInstalled

func GoInstalled() bool

GoInstalled reports whether the Go toolchain is present in PATH. Required when kind needs to be installed via `go install`.

func HasSMTP

func HasSMTP(vars []orktypes.EnvVar) bool

HasSMTP returns true if any variable starts with "SMTP_".

func HasSlack

func HasSlack(vars []orktypes.EnvVar) bool

HasSlack returns true if any variable starts with "SLACK_".

func HelmAvailable

func HelmAvailable() bool

HelmAvailable reports whether helm is present in PATH.

func ImageTag

func ImageTag(registry, appName, tag string) string

ImageTag returns the full image reference for a build. tag defaults to the git commit short SHA when empty.

func Init

func Init(info *orktypes.ProjectInfo, opts GenerateOptions) error

Init generates .orkestra/katalog.yaml and .orkestra/app.yaml, creates the .orkestra/ directory, and updates .gitignore to exclude the bundle directory.

func InstallOrUpgradeOrkestra

func InstallOrUpgradeOrkestra(version, values string, upgrade bool) error

func KatalogChanged

func KatalogChanged(dir string) bool

KatalogChanged returns true when .orkestra/katalog.yaml has uncommitted changes or was modified by the most recent commit. Either condition means the operator should be restarted after the bundle is applied so it picks up the new configuration.

func KubectlAvailable

func KubectlAvailable() bool

KubectlAvailable reports whether kubectl is present in PATH.

func NotificationEnvVars

func NotificationEnvVars(vars []orktypes.EnvVar) map[string]string

NotificationEnvVars maps developer .env keys to Orkestra konfig env var names. Only keys with non-empty values are included.

func OrkestraInstalled

func OrkestraInstalled() bool

OrkestraInstalled reports whether the Orkestra runtime deployment exists and is available in the given namespace.

func OrkestraInstalledRunning

func OrkestraInstalledRunning() bool

OrkestraInstalled reports whether the Orkestra runtime deployment exists and is available and running in the given namespace.

func ParseEnvFile

func ParseEnvFile(path string) ([]orktypes.EnvVar, error)

ParseEnvFile reads a .env file and returns all non-blank, non-comment lines. Variables tagged with "# ork:cfg" on the same line have IsCfg = true; all others are treated as secrets.

func ReadCRName

func ReadCRName(appYAML string) (string, error)

ReadCRName reads the ConfigMap name from .orkestra/app.yaml. Returns the name (e.g. "my-app-orkestra") so callers don't need to re-derive it.

func RestartOrkestra

func RestartOrkestra() error

RestartOrkestra issues a rollout restart of the runtime deployment and waits up to 3 minutes for it to become available again.

func SplitEnvVars

func SplitEnvVars(vars []orktypes.EnvVar) (secrets, config []orktypes.EnvVar)

SplitEnvVars partitions parsed variables into secrets and config vars.

func StateDir

func StateDir() (string, error)

StateDir returns ~/.orkestra/deploy/

func StatefulDepsPerApp

func StatefulDepsPerApp(cf *ComposeFile, appNames []string, stateful []StatefulService) map[string][]StatefulService

StatefulDepsPerApp maps each buildable app name to the stateful services whose Motif declaration belongs in that app's katalog.

Each stateful service is declared exactly once — in the katalog of the first app (in appNames order) that lists it in depends_on. If no app declares a depends_on relationship with a given stateful service, it is assigned to appNames[0] as a fallback so it still gets deployed exactly once.

Example: three apps all depend on postgres and two depend on redis. Result: postgres and redis each appear in one katalog only (the earliest app in appNames that depends on each), not repeated across every depending app.

Types

type ComposeFile

type ComposeFile struct {
	Services map[string]ComposeService `yaml:"services"`
}

ComposeFile is the parsed docker-compose.yaml.

func ParseCompose

func ParseCompose(path string) (*ComposeFile, error)

ParseCompose reads and parses a docker-compose.yaml file.

type ComposeService

type ComposeService struct {
	Image       interface{} `yaml:"image,omitempty"` // string
	Build       interface{} `yaml:"build,omitempty"` // string or object
	Ports       interface{} `yaml:"ports,omitempty"` // []string or []int
	Environment interface{} `yaml:"environment,omitempty"`
	EnvFile     interface{} `yaml:"env_file,omitempty"` // string or []string or [{path,required}]
	Volumes     interface{} `yaml:"volumes,omitempty"`
	DependsOn   interface{} `yaml:"depends_on,omitempty"`
}

ComposeService is one service entry in a docker compose file.

func (ComposeService) BuildContext

func (s ComposeService) BuildContext() (context, dockerfile string)

BuildContext extracts the build context directory and optional Dockerfile path from the service's build: field. Returns ("", "") when build: is not set.

String form: build: ./app → context="./app", dockerfile="" Map form: build: {context: ./app, dockerfile: ./Dockerfile.prod}

func (ComposeService) ContainerPort

func (s ComposeService) ContainerPort() string

ContainerPort returns the container (right-hand) port from the first ports: entry. Handles all compose port formats:

  • "8080" → "8080"
  • "3001:3000" → "3000"
  • "127.0.0.1:8000:8080" → "8080"
  • integer 8080 → "8080"
  • long form {target: 8080} → "8080"

func (ComposeService) DependsOnNames

func (s ComposeService) DependsOnNames() []string

DependsOnNames returns the service names this service depends on. Handles both the list form (depends_on: [a, b]) and the map/condition form (depends_on: {a: {condition: service_healthy}}).

func (ComposeService) EnvFileList

func (s ComposeService) EnvFileList() []string

EnvFileList returns the env_file paths declared on this service. Handles string, []string, and long form [{path: "...", required: false}].

type DeployState

type DeployState struct {
	ClusterContext string                   `json:"clusterContext"`
	Projects       map[string]*ProjectState `json:"projects"`
}

DeployState is the contents of ~/.orkestra/deploy/state.json.

func LoadState

func LoadState() (*DeployState, error)

LoadState reads the state file. Returns an empty state if not found.

func (*DeployState) PreviousImage

func (s *DeployState) PreviousImage(appName string) string

PreviousImage returns the image deployed before the last deploy, or "" if none.

func (*DeployState) RecordDeploy

func (s *DeployState) RecordDeploy(appName, namespace, katalogPath, newImage string)

RecordDeploy captures the current image as previous before recording the new one. Call this BEFORE patching the cluster so previousImage is always the live image.

func (*DeployState) Save

func (s *DeployState) Save() error

Save writes the state file atomically.

type GenerateOptions

type GenerateOptions struct {
	NoHA       bool   // skip HPA and PDB; single replica
	NoSecure   bool   // skip deletionProtection and protection labels
	Clean      bool   // add cleanupOnShutdown: true to deletionProtection
	Name       string // override app name
	AddIngress bool   // force-include Ingress even when no frontend was detected
	NotifyMe   bool   // add notification block
	UseCompose string // path to docker-compose.yaml (expand stateful services via Motifs)
	OutDir     string // override .orkestra/ directory; empty = info.Dir/.orkestra

	// InjectStateful, when non-nil, specifies exactly which stateful services
	// to inject into this app's katalog and app.yaml. When set it takes
	// precedence over the UseCompose-based auto-detection, allowing the caller
	// to supply a pre-filtered, per-app slice derived from depends_on analysis.
	// Use nil (not an empty slice) to fall back to UseCompose detection.
	InjectStateful []StatefulService

	// StatefulDeps lists stateful services (Motifs) this app must wait for
	// before its Deployment is created. Emits when: conditions in onCreate.
	StatefulDeps []StatefulService

	// StatelessDeps lists sibling app names this app must wait for before
	// its Deployment is created. Emits when: conditions in onCreate.
	StatelessDeps []string
}

GenerateOptions controls which sections are included in the generated Katalog.

type GitAuthor

type GitAuthor struct {
	Name     string
	Email    string
	Raw      string // "Name <email>"
	Notfound bool
}

func LastCommitAuthor

func LastCommitAuthor() (*GitAuthor, error)

LastCommitAuthor returns "Name <email>" from the most recent Git commit.

type GlobalKomposer

type GlobalKomposer struct {
	APIVersion string          `yaml:"apiVersion"`
	Kind       string          `yaml:"kind"`
	Metadata   KomposerMeta    `yaml:"metadata"`
	Imports    KomposerSources `yaml:"imports"`
}

GlobalKomposer is the structure of ~/.orkestra/deploy/komposer.yaml. It aggregates Katalog paths from all deployed projects on this machine.

func LoadGlobalKomposer

func LoadGlobalKomposer() (*GlobalKomposer, error)

LoadGlobalKomposer reads the global Komposer, returning a fresh one if absent.

func (*GlobalKomposer) DeployedProjects

func (k *GlobalKomposer) DeployedProjects() []string

DeployedProjects returns app names inferred from the Komposer source paths. Paths are expected to contain a .orkestra/ directory segment.

func (*GlobalKomposer) RegisterKatalog

func (k *GlobalKomposer) RegisterKatalog(katalogPath string) bool

RegisterKatalog adds katalogPath to the Komposer sources if not already present. Returns true when the path was newly added.

func (*GlobalKomposer) Save

func (k *GlobalKomposer) Save() error

Save writes the global Komposer file.

type IngressController

type IngressController string

IngressController is the ingress controller detected on the cluster.

const (
	IngressNginx   IngressController = "nginx"
	IngressTraefik IngressController = "traefik"
	IngressNone    IngressController = ""
)

type KnownMotif

type KnownMotif struct {
	Image       string   // detected image prefix (may include registry like confluentinc/)
	MotifRef    string   // short name in orkestra-motifs
	AppYAMLKeys []string // keys to add to app.yaml
	AdminUI     string   // companion admin UI name
}

KnownMotif maps a detected infrastructure image to its Motif reference.

type KomposerMeta

type KomposerMeta struct {
	Name        string `yaml:"name"`
	Description string `yaml:"description,omitempty"`
	Version     string `yaml:"version,omitempty"`
	Author      string `yaml:"author,omitempty"`
	License     string `yaml:"license,omitempty"`
}

type KomposerSources

type KomposerSources struct {
	Files []string `yaml:"files,omitempty"`
}

type ProjectState

type ProjectState struct {
	Name          string    `json:"name"`
	Namespace     string    `json:"namespace"`
	CurrentImage  string    `json:"currentImage"`
	PreviousImage string    `json:"previousImage,omitempty"`
	KatalogPath   string    `json:"katalogPath"`
	DeployedAt    time.Time `json:"deployedAt"`
}

ProjectState tracks one deployed project.

type RuntimeStatus

type RuntimeStatus struct {
	Running bool
	Reason  string // set when Running is false
}

RuntimeStatus is the result of CheckRuntimeHealth.

func CheckRuntimeHealth

func CheckRuntimeHealth() RuntimeStatus

CheckRuntimeHealth waits up to healthCheckTimeout for the Orkestra runtime deployment to have at least one ready replica. It polls every 2 seconds. If pods are in CrashLoopBackOff, it returns immediately with the reason.

type StatefulService

type StatefulService struct {
	Name  string
	Motif KnownMotif
	Image string
}

StatefulService describes a detected infrastructure service.

func ClassifyServices

func ClassifyServices(cf *ComposeFile) (stateless []string, stateful []StatefulService)

ClassifyServices separates compose services into stateless (Deployments) and stateful (Motif-backed) based on known infrastructure images.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL