Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
d857e31
feat(provider): add SecretBinding type for resolved secret variables
juicycleff Jun 11, 2026
2bea013
feat(vars): add variable definitions and plain/validation resolution
juicycleff Jun 11, 2026
4f6e638
test(vars): cover coercion, secret bindings, and computed expressions
juicycleff Jun 11, 2026
19f1aa6
style(vars): align struct tags (tagalign)
juicycleff Jun 11, 2026
e683937
feat(provider): add DeploymentSource union and validation
juicycleff Jun 11, 2026
6459750
feat(provider): add RenderedSource types and source capabilities
juicycleff Jun 11, 2026
c4054e6
refactor(vars): export Scope.Root for the render package
juicycleff Jun 11, 2026
0265b57
feat(render): render services source
juicycleff Jun 11, 2026
fdb410c
feat(render): render inline manifests
juicycleff Jun 11, 2026
b9d8ac7
feat(render): render kustomize manifests
juicycleff Jun 11, 2026
e003c06
feat(render): render helm values
juicycleff Jun 11, 2026
6d4a3cc
feat(render): render argocd source and guard inline secrets
juicycleff Jun 11, 2026
5496d02
style: satisfy modernize/tagalign for phase 2
juicycleff Jun 11, 2026
fb47978
feat(provider): add ManifestEngine interface
juicycleff Jun 11, 2026
3ed8760
feat(kubernetes): parse manifest documents to unstructured
juicycleff Jun 11, 2026
80b9227
feat(kubernetes): apply unstructured objects create-or-update
juicycleff Jun 11, 2026
5e395ce
feat(kubernetes): apply manifests with ref tracking
juicycleff Jun 11, 2026
d58a9ce
feat(kubernetes): delete tracked manifests
juicycleff Jun 11, 2026
30ce52f
feat(kubernetes): manifest status from tracked refs
juicycleff Jun 11, 2026
28c0ff0
feat(kubernetes): wire manifests engine into provider
juicycleff Jun 11, 2026
7ddcb46
feat(provider): add HelmEngine interface
juicycleff Jun 11, 2026
77fdad2
feat(kubernetes): map helm release status to instance state
juicycleff Jun 11, 2026
5907015
feat(kubernetes): helm install via SDK
juicycleff Jun 11, 2026
02796b4
feat(kubernetes): helm upgrade via SDK
juicycleff Jun 11, 2026
36a4fa4
feat(kubernetes): helm uninstall and status
juicycleff Jun 11, 2026
dbb2302
feat(kubernetes): wire helm engine into provider
juicycleff Jun 11, 2026
20b9066
feat(provider): add ArgoEngine interface
juicycleff Jun 11, 2026
e22c84d
feat(kubernetes): map argo sync/health to instance state
juicycleff Jun 11, 2026
45433f9
feat(kubernetes): build typed argo Application
juicycleff Jun 11, 2026
2214ed1
feat(kubernetes): apply argo Application
juicycleff Jun 11, 2026
2ed59e2
feat(kubernetes): argo delete and status
juicycleff Jun 11, 2026
c0d0300
feat(kubernetes): wire argo engine into provider
juicycleff Jun 11, 2026
52de067
feat(template): add Variables and Source with back-compat normalization
juicycleff Jun 11, 2026
f791d7d
feat(store): persist template Variables and Source with legacy normal…
juicycleff Jun 11, 2026
91b543e
feat(vars): add ValidateDefinitions for author-time validation
juicycleff Jun 11, 2026
5f658ff
feat(template): author and validate deployment source on create/update
juicycleff Jun 11, 2026
2ae3691
feat(dispatch): route rendered source to provider engine
juicycleff Jun 11, 2026
adf2407
feat(dispatch): route deprovision and status by source type
juicycleff Jun 11, 2026
36ade42
feat(instance): provision and teardown via dispatch by source type
juicycleff Jun 11, 2026
3367045
feat(store): persist instance Source for engine-routed teardown
juicycleff Jun 11, 2026
42db146
feat(network): carry Hostname + StripPrefix on AddRoute for host-scop…
juicycleff Jun 11, 2026
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
190 changes: 190 additions & 0 deletions dispatch/dispatch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// Package dispatch routes a rendered deployment source to the appropriate
// provider operation: container services go through the core Provider
// lifecycle, while helm, manifests, and argocd sources are delegated to the
// provider's optional engine interfaces (gated by capability). It is the
// seam that lets one provisioning path serve every source type.
package dispatch

import (
"context"
"fmt"

ctrlplane "github.com/xraph/ctrlplane"
"github.com/xraph/ctrlplane/id"
"github.com/xraph/ctrlplane/provider"
)

// Request carries everything needed to provision an instance from a
// rendered source, independent of source type.
type Request struct {
InstanceID id.ID
TenantID string
Name string
Namespace string
Kind provider.WorkloadKind
Source provider.RenderedSource
Labels map[string]string
}

