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
7 changes: 5 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ go test -run TestName -v ./internal/job/... # Run a single test
- **`internal/agent/`** - Node agent: consumer/handler/processor pipeline for job execution
- **`internal/provider/`** - Operation implementations: `node/{host,disk,mem,load}`, `network/{dns,ping}`
- **`internal/config/`** - Viper-based config from `osapi.yaml`
- **`pkg/sdk/`** - Go SDK for programmatic REST API access (`client/` client library, `orchestrator/` DAG runner)
- **`pkg/sdk/`** - Go SDK for programmatic REST API access (`client/` client library, `orchestrator/` DAG runner). See @docs/docs/sidebar/sdk/guidelines.md for SDK development rules
- Shared `nats-client` and `nats-server` are sibling repos linked via `replace` in `go.mod`
- **`github/`** - Temporary GitHub org config tooling (`repos.json` for declarative repo settings, `sync.sh` for drift detection via `gh` CLI). Untracked and intended to move to its own repo.

Expand Down Expand Up @@ -173,7 +173,10 @@ Create `internal/api/{domain}/`:

The SDK client library lives in `pkg/sdk/client/`. Its generated HTTP client
uses the same combined OpenAPI spec as the server
(`internal/api/gen/api.yaml`).
(`internal/api/gen/api.yaml`). Follow the rules in
@docs/docs/sidebar/sdk/guidelines.md — especially: never expose `gen`
types in public method signatures, add JSON tags to all result types,
and wrap errors with context.

**When modifying existing API specs:**

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ them to be used as appliances.
| --- | --- |
| [nats-client][] | A Go package for connecting to and interacting with a NATS server |
| [nats-server][] | A Go package for running an embedded NATS server |
| [osapi-orchestrator][] | Declarative infrastructure orchestration DSL built on the OSAPI SDK |

[nats-client]: https://github.com/osapi-io/nats-client
[nats-server]: https://github.com/osapi-io/nats-server
[osapi-orchestrator]: https://github.com/osapi-io/osapi-orchestrator

## 📄 License

Expand Down
24 changes: 7 additions & 17 deletions cmd/client_container_docker_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
"github.com/spf13/cobra"

"github.com/retr0h/osapi/internal/cli"
"github.com/retr0h/osapi/pkg/sdk/client/gen"
"github.com/retr0h/osapi/pkg/sdk/client"
)

