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
2 changes: 2 additions & 0 deletions docs/docs/sidebar/architecture/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ configure them — see the Features section:
- [Network Management](../features/network-management.md) — DNS, ping
- [Command Execution](../features/command-execution.md) — exec, shell
- [File Management](../features/file-management.md) — upload, deploy, templates
- [Container Management](../features/container-management.md) — Docker
lifecycle, exec, pull
- [Job System](../features/job-system.md) — async job processing and routing
- [Audit Logging](../features/audit-logging.md) — API audit trail and export
- [Health Checks](../features/health-checks.md) — liveness, readiness, status
Expand Down
36 changes: 36 additions & 0 deletions docs/docs/sidebar/features/container-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,48 @@ osapi token generate -r write -u user@example.com \
-p container:execute
```

## Orchestrator DSL

The [orchestrator](../sdk/orchestrator/orchestrator.md) SDK supports container
operations through the
[Container Targeting](../sdk/orchestrator/features/container-targeting.md)
feature. Use `Plan.Docker()` and `Plan.In()` to create containers and run
existing providers inside them without rewriting any code:

```go
plan := orchestrator.NewPlan(client, orchestrator.WithDockerExecFn(execFn))
web := plan.Docker("web-server", "nginx:alpine")

create := plan.TaskFunc("create", func(ctx context.Context, c *client.Client) (*orchestrator.Result, error) {
return c.Container.Create(ctx, "_any", gen.ContainerCreateRequest{
Image: "nginx:alpine",
Name: ptr("web-server"),
})
})

plan.In(web).TaskFunc("check-config", func(ctx context.Context, c *client.Client) (*orchestrator.Result, error) {
// Runs inside the container via docker exec + provider run
return c.Container.Exec(ctx, "_any", "web-server", gen.ContainerExecRequest{
Command: []string{"nginx", "-t"},
})
}).DependsOn(create)
```

The transport changes from HTTP to `docker exec` + `provider run`, but the SDK
interface is identical. See the
[container targeting feature page](../sdk/orchestrator/features/container-targeting.md)
for details and the
[operations reference](../sdk/orchestrator/orchestrator.md#operations) for all
container operations.

## Related

- [CLI Reference](../usage/cli/client/container/container.mdx) -- container
management commands
- [API Reference](/gen/api/container-management-api-container-operations) --
REST API documentation
- [Orchestrator Container Targeting](../sdk/orchestrator/features/container-targeting.md)
-- DSL for running providers inside containers
- [Job System](job-system.md) -- how async job processing works
- [Authentication & RBAC](authentication.md) -- permissions and roles
- [Architecture](../architecture/architecture.md) -- system design overview
1 change: 1 addition & 0 deletions docs/docs/sidebar/features/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ OSAPI provides a comprehensive set of features for managing Linux systems.
| 📈 | [Metrics](metrics.md) | Prometheus `/metrics` endpoint |
| 📋 | [Audit Logging](audit-logging.md) | Structured API audit trail with 30-day retention |
| 🔐 | [Authentication & RBAC](authentication.md) | JWT with fine-grained `resource:verb` permissions |
| 📦 | [Container Management](container-management.md) | Docker lifecycle, exec, and pull through pluggable runtime drivers |
| 🔍 | [Distributed Tracing](distributed-tracing.md) | OpenTelemetry with trace context propagation |

<!-- prettier-ignore-end -->
143 changes: 143 additions & 0 deletions docs/docs/sidebar/sdk/orchestrator/features/container-targeting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
---
sidebar_position: 14
---

# Container Targeting

Create containers and run provider operations inside them using the same typed
SDK methods. The transport changes from HTTP to `docker exec` + `provider run`,
but the interface is identical.

## Setup

Provide a `WithDockerExecFn` option when creating the plan. The exec function
calls the Docker SDK's `ContainerExecCreate` / `ContainerExecAttach` APIs to run
commands inside containers.

```go
plan := orchestrator.NewPlan(client, orchestrator.WithDockerExecFn(execFn))
```

## Docker Target

`Plan.Docker()` creates a `DockerTarget` bound to a container name and image. It
implements the `RuntimeTarget` interface:

```go
web := plan.Docker("web-server", "nginx:alpine")
```

`RuntimeTarget` is pluggable — Docker is the first implementation. Future
runtimes (LXD, Podman) implement the same interface.

## Scoped Plans

`Plan.In()` returns a `ScopedPlan` that routes provider operations through the
target's `ExecProvider` method:

```go
plan.In(web).TaskFunc("run-inside",
func(ctx context.Context, c *client.Client) (*orchestrator.Result, error) {
// This executes inside the container via:
// docker exec web-server /osapi provider run <provider> <op> --data '<json>'
return &orchestrator.Result{Changed: true}, nil
},
)
```

The `ScopedPlan` supports `TaskFunc` and `TaskFuncWithResults`, with the same
dependency, guard, and error strategy features as the parent plan.

## Full Example

A typical workflow creates a container, runs operations inside it, then cleans
up:

```go
plan := orchestrator.NewPlan(client, orchestrator.WithDockerExecFn(execFn))
web := plan.Docker("my-app", "ubuntu:24.04")

