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,156 changes: 11 additions & 2,145 deletions README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/contribution-notes/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "label": "Contribution Notes", "position": 1 }
100 changes: 100 additions & 0 deletions docs/contribution-notes/contributing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Contributing to xp-clifford

## General Remarks

You are welcome to contribute content (code, documentation, etc.) to this project. There are some important things to know:

1. You must **comply with the license of this project** and **accept the Developer Certificate of Origin** (see below) before being able to contribute.
2. Please **adhere to our [Code of Conduct](https://github.com/SAP/.github/blob/main/CODE_OF_CONDUCT.md)**.
3. If you plan to use **generative AI for your contribution**, please see our [guideline for AI-generated code](https://github.com/SAP/.github/blob/main/CONTRIBUTING_USING_GENAI.md).
4. **Not all proposed contributions can be accepted**. Some features
may fit another project better or don't fit the general direction
of this project. The more effort you invest, the better you should
clarify in advance whether the contribution will match the
project's direction. Open an issue to discuss the feature you plan
to implement (make it clear that you intend to contribute). We will
then forward the proposal to the respective code owner.

## Developer Certificate of Origin (DCO)

Contributors will be asked to accept a DCO before they submit the first pull request to this project, this happens in an automated fashion during the submission process. SAP uses [the standard DCO text of the Linux Foundation](https://developercertificate.org/).

## How to Contribute

1. Make sure the change is welcome (see [General Remarks](#general-remarks)).
2. Create a branch by forking the repository and apply your change.
3. Commit and push your change on that branch.
4. Create a pull request in the repository using this branch.
5. Follow the link posted by the CLA assistant to your pull request and accept it, as described above.
6. Wait for our code review and approval, possibly enhancing your change on request.
7. Once the change has been approved and merged, we will inform you in a comment.

## Development Environment

The `xp-clifford` repository provides a reproducible development environment powered by [Nix](https://nixos.org/). This environment includes the Go compiler, linters, and all tools needed to contribute to the project.

### Prerequisites

1. Install the [Nix package manager](https://nixos.org/download/)
2. Enable [flake support](https://nixos.wiki/wiki/Flakes)

### Getting Started

After cloning the repository, enter the development environment:

```sh
nix develop --no-pure-eval
```

> **Note:** The initial setup may take several minutes as dependencies are downloaded and configured.

Upon entering the environment, [pre-commit](https://pre-commit.com/) hooks are automatically installed. These hooks run various checks before each Git commit to ensure code quality.

### Direnv Integration (Optional)

For a seamless experience, you can use [direnv](https://direnv.net/) to automatically activate the development environment when entering the project directory.

1. [Install direnv](https://direnv.net/docs/installation.html)
2. Navigate to the cloned repository
3. Allow the direnv configuration:

```sh
direnv allow
```

## Project Structure

```text
xp-clifford/
├── cli/ # Core CLI framework
│ ├── configparam/ # Typed configuration parameters
│ ├── export/ # Export subcommand framework
│ └── widget/ # Interactive CLI widgets
├── erratt/ # Errors with structured attributes
├── examples/ # Working examples for each feature
├── mkcontainer/ # Multi-key container package
├── parsan/ # Parsing and sanitization utilities
├── yaml/ # YAML output helpers (commented resources)
├── flake.nix # Nix flake for dev environment and examples
└── go.mod
```

## Running Tests

```sh
go test ./...
```

## Running the Example CLI

Using Nix:

```sh
nix run .#example
```

Using Go:

```sh
go run ./examples/\<example-name\>/main.go \<command\>
```
1 change: 1 addition & 0 deletions docs/end-user-guides/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "label": "End User Guides", "position": 0 }
129 changes: 129 additions & 0 deletions docs/end-user-guides/configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Configuration Parameters

`xp-clifford` provides typed configuration parameters that support three input methods with a defined precedence:

1. **CLI flags** (highest precedence)
2. **Environment variables**
3. **Configuration file** (lowest precedence)

## Supported Types

| Type | Constructor | Go Type |
|-------------|---------------------------------------|-----------------|
| Bool | `configparam.Bool(name, desc)` | `bool` |
| Int | `configparam.Int(name, desc)` | `int` |
| Float | `configparam.Float(name, desc)` | `float64` |
| Duration | `configparam.Duration(name, desc)` | `time.Duration` |
| String | `configparam.String(name, desc)` | `string` |
| StringSlice | `configparam.StringSlice(name, desc)` | `[]string` |
| IntSlice | `configparam.IntSlice(name, desc)` | `[]int` |

## Defining a Parameter

Each parameter supports optional chaining:

```go
var usernameParam = configparam.String("username", "username for authentication").
WithShortName("u").
WithEnvVarName("USERNAME").
WithDefaultValue("testuser")
```

Available methods:

- **`WithShortName(s)`** — Single-character CLI flag
- **`WithFlagName(s)`** — Long CLI flag (defaults to `name`)
- **`WithEnvVarName(s)`** — Environment variable name
- **`WithDefaultValue(v)`** — Default value

## Reading Values

```go
usernameParam.Value() // current value
usernameParam.IsSet() // true if explicitly set by user
```

### Interactive Prompting

All parameter types support `ValueOrAsk(ctx)` — returns the value if set, otherwise prompts interactively:

```go
username, err := usernameParam.ValueOrAsk(ctx)
```

Slice types (`StringSlice`, `IntSlice`) support `WithPossibleValues` or `WithPossibleValuesFn` to define the selection options:

```go
var protocolParam = configparam.StringSlice("protocol", "supported protocols").
WithShortName("p").
WithEnvVarName("PROTOCOLS").
WithPossibleValues([]string{"HTTP", "HTTPS", "FTP", "SSH", "SFTP"})
```

Dynamic options based on runtime state:

```go
func possibleProtocols() ([]string, error) {
if secureParam.Value() {
return []string{"HTTPS", "SFTP", "SSH"}, nil
}
return []string{"FTP", "HTTP"}, nil
}

var protocolParam = configparam.StringSlice("protocol", "supported protocols").
WithPossibleValuesFn(possibleProtocols)
```

## Registering Parameters

### Export subcommand

```go
export.AddConfigParams(usernameParam, protocolParam)
```

### Custom subcommands

Set `ConfigParams` on a `BasicSubCommand`:

```go
var loginCmd = &cli.BasicSubCommand{
Name: "login",
Short: "Login subcommand",
ConfigParams: []configparam.ConfigParam{usernameParam},
Run: loginLogic,
}
```

## Built-in Flags

The `export` subcommand includes:

| Flag | Type | Description |
|------------------|------------|---------------------------|
| `-k`, `--kind` | `[]string` | Resource kinds to export |
| `-o`, `--output` | `string` | Redirect output to a file |

Global flags:

| Flag | Type | Description |
|-------------------|----------|----------------------------|
| `-c`, `--config` | `string` | Path to configuration file |
| `-v`, `--verbose` | `bool` | Enable debug-level logging |

## Configuration File

YAML file with lowercase parameter names as keys:

```yaml
protocol:
- HTTP
- FTP
username: config-user
bool: true
```

Specify via `-c` flag, or auto-discovered from:

1. `$XDG_CONFIG_HOME/export-cli-config-\<shortname\>`
2. `$HOME/export-cli-config-\<shortname\>`
67 changes: 67 additions & 0 deletions docs/end-user-guides/error-handling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Error Handling with Attributes

The `erratt` package provides an error type with structured key-value attributes, designed for use with `slog` and the `EventHandler.Warn` method.

## Creating Errors

```go
// Simple error
err := erratt.New("something went wrong")

// Error with attributes
err := erratt.New("error opening file", "filename", filename)
err := erratt.New("authentication failed", "username", user, "password", pass)
```

## Wrapping Errors

Wrap existing Go errors while preserving attributes:

```go
err := callFunction()
wrapped := erratt.Errorf("unexpected error: %w", err)
```

## Adding Attributes

Chain `With()` to add context:

```go
err := connectToServer(url, user, pass)
wrapped := erratt.Errorf("cannot connect: %w", err).
With("url", url, "username", user)
```

## Full Example

```go
func auth() erratt.Error {
return erratt.New("authentication failure",
"username", "test-user",
"password", "test-password",
)
}

func connect() erratt.Error {
err := auth()
if err != nil {
return erratt.Errorf("connect failed: %w", err).
With("url", "https://example.com")
}
return nil
}
```

Output via `slog`:

```text
ERRO connect failed: authentication failure url=https://example.com username=test-user password=test-password
```

## Integration with EventHandler

The `Warn` method handles `erratt.Error` values, preserving structured attributes in the log output:

```go
events.Warn(erratt.New("resource skipped", "kind", kind, "name", name))
```
90 changes: 90 additions & 0 deletions docs/end-user-guides/exporting-resources.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Exporting Resources

The `export` subcommand is mandatory but requires you to implement the export logic.

## Export Function Signature

```go
func(ctx context.Context, events export.EventHandler) error
```

- **`ctx`** — Use `ctx.Done()` to handle interrupts (e.g., Ctrl-C)
- **`events`** — Communicates progress to the framework via three methods:
- `Warn(err error)` — Recoverable error, does not stop the export
- `Resource(res resource.Object)` — A processed managed resource
- `Stop()` — Signals completion; no further calls allowed
- **return** — Return a non-nil error to indicate a fatal failure

Register the function with `export.SetCommand`:

```go
export.SetCommand(exportLogic)
```

## Exporting a Single Resource

Use `events.Resource()` to output a resource. Any type implementing `resource.Object` works:

```go
func exportLogic(_ context.Context, events export.EventHandler) error {
res := &unstructured.Unstructured{
Object: map[string]interface{}{
"user": "test-user",
"password": "secret",
},
}
events.Resource(res)
events.Stop()
return nil
}
```

Output is printed to stdout. Redirect to a file with `-o`:

```sh
test-exporter export -o output.yaml
```

## Displaying Warnings

Report non-fatal issues during export:

```go
events.Warn(errors.New("resource skipped due to missing field"))
```

Warnings appear on stderr but **not** in the output file.

## Commented Export

Problematic resources can be included in the output but commented out, preventing accidental application.

Wrap a resource with `yaml.NewResourceWithComment`:

```go
commentedResource := yaml.NewResourceWithComment(res)
commentedResource.SetComment("incomplete resource — do not apply")
events.Resource(commentedResource)
```

This produces:

```yaml
#
# incomplete resource — do not apply
#
# ---
# password: secret
# user: test-user
# ...
```

The wrapped resource must implement the `yaml.CommentedYAML` interface:

```go
type CommentedYAML interface {
Comment() (string, bool)
}
```

`bool` indicates whether to comment out; `string` provides the comment message.
Loading
Loading