// clientContainerDockerCreateCmd represents the clientContainerDockerCreate command.
Expand All @@ -44,25 +44,16 @@ var clientContainerDockerCreateCmd = &cobra.Command{
volumeFlags, _ := cmd.Flags().GetStringSlice("volume")
autoStart, _ := cmd.Flags().GetBool("auto-start")

body := gen.DockerCreateRequest{
opts := client.DockerCreateOpts{
Image: image,
Name: name,
AutoStart: &autoStart,
Env: envFlags,
Ports: portFlags,
Volumes: volumeFlags,
}

if name != "" {
body.Name = &name
}
if len(envFlags) > 0 {
body.Env = &envFlags
}
if len(portFlags) > 0 {
body.Ports = &portFlags
}
if len(volumeFlags) > 0 {
body.Volumes = &volumeFlags
}

resp, err := sdkClient.Docker.Create(ctx, host, body)
resp, err := sdkClient.Docker.Create(ctx, host, opts)
if err != nil {
cli.HandleError(err, logger)
return
Expand All @@ -76,7 +67,6 @@ var clientContainerDockerCreateCmd = &cobra.Command{
if resp.Data.JobID != "" {
fmt.Println()
cli.PrintKV("Job ID", resp.Data.JobID)
fmt.Println()
}

for _, r := range resp.Data.Results {
Expand Down
18 changes: 6 additions & 12 deletions cmd/client_container_docker_exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (
"github.com/spf13/cobra"

"github.com/retr0h/osapi/internal/cli"
"github.com/retr0h/osapi/pkg/sdk/client/gen"
"github.com/retr0h/osapi/pkg/sdk/client"
)

// clientContainerDockerExecCmd represents the clientContainerDockerExec command.
Expand All @@ -43,18 +43,13 @@ var clientContainerDockerExecCmd = &cobra.Command{
envFlags, _ := cmd.Flags().GetStringSlice("env")
workingDir, _ := cmd.Flags().GetString("working-dir")

body := gen.DockerExecRequest{
Command: command,
opts := client.DockerExecOpts{
Command: command,
Env: envFlags,
WorkingDir: workingDir,
}

if len(envFlags) > 0 {
body.Env = &envFlags
}
if workingDir != "" {
body.WorkingDir = &workingDir
}

resp, err := sdkClient.Docker.Exec(ctx, host, id, body)
resp, err := sdkClient.Docker.Exec(ctx, host, id, opts)
if err != nil {
cli.HandleError(err, logger)
return
Expand All @@ -68,7 +63,6 @@ var clientContainerDockerExecCmd = &cobra.Command{
if resp.Data.JobID != "" {
fmt.Println()
cli.PrintKV("Job ID", resp.Data.JobID)
fmt.Println()
}

for _, r := range resp.Data.Results {
Expand Down
1 change: 0 additions & 1 deletion cmd/client_container_docker_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ var clientContainerDockerInspectCmd = &cobra.Command{
if resp.Data.JobID != "" {
fmt.Println()
cli.PrintKV("Job ID", resp.Data.JobID)
fmt.Println()
}

for _, r := range resp.Data.Results {
Expand Down
14 changes: 4 additions & 10 deletions cmd/client_container_docker_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
"github.com/spf13/cobra"

"github.com/retr0h/osapi/internal/cli"
"github.com/retr0h/osapi/pkg/sdk/client/gen"
"github.com/retr0h/osapi/pkg/sdk/client"
)

// clientContainerDockerListCmd represents the clientContainerDockerList command.
Expand All @@ -40,14 +40,9 @@ var clientContainerDockerListCmd = &cobra.Command{
stateFlag, _ := cmd.Flags().GetString("state")
limit, _ := cmd.Flags().GetInt("limit")

params := &gen.GetNodeContainerDockerParams{}

if stateFlag != "" {
state := gen.GetNodeContainerDockerParamsState(stateFlag)
params.State = &state
}
if limit > 0 {
params.Limit = &limit
params := &client.DockerListParams{
State: stateFlag,
Limit: limit,
}

resp, err := sdkClient.Docker.List(ctx, host, params)
Expand All @@ -64,7 +59,6 @@ var clientContainerDockerListCmd = &cobra.Command{
if resp.Data.JobID != "" {
fmt.Println()
cli.PrintKV("Job ID", resp.Data.JobID)
fmt.Println()
}

for _, r := range resp.Data.Results {
Expand Down
7 changes: 3 additions & 4 deletions cmd/client_container_docker_pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
"github.com/spf13/cobra"

"github.com/retr0h/osapi/internal/cli"
"github.com/retr0h/osapi/pkg/sdk/client/gen"
"github.com/retr0h/osapi/pkg/sdk/client"
)

// clientContainerDockerPullCmd represents the clientContainerDockerPull command.
Expand All @@ -39,11 +39,11 @@ var clientContainerDockerPullCmd = &cobra.Command{
host, _ := cmd.Flags().GetString("target")
image, _ := cmd.Flags().GetString("image")

body := gen.DockerPullRequest{
opts := client.DockerPullOpts{
Image: image,
}

resp, err := sdkClient.Docker.Pull(ctx, host, body)
resp, err := sdkClient.Docker.Pull(ctx, host, opts)
if err != nil {
cli.HandleError(err, logger)
return
Expand All @@ -57,7 +57,6 @@ var clientContainerDockerPullCmd = &cobra.Command{
if resp.Data.JobID != "" {
fmt.Println()
cli.PrintKV("Job ID", resp.Data.JobID)
fmt.Println()
}

for _, r := range resp.Data.Results {
Expand Down
7 changes: 3 additions & 4 deletions cmd/client_container_docker_remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
"github.com/spf13/cobra"

"github.com/retr0h/osapi/internal/cli"
"github.com/retr0h/osapi/pkg/sdk/client/gen"
"github.com/retr0h/osapi/pkg/sdk/client"
)

// clientContainerDockerRemoveCmd represents the clientContainerDockerRemove command.
Expand All @@ -40,9 +40,9 @@ var clientContainerDockerRemoveCmd = &cobra.Command{
id, _ := cmd.Flags().GetString("id")
force, _ := cmd.Flags().GetBool("force")

params := &gen.DeleteNodeContainerDockerByIDParams{}
var params *client.DockerRemoveParams
if force {
params.Force = &force
params = &client.DockerRemoveParams{Force: true}
}

resp, err := sdkClient.Docker.Remove(ctx, host, id, params)
Expand All @@ -59,7 +59,6 @@ var clientContainerDockerRemoveCmd = &cobra.Command{
if resp.Data.JobID != "" {
fmt.Println()
cli.PrintKV("Job ID", resp.Data.JobID)
fmt.Println()
}

for _, r := range resp.Data.Results {
Expand Down
1 change: 0 additions & 1 deletion cmd/client_container_docker_start.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ var clientContainerDockerStartCmd = &cobra.Command{
if resp.Data.JobID != "" {
fmt.Println()
cli.PrintKV("Job ID", resp.Data.JobID)
fmt.Println()
}

for _, r := range resp.Data.Results {
Expand Down
9 changes: 4 additions & 5 deletions cmd/client_container_docker_stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
"github.com/spf13/cobra"

"github.com/retr0h/osapi/internal/cli"
"github.com/retr0h/osapi/pkg/sdk/client/gen"
"github.com/retr0h/osapi/pkg/sdk/client"
)

// clientContainerDockerStopCmd represents the clientContainerDockerStop command.
Expand All @@ -40,12 +40,12 @@ var clientContainerDockerStopCmd = &cobra.Command{
id, _ := cmd.Flags().GetString("id")
timeout, _ := cmd.Flags().GetInt("timeout")

body := gen.DockerStopRequest{}
opts := client.DockerStopOpts{}
if cmd.Flags().Changed("timeout") {
body.Timeout = &timeout
opts.Timeout = timeout
}

resp, err := sdkClient.Docker.Stop(ctx, host, id, body)
resp, err := sdkClient.Docker.Stop(ctx, host, id, opts)
if err != nil {
cli.HandleError(err, logger)
return
Expand All @@ -59,7 +59,6 @@ var clientContainerDockerStopCmd = &cobra.Command{
if resp.Data.JobID != "" {
fmt.Println()
cli.PrintKV("Job ID", resp.Data.JobID)
fmt.Println()
}

for _, r := range resp.Data.Results {
Expand Down
1 change: 1 addition & 0 deletions docs/docs/sidebar/sdk/client/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ resp, err := client.Node.Hostname(ctx, "_any")
| --------------------- | ---------------------------------- |
| [Agent](agent.md) | Agent discovery and details |
| [Audit](audit.md) | Audit log operations |
| [Docker](docker.md) | Container runtime operations |
| [File](file.md) | File management (Object Store) |
| [Health](health.md) | Health check operations |
| [Job](job.md) | Async job queue operations |
Expand Down
90 changes: 90 additions & 0 deletions docs/docs/sidebar/sdk/client/docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
sidebar_position: 3
---

# DockerService

Docker container lifecycle management — create, list, inspect, start, stop,
remove, exec, and pull operations.

## Methods

| Method | Description |
| ------------------------------- | -------------------------------- |
| `Create(ctx, hostname, opts)` | Create a new container |
| `List(ctx, hostname, params)` | List containers |
| `Inspect(ctx, hostname, id)` | Get detailed container info |
| `Start(ctx, hostname, id)` | Start a stopped container |
| `Stop(ctx, hostname, id, opts)` | Stop a running container |
| `Remove(ctx, hostname, id, p)` | Remove a container |
| `Exec(ctx, hostname, id, opts)` | Execute a command in a container |
| `Pull(ctx, hostname, opts)` | Pull a container image |

## Request Types

The Docker service uses SDK-defined request types. Consumers never need to
import `gen`.

| Type | Fields |
| -------------------- | ---------------------------------------------------- |
| `DockerCreateOpts` | Image, Name, Command, Env, Ports, Volumes, AutoStart |
| `DockerStopOpts` | Timeout |
| `DockerListParams` | State, Limit |
| `DockerRemoveParams` | Force |
| `DockerPullOpts` | Image |
| `DockerExecOpts` | Command |

## Usage

```go
import "github.com/retr0h/osapi/pkg/sdk/client"

c := client.New("http://localhost:8080", token)

// Pull an image
resp, err := c.Docker.Pull(ctx, "_any", client.DockerPullOpts{
Image: "nginx:latest",
})

// Create a container
autoStart := true
resp, err := c.Docker.Create(ctx, "_any", client.DockerCreateOpts{
Image: "nginx:latest",
Name: "web",
Ports: []string{"8080:80"},
AutoStart: &autoStart,
})

// List running containers
resp, err := c.Docker.List(ctx, "_any", &client.DockerListParams{
State: "running",
})

// Execute a command
resp, err := c.Docker.Exec(ctx, "_any", "web", client.DockerExecOpts{
Command: []string{"hostname"},
})

// Stop with timeout
resp, err := c.Docker.Stop(ctx, "_any", "web", client.DockerStopOpts{
Timeout: 30,
})

// Force remove
resp, err := c.Docker.Remove(ctx, "_any", "web", &client.DockerRemoveParams{
Force: true,
})
```

## Permissions

| Operation | Permission |
| --------- | ---------------- |
| Create | `docker:write` |
| List | `docker:read` |
| Inspect | `docker:read` |
| Start | `docker:write` |
| Stop | `docker:write` |
| Remove | `docker:write` |
| Exec | `docker:execute` |
| Pull | `docker:write` |
Loading
Loading