pull := plan.TaskFunc("pull-image",
func(ctx context.Context, c *client.Client) (*orchestrator.Result, error) {
resp, err := c.Container.Pull(ctx, "_any", gen.ContainerPullRequest{
Image: "ubuntu:24.04",
})
if err != nil {
return nil, err
}
return &orchestrator.Result{Changed: true, Data: map[string]any{
"image_id": resp.Data.Results[0].ImageID,
}}, nil
},
)

create := plan.TaskFunc("create-container",
func(ctx context.Context, c *client.Client) (*orchestrator.Result, error) {
autoStart := true
resp, err := c.Container.Create(ctx, "_any", gen.ContainerCreateRequest{
Image: "ubuntu:24.04",
Name: ptr("my-app"),
AutoStart: &autoStart,
})
if err != nil {
return nil, err
}
return &orchestrator.Result{Changed: true, Data: map[string]any{
"container_id": resp.Data.Results[0].ID,
}}, nil
},
)
create.DependsOn(pull)

// Run a command inside the container
checkOS := plan.In(web).TaskFunc("check-os",
func(ctx context.Context, c *client.Client) (*orchestrator.Result, error) {
resp, err := c.Container.Exec(ctx, "_any", "my-app", gen.ContainerExecRequest{
Command: []string{"cat", "/etc/os-release"},
})
if err != nil {
return nil, err
}
return &orchestrator.Result{
Changed: false,
Data: map[string]any{"stdout": resp.Data.Results[0].Stdout},
}, nil
},
)
checkOS.DependsOn(create)

cleanup := plan.TaskFunc("remove-container",
func(ctx context.Context, c *client.Client) (*orchestrator.Result, error) {
force := true
_, err := c.Container.Remove(ctx, "_any", "my-app",
&gen.DeleteNodeContainerByIDParams{Force: &force})
if err != nil {
return nil, err
}
return &orchestrator.Result{Changed: true}, nil
},
)
cleanup.DependsOn(checkOS)

