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
38 changes: 21 additions & 17 deletions cmd/crossplane/render/op/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
"github.com/crossplane/cli/v2/cmd/crossplane/render/contextfn"
"github.com/crossplane/cli/v2/internal/async"
"github.com/crossplane/cli/v2/internal/config"
"github.com/crossplane/cli/v2/internal/containerruntime"
"github.com/crossplane/cli/v2/internal/dependency"
"github.com/crossplane/cli/v2/internal/project"
"github.com/crossplane/cli/v2/internal/project/functions"
Expand Down Expand Up @@ -172,14 +173,22 @@ func (c *Cmd) Run(baseCtx context.Context, k *kong.Context, log logging.Logger,

c.SetDefaultCrossplaneDockerNetwork(fns)

engine := c.newEngine(&c.EngineFlags, log)
sess := render.NewSession(c.EngineFlags, log,
render.WithEngineFactory(c.newEngine),
render.WithLeaseDir(containerruntime.DefaultLeaseDir()),
// Leasing this run and reclaiming dead runs is best effort, and
// must not change this command's output.
render.WithWarn(func(err error) {
log.Debug("Cannot lease this run or reclaim dead runs' Docker resources", "error", err)
}),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
)

seedCtx := len(c.ContextValues) > 0 || len(c.ContextFiles) > 0
captureCtx := c.IncludeContext

var ctxHandle *contextfn.Handle
if seedCtx || captureCtx {
if err := engine.CheckContextSupport(); err != nil {
if err := sess.CheckContextSupport(); err != nil {
return err
}

Expand Down Expand Up @@ -208,28 +217,23 @@ func (c *Cmd) Run(baseCtx context.Context, k *kong.Context, log logging.Logger,
}
}

cleanup, err := engine.Setup(ctx, fns)
if err != nil {
return err
}
defer cleanup()

// Start function runtimes to get their addresses.
fnAddrs, err := render.StartFunctionRuntimes(ctx, log, fns)
if err != nil {
return errors.Wrap(err, "cannot start function runtimes")
}
// Close stops the function runtimes, then removes what Setup created.
defer func() {
if err := render.StopFunctionRuntimes(ctx, fnAddrs); err != nil {
log.Info("Error stopping function runtimes", "error", err)
if err := sess.Close(ctx); err != nil {
log.Info("Error cleaning up render session", "error", err)
render.WarnCleanupFailure(k.Stderr, err)
}
}()

fnAddrs, err := sess.Start(ctx, fns)
if err != nil {
return err
}

// Build and execute the render request.
in := render.OperationInputs{
Operation: op,
FunctionAddrs: fnAddrs.Addresses(),
FunctionAddrs: fnAddrs,
RequiredResources: rrs,
RequiredSchemas: rsc,
FunctionCredentials: fcreds,
Expand All @@ -239,7 +243,7 @@ func (c *Cmd) Run(baseCtx context.Context, k *kong.Context, log logging.Logger,
return errors.Wrap(err, "cannot build render request")
}

rsp, err := engine.Render(ctx, req)
rsp, err := sess.Render(ctx, req)
if err != nil {
return errors.Wrap(err, "cannot render operation")
}
Expand Down
5 changes: 5 additions & 0 deletions cmd/crossplane/render/op/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ func mustNewStruct(t *testing.T, data map[string]any) *structpb.Struct {
}

func TestCmdRun(t *testing.T) {
// Run leases this run in the user's cache directory, and reaps the dead
// runs it finds there; keep both off the developer's real one.
t.Setenv("HOME", t.TempDir())
t.Setenv("XDG_CACHE_HOME", t.TempDir())

type args struct {
cmd Cmd
}
Expand Down
19 changes: 18 additions & 1 deletion cmd/crossplane/render/op/help/render.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ they're run:
| `render.crossplane.io/runtime: "Development"` | Connect to a Function running locally, instead of using Docker, for example when developing or debugging a new Function. The Function must be listening at `localhost:9443` and running with the `--insecure` flag. |
| `render.crossplane.io/runtime-development-target: "dns:///example.org:7443"` | Connect to a Function running somewhere other than `localhost:9443`. The target uses gRPC target syntax (for example, `dns:///example.org:7443` or `example.org:7443`). |
| `render.crossplane.io/runtime-docker-cleanup: "Orphan"` | Don't stop the Function's Docker container after rendering. |
| `render.crossplane.io/runtime-docker-name: "<name>"` | Create or reuse a container with the given name. Restart the container if needed. |
| `render.crossplane.io/runtime-docker-name: "<name>"` | Create or reuse a container with the given name. Restart the container if needed. See below for when a running container is reused. |
| `render.crossplane.io/runtime-docker-pull-policy: "Always"` | Always pull the Function's package, even if it already exists locally. Other supported values are `Never` or `IfNotPresent`. |
| `render.crossplane.io/runtime-docker-publish-address: "0.0.0.0"` | Host address that Docker should publish the Function's container port to. Defaults to `127.0.0.1` (localhost only). Use `0.0.0.0` to publish to all host network interfaces, enabling access from remote machines. |
| `render.crossplane.io/runtime-docker-target: "docker-host"` | Address that the render CLI should use to connect to the Function's Docker container. If not specified, uses the publish address. |
Expand All @@ -31,6 +31,23 @@ to the Docker daemon. See the
[Docker environment variables](https://docs.docker.com/engine/reference/commandline/cli/#environment-variables)
reference.

## Cleaning up after a crashed run

If a run is killed before it cleans up (for example by `kill -9`), the next
`composition render` or `operation render` run cleans up what it left behind,
following each container's `runtime-docker-cleanup` policy. This doesn't apply
to runs started with `--session-id`, to runs on Windows, or to a different
`DOCKER_HOST`.

If a container named by `runtime-docker-name` already exists:

| Container | What this run does |
|---|---|
| Stopped | Restarts it. On exit it keeps it as long as either run's policy asks (`Orphan` > `Stop` > `Remove`). |
| Running, `Orphan` | Shares it and leaves it running. |
| Running, started by this run | Shares it. |
| Running, `Stop` or `Remove`, started by another run | Fails, because that run will stop or remove it. Use `Orphan` to share a container between runs. |

## Project support

When running `render` in a Crossplane Project (any directory containing a
Expand Down
38 changes: 21 additions & 17 deletions cmd/crossplane/render/xr/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import (
"github.com/crossplane/cli/v2/cmd/crossplane/render/contextfn"
"github.com/crossplane/cli/v2/internal/async"
"github.com/crossplane/cli/v2/internal/config"
"github.com/crossplane/cli/v2/internal/containerruntime"
"github.com/crossplane/cli/v2/internal/dependency"
"github.com/crossplane/cli/v2/internal/project"
"github.com/crossplane/cli/v2/internal/project/functions"
Expand Down Expand Up @@ -232,14 +233,22 @@ func (c *Cmd) Run(baseCtx context.Context, k *kong.Context, log logging.Logger,

c.SetDefaultCrossplaneDockerNetwork(fns)

engine := c.newEngine(&c.EngineFlags, log)
sess := render.NewSession(c.EngineFlags, log,
render.WithEngineFactory(c.newEngine),
render.WithLeaseDir(containerruntime.DefaultLeaseDir()),
// Leasing this run and reclaiming dead runs is best effort, and
// must not change this command's output.
render.WithWarn(func(err error) {
log.Debug("Cannot lease this run or reclaim dead runs' Docker resources", "error", err)
}),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
)

seedCtx := len(c.ContextValues) > 0 || len(c.ContextFiles) > 0
captureCtx := c.IncludeContext

var ctxHandle *contextfn.Handle
if seedCtx || captureCtx {
if err := engine.CheckContextSupport(); err != nil {
if err := sess.CheckContextSupport(); err != nil {
return err
}

Expand Down Expand Up @@ -268,29 +277,24 @@ func (c *Cmd) Run(baseCtx context.Context, k *kong.Context, log logging.Logger,
}
}

cleanup, err := engine.Setup(ctx, fns)
if err != nil {
return err
}
defer cleanup()

// Start function runtimes to get their addresses.
fnAddrs, err := render.StartFunctionRuntimes(ctx, log, fns)
if err != nil {
return errors.Wrap(err, "cannot start function runtimes")
}
// Close stops the function runtimes, then removes what Setup created.
defer func() {
if err := render.StopFunctionRuntimes(ctx, fnAddrs); err != nil {
log.Info("Error stopping function runtimes", "error", err)
if err := sess.Close(ctx); err != nil {
log.Info("Error cleaning up render session", "error", err)
render.WarnCleanupFailure(k.Stderr, err)
}
}()

fnAddrs, err := sess.Start(ctx, fns)
if err != nil {
return err
}

// Build and execute the render request.
in := render.CompositionInputs{
CompositeResource: xr,
Composition: comp,
FunctionAddrs: fnAddrs.Addresses(),
FunctionAddrs: fnAddrs,
ObservedResources: ors,
RequiredResources: rrs,
RequiredSchemas: rsc,
Expand All @@ -302,7 +306,7 @@ func (c *Cmd) Run(baseCtx context.Context, k *kong.Context, log logging.Logger,
return errors.Wrap(err, "cannot build render request")
}

rsp, err := engine.Render(ctx, req)
rsp, err := sess.Render(ctx, req)
if err != nil {
return errors.Wrap(err, "cannot render composite resource")
}
Expand Down
5 changes: 5 additions & 0 deletions cmd/crossplane/render/xr/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ func fillResourceRefs(t *testing.T, xr *structpb.Struct) *structpb.Struct {
}

func TestCmdRun(t *testing.T) {
// Run leases this run in the user's cache directory, and reaps the dead
// runs it finds there; keep both off the developer's real one.
t.Setenv("HOME", t.TempDir())
t.Setenv("XDG_CACHE_HOME", t.TempDir())

type args struct {
cmd Cmd
}
Expand Down
19 changes: 18 additions & 1 deletion cmd/crossplane/render/xr/help/render.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ they're run:
| `render.crossplane.io/runtime: "Development"` | Connect to a Function running locally, instead of using Docker, for example when developing or debugging a new Function. The Function must be listening at `localhost:9443` and running with the `--insecure` flag. |
| `render.crossplane.io/runtime-development-target: "dns:///example.org:7443"` | Connect to a Function running somewhere other than `localhost:9443`. The target uses gRPC target syntax (for example, `dns:///example.org:7443` or `example.org:7443`). |
| `render.crossplane.io/runtime-docker-cleanup: "Orphan"` | Don't stop the Function's Docker container after rendering. |
| `render.crossplane.io/runtime-docker-name: "<name>"` | Create or reuse a container with the given name. Restart the container if needed. |
| `render.crossplane.io/runtime-docker-name: "<name>"` | Create or reuse a container with the given name. Restart the container if needed. See below for when a running container is reused. |
| `render.crossplane.io/runtime-docker-pull-policy: "Always"` | Always pull the Function's package, even if it already exists locally. Other supported values are `Never` or `IfNotPresent`. |
| `render.crossplane.io/runtime-docker-publish-address: "0.0.0.0"` | Host address that Docker should publish the Function's container port to. Defaults to `127.0.0.1` (localhost only). Use `0.0.0.0` to publish to all host network interfaces, enabling access from remote machines. |
| `render.crossplane.io/runtime-docker-target: "docker-host"` | Address that the render CLI should use to connect to the Function's Docker container. If not specified, uses the publish address. |
Expand All @@ -34,6 +34,23 @@ to the Docker daemon. See the
[Docker environment variables](https://docs.docker.com/engine/reference/commandline/cli/#environment-variables)
reference.

## Cleaning up after a crashed run

If a run is killed before it cleans up (for example by `kill -9`), the next
`composition render` or `operation render` run cleans up what it left behind,
following each container's `runtime-docker-cleanup` policy. This doesn't apply
to runs started with `--session-id`, to runs on Windows, or to a different
`DOCKER_HOST`.

If a container named by `runtime-docker-name` already exists:

| Container | What this run does |
|---|---|
| Stopped | Restarts it. On exit it keeps it as long as either run's policy asks (`Orphan` > `Stop` > `Remove`). |
| Running, `Orphan` | Shares it and leaves it running. |
| Running, started by this run | Shares it. |
| Running, `Stop` or `Remove`, started by another run | Fails, because that run will stop or remove it. Use `Orphan` to share a container between runs. |

## Project support

When running `render` in a Crossplane Project (any directory containing a
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ require (
github.com/willabides/kongplete v0.4.0
golang.org/x/mod v0.42.0
golang.org/x/sync v0.24.0
golang.org/x/sys v0.49.0
golang.org/x/text v0.43.0
golang.org/x/tools v0.52.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20260831171406-18b4a7587f8a
Expand Down Expand Up @@ -319,7 +320,6 @@ require (
golang.org/x/exp v0.0.0-20260824195058-e88cd73687aa // indirect
golang.org/x/net v0.62.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sys v0.49.0 // indirect
golang.org/x/term v0.47.0 // indirect
golang.org/x/time v0.15.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
Expand Down
Loading
Loading