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
6 changes: 3 additions & 3 deletions cmd/client_node_schedule_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ import (
"github.com/osapi-io/osapi/pkg/sdk/client"
)

// clientNodeScheduleCreateCmd represents the cron create command.
// clientNodeScheduleCreateCmd represents the schedule create command.
var clientNodeScheduleCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a cron entry",
Short: "Create a scheduled entry",
Run: func(cmd *cobra.Command, _ []string) {
ctx := cmd.Context()
host, _ := cmd.Flags().GetString("target")
Expand Down Expand Up @@ -92,7 +92,7 @@ func init() {
clientNodeScheduleCmd.AddCommand(clientNodeScheduleCreateCmd)

clientNodeScheduleCreateCmd.PersistentFlags().
String("name", "", "Name for the cron drop-in entry (required)")
String("name", "", "Name for the scheduled entry (required)")
clientNodeScheduleCreateCmd.PersistentFlags().
String("object", "", "Name of the uploaded file in the object store (required)")
clientNodeScheduleCreateCmd.PersistentFlags().
Expand Down
6 changes: 3 additions & 3 deletions cmd/client_node_schedule_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ import (
"github.com/osapi-io/osapi/internal/cli"
)

// clientNodeScheduleDeleteCmd represents the cron delete command.
// clientNodeScheduleDeleteCmd represents the schedule delete command.
var clientNodeScheduleDeleteCmd = &cobra.Command{
Use: "delete",
Short: "Delete a cron entry",
Short: "Delete a scheduled entry",
Run: func(cmd *cobra.Command, _ []string) {
ctx := cmd.Context()
host, _ := cmd.Flags().GetString("target")
Expand Down Expand Up @@ -79,7 +79,7 @@ func init() {
clientNodeScheduleCmd.AddCommand(clientNodeScheduleDeleteCmd)

clientNodeScheduleDeleteCmd.PersistentFlags().
String("name", "", "Name of the cron entry to delete (required)")
String("name", "", "Name of the scheduled entry to delete (required)")

_ = clientNodeScheduleDeleteCmd.MarkPersistentFlagRequired("name")
}
6 changes: 3 additions & 3 deletions cmd/client_node_schedule_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ import (
"github.com/osapi-io/osapi/internal/cli"
)

// clientNodeScheduleGetCmd represents the cron get command.
// clientNodeScheduleGetCmd represents the schedule get command.
var clientNodeScheduleGetCmd = &cobra.Command{
Use: "get",
Short: "Get a cron entry by name",
Short: "Get a scheduled entry by name",
Run: func(cmd *cobra.Command, _ []string) {
ctx := cmd.Context()
host, _ := cmd.Flags().GetString("target")
Expand Down Expand Up @@ -79,7 +79,7 @@ func init() {
clientNodeScheduleCmd.AddCommand(clientNodeScheduleGetCmd)

clientNodeScheduleGetCmd.PersistentFlags().
String("name", "", "Name of the cron entry (required)")
String("name", "", "Name of the scheduled entry (required)")

_ = clientNodeScheduleGetCmd.MarkPersistentFlagRequired("name")
}
4 changes: 2 additions & 2 deletions cmd/client_node_schedule_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ import (
"github.com/osapi-io/osapi/internal/cli"
)

// clientNodeScheduleListCmd represents the cron list command.
// clientNodeScheduleListCmd represents the schedule list command.
var clientNodeScheduleListCmd = &cobra.Command{
Use: "list",
Short: "List all cron entries",
Short: "List all scheduled entries",
Run: func(cmd *cobra.Command, _ []string) {
ctx := cmd.Context()
host, _ := cmd.Flags().GetString("target")
Expand Down
6 changes: 3 additions & 3 deletions cmd/client_node_schedule_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ import (
"github.com/osapi-io/osapi/pkg/sdk/client"
)

// clientNodeScheduleUpdateCmd represents the cron update command.
// clientNodeScheduleUpdateCmd represents the schedule update command.
var clientNodeScheduleUpdateCmd = &cobra.Command{
Use: "update",
Short: "Update a cron entry",
Short: "Update a scheduled entry",
Run: func(cmd *cobra.Command, _ []string) {
ctx := cmd.Context()
host, _ := cmd.Flags().GetString("target")
Expand Down Expand Up @@ -89,7 +89,7 @@ func init() {
clientNodeScheduleCmd.AddCommand(clientNodeScheduleUpdateCmd)

clientNodeScheduleUpdateCmd.PersistentFlags().
String("name", "", "Name of the cron entry to update (required)")
String("name", "", "Name of the scheduled entry to update (required)")
clientNodeScheduleUpdateCmd.PersistentFlags().
String("object", "", "New object to deploy")
clientNodeScheduleUpdateCmd.PersistentFlags().
Expand Down
27 changes: 14 additions & 13 deletions docs/docs/sidebar/features/schedule-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,26 @@ sidebar_position: 9

# Schedule Management

OSAPI manages cron entries on target hosts. It supports two placement modes:
OSAPI manages scheduled entries on target hosts. It supports two placement
modes:

- **Custom schedule** — writes to `/etc/cron.d/{name}` with a 5-field cron
expression
- **Periodic interval** — writes to `/etc/cron.{hourly,daily,weekly,monthly}/`
as executable scripts

Scheduled entries reference scripts stored in the NATS Object Store by name.
Upload a script first with the file management commands, then create a cron
entry pointing at it. This separates script content from scheduling
configuration and enables versioned updates.
Upload a script first with the file management commands, then create an entry
pointing at it. This separates script content from scheduling configuration and
enables versioned updates.

## How It Works

### Object-Based Workflow

The cron provider is a **meta provider**: it does not embed script content
directly. Instead, each cron entry holds an `object` name that references a file
in the NATS Object Store. When the agent deploys or updates a cron entry, it:
directly. Instead, each entry holds an `object` name that references a file in
the NATS Object Store. When the agent deploys or updates an entry, it:

1. Fetches the named object from the Object Store.
2. Writes the script content to the appropriate path under `/etc/cron.d/` or
Expand Down Expand Up @@ -74,7 +75,7 @@ deployed file as a header comment. This means:
marker.
- The list and get operations query the file-state KV to discover managed
entries; manually created files are left untouched.
- State persists in the KV until explicitly removed — deleting a cron entry
- State persists in the KV until explicitly removed — deleting an entry
undeploys the file from disk but preserves the file-state record.

### Template Support
Expand Down Expand Up @@ -102,9 +103,9 @@ Template variables are merged with the agent's system facts and hostname. See

| Operation | Description |
| --------- | -------------------------------------- |
| List | List all osapi-managed cron entries |
| List | List all osapi-managed entries |
| Get | Get a specific entry by name |
| Create | Upload script, then create cron entry |
| Create | Upload script, then create the entry |
| Update | Upload new script version, then update |
| Delete | Undeploy cron file from disk |

Expand All @@ -125,7 +126,7 @@ osapi client node schedule create --target web-01 \
--name logrotate --interval daily \
--object logrotate-script

# List all managed cron entries
# List all managed entries
osapi client node schedule list --target web-01

# Get a specific entry
Expand Down Expand Up @@ -158,15 +159,15 @@ Files are created as root (the agent runs as root). Names must not contain dots

## Undeploy Behavior

Deleting a cron entry **undeploys** the file: it is removed from the filesystem,
but the file-state KV record is preserved. This means:
Deleting an entry **undeploys** the file: it is removed from the filesystem, but
the file-state KV record is preserved. This means:

- Re-creating the entry with the same name and object will detect the prior
state and only write the file if the content differs.
- The KV record serves as an audit trail of what was last deployed.

To remove the file-state record entirely, delete the corresponding file-state
entry via the file management API after removing the cron entry.
entry via the file management API after removing the scheduled entry.

## Supported Platforms

Expand Down
8 changes: 4 additions & 4 deletions docs/docs/sidebar/sdk/client/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ resp, err := client.Hostname.Get(ctx, "_any")

## Services

| Service | Description |
| ------------------------------ | ---------------------------- |
| [Service](services/service.md) | Service management (systemd) |
| [Cron](services/schedule.md) | Cron schedule management |
| Service | Description |
| -------------------------------- | ---------------------------- |
| [Service](services/service.md) | Service management (systemd) |
| [Schedule](services/schedule.md) | Scheduled entry management |

### Software

Expand Down
41 changes: 21 additions & 20 deletions docs/docs/sidebar/sdk/client/services/schedule.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
sidebar_position: 2
---

# Cron
# Schedule

The `Cron` service provides methods for managing cron drop-in files on target
hosts. Access via `client.Cron.List()`, `client.Cron.Create()`, etc.
The `Schedule` service provides methods for managing scheduled entries on target
hosts. On Debian family hosts these are cron drop-in files. Access via
`client.Schedule.List()`, `client.Schedule.Create()`, etc.

## Methods

Expand All @@ -19,11 +20,11 @@ hosts. Access via `client.Cron.List()`, `client.Cron.Create()`, etc.

## Request Types

| Type | Fields |
| ---------------- | --------------------------------------------------------------- |
| `CronCreateOpts` | Name, Object, Schedule\*, Interval\*, User, ContentType, Vars |
| | (\* Schedule and Interval are mutually exclusive; one required) |
| `CronUpdateOpts` | Object, Schedule, User, ContentType, Vars (all optional) |
| Type | Fields |
| -------------------- | --------------------------------------------------------------- |
| `ScheduleCreateOpts` | Name, Object, Schedule\*, Interval\*, User, ContentType, Vars |
| | (\* Schedule and Interval are mutually exclusive; one required) |
| `ScheduleUpdateOpts` | Object, Schedule, User, ContentType, Vars (all optional) |

## Usage

Expand All @@ -32,34 +33,34 @@ import "github.com/osapi-io/osapi/pkg/sdk/client"

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

// List all managed cron entries
resp, err := c.Cron.List(ctx, "web-01")
// List all managed entries
resp, err := c.Schedule.List(ctx, "web-01")
for _, entry := range resp.Data.Results {
fmt.Printf("%s: %s %s %s\n",
entry.Name, entry.Schedule, entry.User, entry.Object)
}

// Get a specific entry
resp, err := c.Cron.Get(ctx, "web-01", "backup-daily")
resp, err := c.Schedule.Get(ctx, "web-01", "backup-daily")

// Create with custom schedule (/etc/cron.d/)
// Object references an uploaded file in the Object Store.
resp, err := c.Cron.Create(ctx, "web-01", client.CronCreateOpts{
resp, err := c.Schedule.Create(ctx, "web-01", client.ScheduleCreateOpts{
Name: "backup-daily",
Schedule: "0 2 * * *",
Object: "backup-script",
User: "root",
})

// Create with interval (/etc/cron.daily/)
resp, err := c.Cron.Create(ctx, "web-01", client.CronCreateOpts{
resp, err := c.Schedule.Create(ctx, "web-01", client.ScheduleCreateOpts{
Name: "logrotate",
Interval: "daily",
Object: "logrotate-script",
})

// Create with template rendering
resp, err := c.Cron.Create(ctx, "web-01", client.CronCreateOpts{
resp, err := c.Schedule.Create(ctx, "web-01", client.ScheduleCreateOpts{
Name: "db-backup",
Schedule: "0 4 * * *",
Object: "db-backup-template",
Expand All @@ -69,19 +70,19 @@ resp, err := c.Cron.Create(ctx, "web-01", client.CronCreateOpts{
})

// Update the schedule and object
resp, err := c.Cron.Update(ctx, "web-01", "backup-daily",
client.CronUpdateOpts{
resp, err := c.Schedule.Update(ctx, "web-01", "backup-daily",
client.ScheduleUpdateOpts{
Schedule: "0 3 * * *",
Object: "backup-script-v2",
})

// Delete an entry
resp, err := c.Cron.Delete(ctx, "web-01", "backup-daily")
resp, err := c.Schedule.Delete(ctx, "web-01", "backup-daily")
```

## Example

- [`examples/sdk/client/cron.go`](https://github.com/osapi-io/osapi/blob/main/examples/sdk/client/cron.go)
- [`examples/sdk/client/schedule.go`](https://github.com/osapi-io/osapi/blob/main/examples/sdk/client/schedule.go)

## Permissions

Expand All @@ -90,7 +91,7 @@ resp, err := c.Cron.Delete(ctx, "web-01", "backup-daily")
| List, Get | `schedule:read` |
| Create, Update, Delete | `schedule:write` |

Cron management is supported on the Debian OS family (Ubuntu, Debian, Raspbian).
On unsupported platforms (Darwin, generic Linux), operations return
Schedule management is supported on the Debian OS family (Ubuntu, Debian,
Raspbian). On unsupported platforms (Darwin, generic Linux), operations return
`status: skipped`. See [Platform Detection](../../platform/detection.md) for
details.
10 changes: 5 additions & 5 deletions docs/docs/sidebar/usage/cli/client/node/schedule/schedule.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ $ osapi client node schedule list --target web-01

## Get

Get a specific cron entry by name:
Get a specific scheduled entry by name:

```bash
$ osapi client node schedule get --target web-01 --name backup-daily
Expand All @@ -36,15 +36,15 @@ $ osapi client node schedule get --target web-01 --name backup-daily

## Create

Upload the script to the Object Store first, then create the cron entry
Upload the script to the Object Store first, then create the scheduled entry
referencing it by object name:

```bash
$ osapi client file upload --name backup-script \
--file /usr/local/bin/backup.sh
```

Then create the cron entry using `--object` to reference the uploaded file:
Then create the entry using `--object` to reference the uploaded file:

```bash
$ osapi client node schedule create --target web-01 \
Expand All @@ -66,7 +66,7 @@ should be rendered with agent facts before being written to disk.

## Update

Update an existing cron entry:
Update an existing scheduled entry:

```bash
$ osapi client node schedule update --target web-01 \
Expand All @@ -83,7 +83,7 @@ Only the fields you specify are updated. If nothing changed, `Changed: false`.

## Delete

Delete a cron entry:
Delete a scheduled entry:

```bash
$ osapi client node schedule delete --target web-01 --name backup-daily
Expand Down
2 changes: 1 addition & 1 deletion docs/docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ const config: Config = {
},
{
type: 'doc',
label: 'Cron',
label: 'Schedule',
docId: 'sidebar/sdk/client/services/schedule'
},
{
Expand Down
Loading