report, err := plan.Run(context.Background())
```

## RuntimeTarget Interface

```go
type RuntimeTarget interface {
Name() string
Runtime() string // "docker", "lxd", "podman"
ExecProvider(ctx context.Context, provider, operation string, data []byte) ([]byte, error)
}
```

`DockerTarget` implements this by running
`docker exec <container> /osapi provider run <provider> <operation> --data '<json>'`.
The host's `osapi` binary is volume-mounted into the container at creation time.

## Example

See
[`examples/sdk/orchestrator/features/container-targeting.go`](https://github.com/retr0h/osapi/blob/main/examples/sdk/orchestrator/features/container-targeting.go)
for a complete working example.
61 changes: 61 additions & 0 deletions docs/docs/sidebar/sdk/orchestrator/operations/container-create.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
sidebar_position: 16
---

# container.create.execute

Create a new container from a specified image.

## Usage

```go
task := plan.TaskFunc("create-container",
func(ctx context.Context, c *client.Client) (*orchestrator.Result, error) {
autoStart := true
resp, err := c.Container.Create(ctx, "_any", gen.ContainerCreateRequest{
Image: "nginx:alpine",
Name: ptr("my-nginx"),
AutoStart: &autoStart,
})
if err != nil {
return nil, err
}
r := resp.Data.Results[0]
return &orchestrator.Result{
Changed: true,
Data: map[string]any{"id": r.ID, "name": r.Name},
}, nil
},
)
```

## Parameters

| Param | Type | Required | Description |
| ------------ | -------- | -------- | --------------------------------------- |
| `image` | string | Yes | Container image reference |
| `name` | string | No | Optional container name |
| `env` | []string | No | Environment variables (KEY=VALUE) |
| `ports` | []string | No | Port mappings (host:container) |
| `volumes` | []string | No | Volume mounts (host:container) |
| `auto_start` | bool | No | Start immediately after creation (true) |

## Target

Accepts any valid target: `_any`, `_all`, a hostname, or a label selector
(`key:value`).

## Idempotency

**Not idempotent.** Always creates a new container. Use guards to prevent
duplicate creation.

## Permissions

Requires `container:write` permission.

## Example

See
[`examples/sdk/orchestrator/features/container-targeting.go`](https://github.com/retr0h/osapi/blob/main/examples/sdk/orchestrator/features/container-targeting.go)
for a complete working example.
59 changes: 59 additions & 0 deletions docs/docs/sidebar/sdk/orchestrator/operations/container-exec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
sidebar_position: 22
---

# container.exec.execute

Execute a command inside a running container.

## Usage

```go
task := plan.TaskFunc("exec-in-container",
func(ctx context.Context, c *client.Client) (*orchestrator.Result, error) {
resp, err := c.Container.Exec(ctx, "_any", "my-nginx", gen.ContainerExecRequest{
Command: []string{"nginx", "-t"},
})
if err != nil {
return nil, err
}
r := resp.Data.Results[0]
return &orchestrator.Result{
Changed: true,
Data: map[string]any{
"exit_code": r.ExitCode,
"stdout": r.Stdout,
},
}, nil
},
)
```

## Parameters

| Param | Type | Required | Description |
| ------------- | -------- | -------- | -------------------------------------- |
| `id` | string | Yes | Container ID (short or full) or name |
| `command` | []string | Yes | Command and arguments to execute |
| `env` | []string | No | Environment variables (KEY=VALUE) |
| `working_dir` | string | No | Working directory inside the container |

## Target

Accepts any valid target: `_any`, `_all`, a hostname, or a label selector
(`key:value`).

## Idempotency

**Not idempotent.** Always returns `Changed: true`. Use guards (`OnlyIfChanged`,
`When`) to control execution.

## Permissions

Requires `container:execute` permission.

## Example

See
[`examples/sdk/orchestrator/features/container-targeting.go`](https://github.com/retr0h/osapi/blob/main/examples/sdk/orchestrator/features/container-targeting.go)
for a complete working example.
50 changes: 50 additions & 0 deletions docs/docs/sidebar/sdk/orchestrator/operations/container-inspect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
sidebar_position: 18
---

# container.inspect.get

Retrieve detailed information about a specific container.

## Usage

```go
task := plan.TaskFunc("inspect-container",
func(ctx context.Context, c *client.Client) (*orchestrator.Result, error) {
resp, err := c.Container.Inspect(ctx, "_any", "my-nginx")
if err != nil {
return nil, err
}
r := resp.Data.Results[0]
return &orchestrator.Result{
Changed: false,
Data: map[string]any{"state": r.State, "image": r.Image},
}, nil
},
)
```

## Parameters

| Param | Type | Required | Description |
| ----- | ------ | -------- | ------------------------------------ |
| `id` | string | Yes | Container ID (short or full) or name |

## Target

Accepts any valid target: `_any`, `_all`, a hostname, or a label selector
(`key:value`).

## Idempotency

**Read-only.** Never modifies state. Always returns `Changed: false`.

## Permissions

Requires `container:read` permission.

## Example

See
[`examples/sdk/orchestrator/features/container-targeting.go`](https://github.com/retr0h/osapi/blob/main/examples/sdk/orchestrator/features/container-targeting.go)
for a complete working example.
Loading
Loading