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
882 changes: 448 additions & 434 deletions docs/gen/gen.md

Large diffs are not rendered by default.

96 changes: 72 additions & 24 deletions docs/gen/osapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,19 @@ resp, err := client.Node.Exec(ctx, osapi.ExecRequest{
- [type Disk](<#Disk>)
- [type DiskResult](<#DiskResult>)
- [type ExecRequest](<#ExecRequest>)
- [type FileChanged](<#FileChanged>)
- [type FileDelete](<#FileDelete>)
- [type FileDeployOpts](<#FileDeployOpts>)
- [type FileDeployResult](<#FileDeployResult>)
- [type FileItem](<#FileItem>)
- [type FileList](<#FileList>)
- [type FileMetadata](<#FileMetadata>)
- [type FileService](<#FileService>)
- [func \(s \*FileService\) Changed\(ctx context.Context, name string, file io.Reader\) \(\*Response\[FileChanged\], error\)](<#FileService.Changed>)
- [func \(s \*FileService\) Delete\(ctx context.Context, name string\) \(\*Response\[FileDelete\], error\)](<#FileService.Delete>)
- [func \(s \*FileService\) Get\(ctx context.Context, name string\) \(\*Response\[FileMetadata\], error\)](<#FileService.Get>)
- [func \(s \*FileService\) List\(ctx context.Context\) \(\*Response\[FileList\], error\)](<#FileService.List>)
- [func \(s \*FileService\) Upload\(ctx context.Context, name string, content \[\]byte\) \(\*Response\[FileUpload\], error\)](<#FileService.Upload>)
- [func \(s \*FileService\) Upload\(ctx context.Context, name string, contentType string, file io.Reader, opts ...UploadOption\) \(\*Response\[FileUpload\], error\)](<#FileService.Upload>)
- [type FileStatusResult](<#FileStatusResult>)
- [type FileUpload](<#FileUpload>)
- [type HealthService](<#HealthService>)
Expand Down Expand Up @@ -140,6 +142,8 @@ resp, err := client.Node.Exec(ctx, osapi.ExecRequest{
- [type TimelineEvent](<#TimelineEvent>)
- [type UnexpectedStatusError](<#UnexpectedStatusError>)
- [func \(e \*UnexpectedStatusError\) Unwrap\(\) error](<#UnexpectedStatusError.Unwrap>)
- [type UploadOption](<#UploadOption>)
- [func WithForce\(\) UploadOption](<#WithForce>)
- [type UptimeResult](<#UptimeResult>)
- [type ValidationError](<#ValidationError>)
- [func \(e \*ValidationError\) Unwrap\(\) error](<#ValidationError.Unwrap>)
Expand Down Expand Up @@ -622,8 +626,21 @@ type ExecRequest struct {
}
```

<a name="FileChanged"></a>
## type [FileChanged](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file_types.go#L63-L67>)

FileChanged represents the result of a change detection check.

```go
type FileChanged struct {
Name string
Changed bool
SHA256 string
}
```

<a name="FileDelete"></a>
## type [FileDelete](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file_types.go#L53-L56>)
## type [FileDelete](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file_types.go#L57-L60>)

FileDelete represents the result of a file deletion.

Expand Down Expand Up @@ -669,7 +686,7 @@ type FileDeployOpts struct {
```

<a name="FileDeployResult"></a>
## type [FileDeployResult](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file_types.go#L59-L63>)
## type [FileDeployResult](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file_types.go#L70-L74>)

FileDeployResult represents the result of a file deploy operation.

Expand All @@ -682,20 +699,21 @@ type FileDeployResult struct {
```

<a name="FileItem"></a>
## type [FileItem](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file_types.go#L33-L37>)
## type [FileItem](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file_types.go#L35-L40>)

FileItem represents file metadata in a list.

```go
type FileItem struct {
Name string
SHA256 string
Size int
Name string
SHA256 string
Size int
ContentType string
}
```

<a name="FileList"></a>
## type [FileList](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file_types.go#L40-L43>)
## type [FileList](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file_types.go#L43-L46>)

FileList is a collection of files with total count.

Expand All @@ -707,20 +725,21 @@ type FileList struct {
```

<a name="FileMetadata"></a>
## type [FileMetadata](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file_types.go#L46-L50>)
## type [FileMetadata](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file_types.go#L49-L54>)

FileMetadata represents metadata for a single file.

```go
type FileMetadata struct {
Name string
SHA256 string
Size int
Name string
SHA256 string
Size int
ContentType string
}
```

<a name="FileService"></a>
## type [FileService](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file.go#L31-L33>)
## type [FileService](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file.go#L49-L51>)

FileService provides file management operations for the Object Store.

Expand All @@ -730,8 +749,17 @@ type FileService struct {
}
```

<a name="FileService.Changed"></a>
### func \(\*FileService\) [Changed](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file.go#L236-L240>)

```go
func (s *FileService) Changed(ctx context.Context, name string, file io.Reader) (*Response[FileChanged], error)
```

Changed computes the SHA\-256 of the provided content and compares it against the stored hash in the Object Store. Returns true if the content differs or the file does not exist yet.

<a name="FileService.Delete"></a>
### func \(\*FileService\) [Delete](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file.go#L131-L134>)
### func \(\*FileService\) [Delete](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file.go#L203-L206>)

```go
func (s *FileService) Delete(ctx context.Context, name string) (*Response[FileDelete], error)
Expand All @@ -740,7 +768,7 @@ func (s *FileService) Delete(ctx context.Context, name string) (*Response[FileDe
Delete removes a file from the Object Store.

<a name="FileService.Get"></a>
### func \(\*FileService\) [Get](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file.go#L100-L103>)
### func \(\*FileService\) [Get](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file.go#L172-L175>)

```go
func (s *FileService) Get(ctx context.Context, name string) (*Response[FileMetadata], error)
Expand All @@ -749,7 +777,7 @@ func (s *FileService) Get(ctx context.Context, name string) (*Response[FileMetad
Get retrieves metadata for a specific file in the Object Store.

<a name="FileService.List"></a>
### func \(\*FileService\) [List](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file.go#L72-L74>)
### func \(\*FileService\) [List](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file.go#L144-L146>)

```go
func (s *FileService) List(ctx context.Context) (*Response[FileList], error)
Expand All @@ -758,16 +786,16 @@ func (s *FileService) List(ctx context.Context) (*Response[FileList], error)
List retrieves all files stored in the Object Store.

<a name="FileService.Upload"></a>
### func \(\*FileService\) [Upload](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file.go#L36-L40>)
### func \(\*FileService\) [Upload](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file.go#L57-L63>)

```go
func (s *FileService) Upload(ctx context.Context, name string, content []byte) (*Response[FileUpload], error)
func (s *FileService) Upload(ctx context.Context, name string, contentType string, file io.Reader, opts ...UploadOption) (*Response[FileUpload], error)
```

Upload uploads a file to the Object Store.
Upload uploads a file to the Object Store via multipart/form\-data. By default, it computes SHA\-256 locally and compares against the stored hash to skip the upload when content is unchanged. Use WithForce to bypass this check.

<a name="FileStatusResult"></a>
## type [FileStatusResult](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file_types.go#L66-L72>)
## type [FileStatusResult](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file_types.go#L77-L83>)

FileStatusResult represents the result of a file status check.

Expand All @@ -782,15 +810,17 @@ type FileStatusResult struct {
```

<a name="FileUpload"></a>
## type [FileUpload](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file_types.go#L26-L30>)
## type [FileUpload](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file_types.go#L26-L32>)

FileUpload represents a successfully uploaded file.

```go
type FileUpload struct {
Name string
SHA256 string
Size int
Name string
SHA256 string
Size int
Changed bool
ContentType string
}
```

Expand Down Expand Up @@ -1555,6 +1585,24 @@ func (e *UnexpectedStatusError) Unwrap() error

Unwrap returns the underlying APIError.

<a name="UploadOption"></a>
## type [UploadOption](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file.go#L36>)

UploadOption configures Upload behavior.

```go
type UploadOption func(*uploadOptions)
```

<a name="WithForce"></a>
### func [WithForce](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/file.go#L44>)

```go
func WithForce() UploadOption
```

WithForce bypasses both SDK\-side pre\-check and server\-side digest check. The file is always uploaded and changed is always true.

<a name="UptimeResult"></a>
## type [UptimeResult](<https://github.com/osapi-io/osapi-sdk/blob/main/pkg/osapi/node_types.go#L90-L94>)

Expand Down
58 changes: 44 additions & 14 deletions docs/osapi/file.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ and delete files that can be deployed to agents via `Node.FileDeploy`.

### Object Store

| Method | Description |
| ------------------- | ----------------------------------- |
| `Upload(ctx, n, d)` | Upload file content to Object Store |
| `List(ctx)` | List all stored files |
| `Get(ctx, name)` | Get file metadata by name |
| `Delete(ctx, name)` | Delete a file from Object Store |
| Method | Description |
| ------------------------------- | ----------------------------------------------- |
| `Upload(ctx, name, ct, r, ...)` | Upload file content to Object Store |
| `Changed(ctx, name, r)` | Check if local content differs from stored file |
| `List(ctx)` | List all stored files |
| `Get(ctx, name)` | Get file metadata by name |
| `Delete(ctx, name)` | Delete a file from Object Store |

### Node File Operations

Expand All @@ -37,22 +38,42 @@ specific host:
| `Vars` | map[string]any | No | Template variables for `"template"` type |
| `Target` | string | Yes | Host target (see Targeting below) |

## Upload Options

| Option | Description |
| ------------- | ------------------------------------------------------- |
| `WithForce()` | Bypass SDK-side and server-side SHA check; always write |

## Usage

```go
// Upload a file
resp, err := client.File.Upload(ctx, "nginx.conf", configBytes)
// Upload a raw file.
resp, err := client.File.Upload(
ctx, "nginx.conf", "raw", bytes.NewReader(data),
)

// Force upload — skip SHA-256 check, always write.
resp, err := client.File.Upload(
ctx, "nginx.conf", "raw", bytes.NewReader(data),
osapi.WithForce(),
)

// List all files
// Check if content differs without uploading.
chk, err := client.File.Changed(
ctx, "nginx.conf", bytes.NewReader(data),
)
fmt.Println(chk.Data.Changed) // true if content differs

// List all files.
resp, err := client.File.List(ctx)

// Get file metadata
// Get file metadata.
resp, err := client.File.Get(ctx, "nginx.conf")

// Delete a file
// Delete a file.
resp, err := client.File.Delete(ctx, "nginx.conf")

// Deploy a raw file to a specific host
// Deploy a raw file to a specific host.
resp, err := client.Node.FileDeploy(ctx, osapi.FileDeployOpts{
ObjectName: "nginx.conf",
Path: "/etc/nginx/nginx.conf",
Expand All @@ -63,7 +84,7 @@ resp, err := client.Node.FileDeploy(ctx, osapi.FileDeployOpts{
Target: "web-01",
})

// Deploy a template file with variables
// Deploy a template file with variables.
resp, err := client.Node.FileDeploy(ctx, osapi.FileDeployOpts{
ObjectName: "app.conf.tmpl",
Path: "/etc/app/config.yaml",
Expand All @@ -75,7 +96,7 @@ resp, err := client.Node.FileDeploy(ctx, osapi.FileDeployOpts{
Target: "_all",
})

// Check file status on a host
// Check file status on a host.
resp, err := client.Node.FileStatus(
ctx, "web-01", "/etc/nginx/nginx.conf",
)
Expand All @@ -89,6 +110,15 @@ hostname, or a label selector (`key:value`).
Object Store operations (`Upload`, `List`, `Get`, `Delete`) are server-side and
do not use targeting.

## Change Detection

`Upload` computes a SHA-256 of the file content locally before uploading. If the
hash matches the stored file, the upload is skipped and `Changed: false` is
returned. Use `WithForce()` to bypass this check.

`Changed` performs the same SHA-256 comparison without uploading. It returns
`Changed: true` when the file does not exist or the content differs.

## Idempotency

`FileDeploy` compares the SHA-256 of the Object Store content against the
Expand Down
19 changes: 13 additions & 6 deletions examples/orchestration/file-deploy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
package main

import (
"bytes"
"context"
"fmt"
"log"
Expand Down Expand Up @@ -74,15 +75,21 @@ func main() {
listen_address = {{ .Vars.listen_address }}
workers = {{ .Facts.cpu_count }}
`)
resp, err := c.File.Upload(ctx, "app.conf.tmpl", tmpl)
resp, err := c.File.Upload(
ctx,
"app.conf.tmpl",
"template",
bytes.NewReader(tmpl),
osapi.WithForce(),
)
if err != nil {
return nil, fmt.Errorf("upload: %w", err)
}

fmt.Printf(" uploaded %s (sha256=%s)\n",
resp.Data.Name, resp.Data.SHA256)
fmt.Printf(" uploaded %s (sha256=%s changed=%v)\n",
resp.Data.Name, resp.Data.SHA256, resp.Data.Changed)

return &orchestrator.Result{Changed: true}, nil
return &orchestrator.Result{Changed: resp.Data.Changed}, nil
},
)

Expand All @@ -92,7 +99,7 @@ workers = {{ .Facts.cpu_count }}
Target: "_all",
Params: map[string]any{
"object_name": "app.conf.tmpl",
"path": "/etc/app/app.conf",
"path": "/tmp/app.conf",
"content_type": "template",
"mode": "0644",
"vars": map[string]any{
Expand All @@ -107,7 +114,7 @@ workers = {{ .Facts.cpu_count }}
Operation: "file.status.get",
Target: "_all",
Params: map[string]any{
"path": "/etc/app/app.conf",
"path": "/tmp/app.conf",
},
})
verify.DependsOn(deploy)
Expand Down
Loading