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
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

!cmd/
!internal/
!dist/
!go.mod
!go.sum
!main.go
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
sidebar_position: 22
---

# docker.image-remove.execute

Remove a container image from the host.

## Usage

```go
task := plan.TaskFunc("remove-image",
func(ctx context.Context, c *client.Client) (*orchestrator.Result, error) {
resp, err := c.Docker.ImageRemove(ctx, "_any", "nginx:latest",
&client.DockerImageRemoveParams{Force: true})
if err != nil {
return nil, err
}

return orchestrator.CollectionResult(resp.Data, resp.RawJSON(),
func(r client.DockerActionResult) orchestrator.HostResult {
return orchestrator.HostResult{
Hostname: r.Hostname,
Changed: r.Changed,
Error: r.Error,
}
},
)
},
)
```

## Parameters

| Param | Type | Required | Description |
| ------- | ------ | -------- | ----------------------------------------- |
| `image` | string | Yes | Image name or ID (e.g., "nginx:latest") |
| `force` | bool | No | Force removal even if the image is in use |

## Target

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

## Idempotency

**Idempotent.** Removing an image that does not exist returns success.

## Permissions

Requires `docker:write` permission.

## Example

See
[`examples/sdk/orchestrator/operations/docker-image-remove.go`](https://github.com/retr0h/osapi/blob/main/examples/sdk/orchestrator/operations/docker-image-remove.go)
for a complete working example.
6 changes: 3 additions & 3 deletions examples/sdk/client/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ func main() {
log.Fatal("OSAPI_TOKEN is required")
}

client := client.New(url, token)
c := client.New(url, token)
ctx := context.Background()

// List all active agents.
list, err := client.Agent.List(ctx)
list, err := c.Agent.List(ctx)
if err != nil {
log.Fatalf("list agents: %v", err)
}
Expand All @@ -68,7 +68,7 @@ func main() {
// Get rich facts for the first agent.
hostname := list.Data.Agents[0].Hostname

resp, err := client.Agent.Get(ctx, hostname)
resp, err := c.Agent.Get(ctx, hostname)
if err != nil {
log.Fatalf("get agent %s: %v", hostname, err)
}
Expand Down
19 changes: 16 additions & 3 deletions examples/sdk/client/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ func main() {
log.Fatal("OSAPI_TOKEN is required")
}

client := client.New(url, token)
c := client.New(url, token)
ctx := context.Background()

// List recent audit entries.
list, err := client.Audit.List(ctx, 10, 0)
list, err := c.Audit.List(ctx, 10, 0)
if err != nil {
log.Fatalf("list audit: %v", err)
}
Expand All @@ -67,7 +67,7 @@ func main() {
// Get a specific audit entry.
id := list.Data.Items[0].ID

entry, err := client.Audit.Get(ctx, id)
entry, err := c.Audit.Get(ctx, id)
if err != nil {
log.Fatalf("get audit %s: %v", id, err)
}
Expand All @@ -77,4 +77,17 @@ func main() {
fmt.Printf(" Path: %s\n", entry.Data.Path)
fmt.Printf(" User: %s\n", entry.Data.User)
fmt.Printf(" Duration: %dms\n", entry.Data.DurationMs)

// Export all audit entries.
export, err := c.Audit.Export(ctx)
if err != nil {
log.Fatalf("export audit: %v", err)
}

fmt.Printf("\nExported: %d entries\n", export.Data.TotalItems)

for _, e := range export.Data.Items {
fmt.Printf(" %s %s %s code=%d\n",
e.ID, e.Method, e.Path, e.ResponseCode)
}
}
6 changes: 3 additions & 3 deletions examples/sdk/client/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ func main() {
log.Fatal("OSAPI_TOKEN is required")
}

client := client.New(url, token)
c := client.New(url, token)
ctx := context.Background()
target := "_any"

// Direct exec — runs a binary with arguments.
exec, err := client.Node.Exec(ctx, client.ExecRequest{
exec, err := c.Node.Exec(ctx, client.ExecRequest{
Target: target,
Command: "uptime",
})
Expand All @@ -64,7 +64,7 @@ func main() {
}

// Shell — interpreted by /bin/sh, supports pipes and redirection.
shell, err := client.Node.Shell(ctx, client.ShellRequest{
shell, err := c.Node.Shell(ctx, client.ShellRequest{
Target: target,
Command: "uname -a",
})
Expand Down
16 changes: 16 additions & 0 deletions examples/sdk/client/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,20 @@ func main() {
fmt.Printf("Remove (%s): id=%s message=%s\n",
r.Hostname, r.ID, r.Message)
}

// Remove the image.
imgRemove, err := c.Docker.ImageRemove(
ctx,
target,
"nginx:alpine",
&client.DockerImageRemoveParams{Force: true},
)
if err != nil {
log.Fatalf("image remove: %v", err)
}

for _, r := range imgRemove.Data.Results {
fmt.Printf("ImageRemove (%s): id=%s message=%s\n",
r.Hostname, r.ID, r.Message)
}
}
18 changes: 9 additions & 9 deletions examples/sdk/client/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ func main() {
log.Fatal("OSAPI_TOKEN is required")
}

client := client.New(url, token)
c := client.New(url, token)
ctx := context.Background()

// Upload a raw file to the Object Store.
content := []byte("listen_address = 0.0.0.0:8080\nworkers = 4\n")
upload, err := client.File.Upload(
upload, err := c.File.Upload(
ctx,
"app.conf",
"raw",
Expand All @@ -65,15 +65,15 @@ func main() {
upload.Data.Name, upload.Data.SHA256, upload.Data.Size, upload.Data.Changed)

// Check if the file has changed without uploading.
chk, err := client.File.Changed(ctx, "app.conf", bytes.NewReader(content))
chk, err := c.File.Changed(ctx, "app.conf", bytes.NewReader(content))
if err != nil {
log.Fatalf("changed: %v", err)
}

fmt.Printf("Changed: name=%s changed=%v\n", chk.Data.Name, chk.Data.Changed)

// Force upload bypasses both SDK-side and server-side checks.
force, err := client.File.Upload(
force, err := c.File.Upload(
ctx,
"app.conf",
"raw",
Expand All @@ -88,7 +88,7 @@ func main() {
force.Data.Name, force.Data.Changed)

// List all stored files.
list, err := client.File.List(ctx)
list, err := c.File.List(ctx)
if err != nil {
log.Fatalf("list: %v", err)
}
Expand All @@ -99,7 +99,7 @@ func main() {
}

// Get metadata for a specific file.
meta, err := client.File.Get(ctx, "app.conf")
meta, err := c.File.Get(ctx, "app.conf")
if err != nil {
log.Fatalf("get: %v", err)
}
Expand All @@ -108,7 +108,7 @@ func main() {
meta.Data.Name, meta.Data.SHA256, meta.Data.Size)

// Deploy the file to an agent.
deploy, err := client.Node.FileDeploy(ctx, client.FileDeployOpts{
deploy, err := c.Node.FileDeploy(ctx, client.FileDeployOpts{
ObjectName: "app.conf",
Path: "/tmp/app.conf",
ContentType: "raw",
Expand All @@ -123,7 +123,7 @@ func main() {
deploy.Data.JobID, deploy.Data.Hostname, deploy.Data.Changed)

// Check file status on the agent.
status, err := client.Node.FileStatus(ctx, "_any", "/tmp/app.conf")
status, err := c.Node.FileStatus(ctx, "_any", "/tmp/app.conf")
if err != nil {
log.Fatalf("status: %v", err)
}
Expand All @@ -132,7 +132,7 @@ func main() {
status.Data.Path, status.Data.Status)

// Clean up — delete the file from the Object Store.
del, err := client.File.Delete(ctx, "app.conf")
del, err := c.File.Delete(ctx, "app.conf")
if err != nil {
log.Fatalf("delete: %v", err)
}
Expand Down
98 changes: 90 additions & 8 deletions examples/sdk/client/health.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
// DEALINGS IN THE SOFTWARE.

// Package main demonstrates the HealthService: liveness, readiness,
// and detailed system status checks.
// and detailed system status including components, NATS info, agents,
// jobs, streams, KV buckets, object stores, and the component registry.
//
// Run with: OSAPI_TOKEN="<jwt>" go run health.go
package main
Expand All @@ -44,32 +45,113 @@ func main() {
log.Fatal("OSAPI_TOKEN is required")
}

client := client.New(url, token)
c := client.New(url, token)
ctx := context.Background()

// Liveness — is the API process running?
live, err := client.Health.Liveness(ctx)
live, err := c.Health.Liveness(ctx)
if err != nil {
log.Fatalf("liveness: %v", err)
}

fmt.Printf("Liveness: %s\n", live.Data.Status)

// Readiness — is the API ready to serve requests?
ready, err := client.Health.Ready(ctx)
ready, err := c.Health.Ready(ctx)
if err != nil {
log.Fatalf("readiness: %v", err)
}

fmt.Printf("Readiness: %s\n", ready.Data.Status)

// Status — detailed system info (requires auth).
status, err := client.Health.Status(ctx)
status, err := c.Health.Status(ctx)
if err != nil {
log.Fatalf("status: %v", err)
}

fmt.Printf("Status: %s\n", status.Data.Status)
fmt.Printf("Version: %s\n", status.Data.Version)
fmt.Printf("Uptime: %s\n", status.Data.Uptime)
s := status.Data
fmt.Printf("\nStatus: %s\n", s.Status)
fmt.Printf("Version: %s\n", s.Version)
fmt.Printf("Uptime: %s\n", s.Uptime)

// Components (nats, jetstream).
if len(s.Components) > 0 {
fmt.Printf("\nComponents:\n")
for name, comp := range s.Components {
if comp.Error != "" {
fmt.Printf(" %-12s %s (error: %s)\n", name, comp.Status, comp.Error)
} else {
fmt.Printf(" %-12s %s\n", name, comp.Status)
}
}
}

// NATS connection info.
if s.NATS != nil {
fmt.Printf("\nNATS: url=%s version=%s\n", s.NATS.URL, s.NATS.Version)
}

// Agent stats.
if s.Agents != nil {
fmt.Printf("\nAgents: %d total, %d ready\n", s.Agents.Total, s.Agents.Ready)
for _, a := range s.Agents.Agents {
fmt.Printf(" %s registered=%s labels=%s\n",
a.Hostname, a.Registered, a.Labels)
}
}

// Job stats.
if s.Jobs != nil {
fmt.Printf("\nJobs: total=%d completed=%d failed=%d processing=%d unprocessed=%d dlq=%d\n",
s.Jobs.Total, s.Jobs.Completed, s.Jobs.Failed,
s.Jobs.Processing, s.Jobs.Unprocessed, s.Jobs.Dlq)
}

// Consumer stats.
if s.Consumers != nil {
fmt.Printf("\nConsumers: %d total\n", s.Consumers.Total)
for _, c := range s.Consumers.Consumers {
fmt.Printf(" %-20s pending=%d ack_pending=%d redelivered=%d\n",
c.Name, c.Pending, c.AckPending, c.Redelivered)
}
}

// Streams.
if len(s.Streams) > 0 {
fmt.Printf("\nStreams:\n")
for _, st := range s.Streams {
fmt.Printf(" %-20s messages=%d bytes=%d consumers=%d\n",
st.Name, st.Messages, st.Bytes, st.Consumers)
}
}

// KV buckets.
if len(s.KVBuckets) > 0 {
fmt.Printf("\nKV Buckets:\n")
for _, b := range s.KVBuckets {
fmt.Printf(" %-20s keys=%d bytes=%d\n", b.Name, b.Keys, b.Bytes)
}
}

// Object stores.
if len(s.ObjectStores) > 0 {
fmt.Printf("\nObject Stores:\n")
for _, o := range s.ObjectStores {
fmt.Printf(" %-20s size=%d\n", o.Name, o.Size)
}
}

// Component registry (agents, API servers, NATS servers).
if len(s.Registry) > 0 {
fmt.Printf("\nRegistry:\n")
for _, e := range s.Registry {
fmt.Printf(" %-6s %-30s status=%-6s age=%-5s cpu=%.1f%% mem=%d",
e.Type, e.Hostname, e.Status, e.Age, e.CPUPercent, e.MemBytes)
if len(e.Conditions) > 0 {
fmt.Printf(" conditions=%v", e.Conditions)
}
fmt.Println()
}
}
}
Loading
Loading