// Provision routes a rendered source to the right provider engine. Services
// use the core Provision; other types require the provider to implement the
// matching engine interface and advertise its capability, else
// ctrlplane.ErrUnsupportedSource.
func Provision(ctx context.Context, p provider.Provider, req Request) (*provider.ProvisionResult, error) {
switch req.Source.Type {
case provider.SourceServices:
return p.Provision(ctx, provider.ProvisionRequest{
InstanceID: req.InstanceID,
TenantID: req.TenantID,
Name: req.Name,
Kind: req.Kind,
Services: req.Source.Services,
Labels: req.Labels,
})
case provider.SourceManifests:
eng, ok := manifestEngine(p)
if !ok {
return nil, unsupported(req.Source.Type)
}

var docs provider.RenderedManifests
if req.Source.Manifests != nil {
docs = *req.Source.Manifests
}

return eng.ApplyManifests(ctx, provider.ManifestApplyRequest{
InstanceID: req.InstanceID,
TenantID: req.TenantID,
Namespace: req.Namespace,
Manifests: docs,
Labels: req.Labels,
})
case provider.SourceHelm:
eng, ok := helmEngine(p)
if !ok {
return nil, unsupported(req.Source.Type)
}

var chart provider.RenderedHelm
if req.Source.Helm != nil {
chart = *req.Source.Helm
}

return eng.HelmInstall(ctx, provider.HelmInstallRequest{
InstanceID: req.InstanceID,
TenantID: req.TenantID,
Namespace: req.Namespace,
Chart: chart,
})
case provider.SourceArgoCD:
eng, ok := argoEngine(p)
if !ok {
return nil, unsupported(req.Source.Type)
}

var app provider.ArgoCDSource
if req.Source.ArgoCD != nil {
app = *req.Source.ArgoCD
}

return eng.ArgoApply(ctx, provider.ArgoApplyRequest{
InstanceID: req.InstanceID,
TenantID: req.TenantID,
App: app,
Labels: req.Labels,
})
default:
return nil, fmt.Errorf("%w: %q", ctrlplane.ErrInvalidSource, req.Source.Type)
}
}

// Deprovision routes teardown by source type. An empty sourceType (legacy
// instances that predate Source) is treated as services.
func Deprovision(ctx context.Context, p provider.Provider, sourceType provider.SourceType, instanceID id.ID) error {
switch sourceType {
case provider.SourceServices, "":
return p.Deprovision(ctx, instanceID)
case provider.SourceManifests:
eng, ok := manifestEngine(p)
if !ok {
return unsupported(sourceType)
}

return eng.DeleteManifests(ctx, instanceID)
case provider.SourceHelm:
eng, ok := helmEngine(p)
if !ok {
return unsupported(sourceType)
}

return eng.HelmUninstall(ctx, instanceID)
case provider.SourceArgoCD:
eng, ok := argoEngine(p)
if !ok {
return unsupported(sourceType)
}

return eng.ArgoDelete(ctx, instanceID)
default:
return fmt.Errorf("%w: %q", ctrlplane.ErrInvalidSource, sourceType)
}
}

// Status routes a status read by source type. An empty sourceType is treated
// as services.
func Status(ctx context.Context, p provider.Provider, sourceType provider.SourceType, instanceID id.ID) (*provider.InstanceStatus, error) {
switch sourceType {
case provider.SourceServices, "":
return p.Status(ctx, instanceID)
case provider.SourceManifests:
eng, ok := manifestEngine(p)
if !ok {
return nil, unsupported(sourceType)
}

return eng.ManifestStatus(ctx, instanceID)
case provider.SourceHelm:
eng, ok := helmEngine(p)
if !ok {
return nil, unsupported(sourceType)
}

return eng.HelmStatus(ctx, instanceID)
case provider.SourceArgoCD:
eng, ok := argoEngine(p)
if !ok {
return nil, unsupported(sourceType)
}

return eng.ArgoStatus(ctx, instanceID)
default:
return nil, fmt.Errorf("%w: %q", ctrlplane.ErrInvalidSource, sourceType)
}
}

// unsupported builds the capability-gated error.
func unsupported(t provider.SourceType) error {
return fmt.Errorf("%w: %q", ctrlplane.ErrUnsupportedSource, t)
}

// manifestEngine returns the provider as a ManifestEngine when it both
// implements the interface and advertises the capability.
func manifestEngine(p provider.Provider) (provider.ManifestEngine, bool) {
eng, ok := p.(provider.ManifestEngine)

return eng, ok && provider.HasCapability(p, provider.CapManifests)
}

// helmEngine returns the provider as a HelmEngine when supported.
func helmEngine(p provider.Provider) (provider.HelmEngine, bool) {
eng, ok := p.(provider.HelmEngine)

return eng, ok && provider.HasCapability(p, provider.CapHelm)
}

// argoEngine returns the provider as an ArgoEngine when supported.
func argoEngine(p provider.Provider) (provider.ArgoEngine, bool) {
eng, ok := p.(provider.ArgoEngine)

return eng, ok && provider.HasCapability(p, provider.CapArgoCD)
}
Loading
Loading