{"id":43228690,"url":"https://github.com/sap/xp-clifford","last_synced_at":"2026-02-01T10:00:45.405Z","repository":{"id":335457461,"uuid":"1129561665","full_name":"SAP/xp-clifford","owner":"SAP","description":"Crossplane Provider Export CLI framework for resource data extraction","archived":false,"fork":false,"pushed_at":"2026-01-30T10:26:13.000Z","size":400,"stargazers_count":1,"open_issues_count":9,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-31T00:28:53.609Z","etag":null,"topics":["crossplane","framework","tooling"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SAP.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-07T09:08:25.000Z","updated_at":"2026-01-30T08:49:03.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/SAP/xp-clifford","commit_stats":null,"previous_names":["sap/xp-clifford"],"tags_count":null,"template":false,"template_full_name":"SAP/repository-template","purl":"pkg:github/SAP/xp-clifford","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SAP%2Fxp-clifford","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SAP%2Fxp-clifford/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SAP%2Fxp-clifford/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SAP%2Fxp-clifford/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SAP","download_url":"https://codeload.github.com/SAP/xp-clifford/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SAP%2Fxp-clifford/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28975278,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-01T09:57:52.632Z","status":"ssl_error","status_checked_at":"2026-02-01T09:57:49.143Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["crossplane","framework","tooling"],"created_at":"2026-02-01T10:00:35.270Z","updated_at":"2026-02-01T10:00:45.396Z","avatar_url":"https://github.com/SAP.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![REUSE status](https://api.reuse.software/badge/github.com/SAP/xp-clifford)](https://api.reuse.software/info/github.com/SAP/xp-clifford)\n\n# xp-clifford\n\n## About this project\n\n`xp-clifford` (Crossplane CLI Framework for Resource Data Extraction) is a Go module that facilitates the development of CLI tools for exporting definitions of external resources in the format of specific Crossplane provider managed resource definitions.\n\nThe resource definitions can then be imported into Crossplane using the [standard import procedure](https://docs.crossplane.io/v2.1/guides/import-existing-resources/). It is recommended to check the generated definitions for comments, before doing the import. See also [Exporting commented out resources](#commented-export).\n\n## Requirements\n\n`xp-clifford` is a Go module and requires only a working Go development environment.\n\n## Setup\n\nTo install the `xp-clifford` Go module, run the following command:\n\n```sh\ngo get github.com/SAP/xp-clifford\n```\n\n## Support, Feedback, Contributing\n\nThis project is open to feature requests/suggestions, bug reports etc. via [GitHub issues](\u003chttps://github.com/SAP/xp-clifford/issues\u003e). Contribution and feedback are encouraged and always welcome. For more information about how to contribute, the project structure, as well as additional contribution information, see our [Contribution Guidelines](CONTRIBUTING.md).\n\n\n## Security / Disclosure\n\nIf you find any bug that may be a security problem, please follow our instructions at [in our security policy](https://github.com/SAP/xp-clifford/security/policy) on how to report it. Please do not create GitHub issues for security-related doubts or problems.\n\n\n## Code of Conduct\n\nWe as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone. By participating in this project, you agree to abide by its [Code of Conduct](\u003chttps://github.com/SAP/.github/blob/main/CODE_OF_CONDUCT.md\u003e) at all times.\n\n\n## Licensing\n\nCopyright 2026 SAP SE or an SAP affiliate company and xp-clifford contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](\u003chttps://api.reuse.software/info/github.com/SAP/xp-clifford\u003e).\n\n\n## Examples\n\nThese examples demonstrate the basic features of `xp-clifford` and build progressively on one another.\n\n\n### The simplest CLI tool\n\nThe simplest CLI tool you can create using `xp-clifford` looks like this:\n\n```go\npackage main\n\nimport (\n\t\"github.com/SAP/xp-clifford/cli\"\n\t_ \"github.com/SAP/xp-clifford/cli/export\"\n)\n\nfunc main() {\n\tcli.Configuration.ShortName = \"test\"\n\tcli.Configuration.ObservedSystem = \"test system\"\n\tcli.Execute()\n}\n```\n\nLet's examine the `import` section.\n\n```go\nimport (\n\t\"github.com/SAP/xp-clifford/cli\"\n\t_ \"github.com/SAP/xp-clifford/cli/export\"\n)\n```\n\nTwo packages must be imported:\n\n-   `github.com/SAP/xp-clifford/cli`\n-   `github.com/SAP/xp-clifford/cli/export`\n\nThe `cli/export` package is imported for side effects only.\n\nThe `main` function looks like this:\n\n```go\nfunc main() {\n\tcli.Configuration.ShortName = \"test\"\n\tcli.Configuration.ObservedSystem = \"test system\"\n\tcli.Execute()\n}\n```\n\nThe `Configuration` variable from the `cli` package is used to set specific parameters for the built CLI tool. Here we set the `ShortName` and `ObservedSystem` fields.\n\nThese fields have the following meanings:\n\n-   **ShortName:** The abbreviated name of the observed system without spaces, such as \"cf\" for the CloudFoundry provider\n-   **ObservedSystem:** The full name of the external system, which may contain spaces, such as \"Cloud Foundry\"\n\nAt the end of the `main` function, we invoke the `Execute` function from the `cli` package to start the CLI.\n\nWhen we run this basic example, it generates the following output:\n\n```sh\ngo run ./examples/basic/main.go\n```\n\n```\ntest system exporting tool is a CLI tool for exporting existing resources as Crossplane managed resources\n\nUsage:\n  test-exporter [command]\n\nAvailable Commands:\n  completion  Generate the autocompletion script for the specified shell\n  export      Export test system resources\n  help        Help about any command\n\nFlags:\n  -c, --config string   Configuration file\n  -h, --help            help for test-exporter\n  -v, --verbose         Verbose output\n\nUse \"test-exporter [command] --help\" for more information about a command.\n```\n\nIf you try running the CLI tool with the export subcommand, you get an **error** message.\n\n```sh\ngo run ./examples/basic/main.go export\n```\n\n    ERRO export subcommand is not set\n\n\n### Exporting\n\n\n#### Basic export subcommand\n\nThe `export` subcommand is mandatory, but you are responsible for implementing the code that executes when it is invoked.\n\nThe code must be defined as a function with the following signature:\n\n```go\nfunc(ctx context.Context, events export.EventHandler) error\n```\n\nThe `ctx` parameter can be used to handle interruptions, such as when the user presses *Ctrl-C*. In such cases, the `Done()` channel of the context is closed.\n\nThe `events` parameter from the `export` package provides three methods for communicating progress to the CLI framework:\n\n-   **Warn:** Indicates a recoverable error that does not terminate the export operation.\n-   **Resource:** Indicates a processed managed resource to be printed or stored by the export operation.\n-   **Stop:** Indicates that exporting has finished. No more `Warn` or `Resource` calls should be made after `Stop`.\n\nA fatal error can be indicated by returning a non-nil error value.\n\nA simple implementation of an export logic function looks like this:\n\n```go\nfunc exportLogic(_ context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\")\n\tevents.Stop()\n\treturn nil\n}\n```\n\nThis implementation prints a log message, stops the event handler, and returns a `nil` error value.\n\nYou can configure the business logic function using the `SetCommand` function from the `export` package:\n\n```go\nexport.SetCommand(exportLogic)\n```\n\nA complete example is:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\n\t\"github.com/SAP/xp-clifford/cli\"\n\t\"github.com/SAP/xp-clifford/cli/export\"\n)\n\nfunc exportLogic(_ context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\")\n\tevents.Stop()\n\treturn nil\n}\n\nfunc main() {\n\tcli.Configuration.ShortName = \"test\"\n\tcli.Configuration.ObservedSystem = \"test system\"\n\texport.SetCommand(exportLogic)\n\tcli.Execute()\n}\n```\n\nTo invoke the `export` subcommand:\n\n```sh\ngo run ./examples/export/main.go export\n```\n\n    INFO export command invoked\n\n\n#### Exporting a resource\n\nIn the previous example, we created a proper `export` subcommand, but didn't actually export any resources.\n\nTo export a resource, use the `Resource` method of the `EventHandler` type:\n\n```go\nResource(res resource.Object) // Object interface defined in\n                              // github.com/crossplane/crossplane-runtime/pkg/resource\n```\n\nThis method accepts a `resource.Object`, an interface implemented by all Crossplane resources.\n\nLet's update our `exportLogic` function to export a single resource. For simplicity, we'll use the `Unstructured` type from `k8s.io/apimachinery/pkg/apis/meta/v1/unstructured`, which implements the `resource.Object` interface:\n\n```go\nfunc exportLogic(_ context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\")\n\n\tres := \u0026unstructured.Unstructured{\n\t  Object: map[string]interface{}{\n\t      \"user\": \"test-user\",\n\t      \"password\": \"secret\",\n\t  },\n\t}\n\tevents.Resource(res)\n\n\tevents.Stop()\n\treturn nil\n}\n```\n\nThe complete example now looks like this:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\n\t\"github.com/SAP/xp-clifford/cli\"\n\t\"github.com/SAP/xp-clifford/cli/export\"\n\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n)\n\nfunc exportLogic(_ context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\")\n\n\tres := \u0026unstructured.Unstructured{\n\t  Object: map[string]interface{}{\n\t      \"user\": \"test-user\",\n\t      \"password\": \"secret\",\n\t  },\n\t}\n\tevents.Resource(res)\n\n\tevents.Stop()\n\treturn nil\n}\n\nfunc main() {\n\tcli.Configuration.ShortName = \"test\"\n\tcli.Configuration.ObservedSystem = \"test system\"\n\texport.SetCommand(exportLogic)\n\tcli.Execute()\n}\n```\n\nRunning this example produces the following output:\n\n```sh\ngo run ./examples/exportsingle/main.go export\n```\n\n    INFO export command invoked\n\n\n        ---\n        password: secret\n        user: test-user\n        ...\n\nThe exported resource is printed to the console. You can redirect the output to a file using the `-o` flag:\n\n```sh\ngo run ./examples/exportsingle/main.go export -o output.yaml\n```\n\n    INFO export command invoked\n    INFO Writing output to file output=output.yaml\n\nThe `output.yaml` file contains the exported resource object:\n\n```sh\ncat output.yaml\n```\n\n    ---\n    password: secret\n    user: test-user\n    ...\n\n\n#### Displaying warnings\n\nDuring the processing and conversion of external resources, the export logic may encounter unexpected situations such as unstable network connections, authentication issues, or unknown resource configurations.\n\nThese events should not halt the resource export process, but they must be reported to the user.\n\nYou can report warnings using the `Warn` method of the `EventHandler` type:\n\n```go\nWarn(err error)\n```\n\nThe `Warn` method supports `erratt.Error` types. The `erratt.Error` type is demonstrated in [6.3](#erratt-example).\n\nLet's add a warning message to our `exportLogic` function:\n\n```go\nfunc exportLogic(_ context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\")\n\n\tevents.Warn(errors.New(\"generating test resource\"))\n\n\tres := \u0026unstructured.Unstructured{\n\t  Object: map[string]interface{}{\n\t      \"user\": \"test-user-with-warning\",\n\t      \"password\": \"secret\",\n\t  },\n\t}\n\tevents.Resource(res)\n\n\tevents.Stop()\n\treturn nil\n}\n```\n\nThe complete example now looks like this:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"log/slog\"\n\n\t\"github.com/SAP/xp-clifford/cli\"\n\t\"github.com/SAP/xp-clifford/cli/export\"\n\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n)\n\nfunc exportLogic(_ context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\")\n\n\tevents.Warn(errors.New(\"generating test resource\"))\n\n\tres := \u0026unstructured.Unstructured{\n\t  Object: map[string]interface{}{\n\t      \"user\": \"test-user-with-warning\",\n\t      \"password\": \"secret\",\n\t  },\n\t}\n\tevents.Resource(res)\n\n\tevents.Stop()\n\treturn nil\n}\n\nfunc main() {\n\tcli.Configuration.ShortName = \"test\"\n\tcli.Configuration.ObservedSystem = \"test system\"\n\texport.SetCommand(exportLogic)\n\tcli.Execute()\n}\n```\n\nRunning this example displays the warning message in the logs:\n\n```sh\ngo run ./examples/exportwarn/main.go export\n```\n\n    INFO export command invoked\n    WARN generating test resource\n\n\n        ---\n        password: secret\n        user: test-user-with-warning\n        ...\n\nWhen redirecting the output to a file, the warning appears on screen but not in the file:\n\n```sh\ngo run ./examples/exportwarn/main.go export -o output.yaml\n```\n\n    INFO export command invoked\n    WARN generating test resource\n    INFO Writing output to file output=output.yaml\n\n```sh\ncat output.yaml\n```\n\n    ---\n    password: secret\n    user: test-user-with-warning\n    ...\n\n\n\u003ca id=\"commented-export\"\u003e\u003c/a\u003e\n\n#### Exporting commented out resources\n\nDuring the export process, problems may prevent generation of valid managed resource definitions, or the definitions produced may be unsafe to apply.\n\nYou have two options for handling problematic resources: omit them from the output entirely, or include them but commented out. Commenting out invalid or unsafe resource definitions ensures users won't encounter problems when applying the export tool output.\n\n`xp-clifford` comments out resources that implement the `yaml.CommentedYAML` interface, which defines a single method:\n\n```go\ntype CommentedYAML interface {\n\tComment() (string, bool)\n}\n```\n\nThe `bool` return value indicates whether the managed resource should be commented out. The `string` return value provides a message that will be printed as part of the comment.\n\nSince Crossplane managed resources don't typically implement the `CommentedYAML` interface, you can wrap them to add this functionality.\n\nThe `yaml.NewResourceWithComment` function handles this wrapping for you:\n\n```go\nfunc NewResourceWithComment(res resource.Object) *yaml.ResourceWithComment\n```\n\nThe `*yaml.ResourceWithComment` type wraps `res` and implements the `yaml.CommentedYAML` interface. It also provides helper methods:\n\n-   **SetComment:** sets the comment string\n-   **AddComment:** appends to the comment string\n\nThe following example demonstrates the commenting feature:\n\n```go\nfunc exportLogic(_ context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\")\n\n\tres := \u0026unstructured.Unstructured{\n\t  Object: map[string]interface{}{\n\t      \"user\": \"test-user-commented\",\n\t      \"password\": \"secret\",\n\t  },\n\t}\n\n\tcommentedResource := yaml.NewResourceWithComment(res)\n\tcommentedResource.SetComment(\"don't deploy it, this is a test resource!\")\n\tevents.Resource(commentedResource)\n\n\tevents.Stop()\n\treturn nil\n}\n```\n\nHere is the complete example:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\n\t\"github.com/SAP/xp-clifford/cli\"\n\t\"github.com/SAP/xp-clifford/cli/export\"\n\t\"github.com/SAP/xp-clifford/yaml\"\n\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n)\n\nfunc exportLogic(_ context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\")\n\n\tres := \u0026unstructured.Unstructured{\n\t  Object: map[string]interface{}{\n\t      \"user\": \"test-user-commented\",\n\t      \"password\": \"secret\",\n\t  },\n\t}\n\n\tcommentedResource := yaml.NewResourceWithComment(res)\n\tcommentedResource.SetComment(\"don't deploy it, this is a test resource!\")\n\tevents.Resource(commentedResource)\n\n\tevents.Stop()\n\treturn nil\n}\n\nfunc main() {\n\tcli.Configuration.ShortName = \"test\"\n\tcli.Configuration.ObservedSystem = \"test system\"\n\texport.SetCommand(exportLogic)\n\tcli.Execute()\n}\n```\n\nRunning this example displays the commented resource with its comment message:\n\n```sh\ngo run ./examples/exportcomment/main.go export\n```\n\n```\nINFO export command invoked\n\n\n    #\n    # don't deploy it, this is a test resource!\n    #\n    # ---\n    # password: secret\n    # user: test-user-commented\n    # ...\n\n```\n\nThis works equally well when redirecting output to a file using the `-o` flag.\n\n\n\u003ca id=\"erratt-example\"\u003e\u003c/a\u003e\n\n### Errors with attributes\n\nThe `erratt` package implements a new `error` type designed for efficient use with the `Warn` method of `EventHandler`.\n\nThe `erratt.Error` type implements the standard Go `error` interface. Additionally, it can be extended with `slog` package compatible key-value pairs used for structured logging. The `erratt.Error` type also supports wrapping Go `error` values. When an `erratt.Error` is wrapped, its attributes are preserved.\n\nYou can create a simple `erratt.Error` using the `erratt.New` function:\n\n```go\nerr := erratt.New(\"something went wrong\")\nerrWithAttrs1 := erratt.New(\"error opening file\", \"filename\", filename)\nerrWithAttrs2 := erratt.New(\"authentication failed\", \"username\", user, \"password\", pass)\n```\n\nIn this example, `errWithAttrs1` and `errWithAttrs2` include additional attributes.\n\nYou can wrap an existing `error` value using the `erratt.Errorf` function:\n\n```go\nerr := callFunction()\nerrWrapped := erratt.Errorf(\"unexpected error occurred: %w\", err)\n```\n\nYou can extend an `erratt.Error` value with attributes using the `With` method:\n\n```go\nerr := connectToServer(url, username, password)\nerrWrapped := erratt.Errorf(\"cannot connect to server: %w\", err).\n\tWith(\"url\", url, \"username\", username, \"password\", password)\n```\n\nFor a complete example, consider two functions that return `erratt.Error` values and demonstrate wrapping:\n\n```go\nfunc auth() erratt.Error {\n\treturn erratt.New(\"authentication failure\",\n\t\t\"username\", \"test-user\",\n\t\t\"password\", \"test-password\",\n\t)\n}\n\nfunc connect() erratt.Error {\n\terr := auth()\n\tif err != nil {\n\t\treturn erratt.Errorf(\"connect failed: %w\", err).\n\t\t\tWith(\"url\", \"https://example.com\")\n\t}\n\treturn nil\n}\n```\n\nThe `auth` function returns an `erratt.Error` value with username and password attributes.\n\nThe `exportLogic` function calls `connect` and handles the error:\n\n```go\nfunc exportLogic(_ context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\")\n\n\terr := connect()\n\n\tevents.Stop()\n\treturn err\n}\n```\n\nHere is the complete example:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\n\t\"github.com/SAP/xp-clifford/cli\"\n\t\"github.com/SAP/xp-clifford/cli/export\"\n\t\"github.com/SAP/xp-clifford/erratt\"\n)\n\nfunc auth() erratt.Error {\n\treturn erratt.New(\"authentication failure\",\n\t\t\"username\", \"test-user\",\n\t\t\"password\", \"test-password\",\n\t)\n}\n\nfunc connect() erratt.Error {\n\terr := auth()\n\tif err != nil {\n\t\treturn erratt.Errorf(\"connect failed: %w\", err).\n\t\t\tWith(\"url\", \"https://example.com\")\n\t}\n\treturn nil\n}\n\nfunc exportLogic(_ context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\")\n\n\terr := connect()\n\n\tevents.Stop()\n\treturn err\n}\n\nfunc main() {\n\tcli.Configuration.ShortName = \"test\"\n\tcli.Configuration.ObservedSystem = \"test system\"\n\texport.SetCommand(exportLogic)\n\tcli.Execute()\n}\n```\n\nRunning this code produces the following output:\n\n```sh\ngo run ./examples/erratt/main.go export\n```\n\n    INFO export command invoked\n    ERRO connect failed: authentication failure url=https://example.com username=test-user password=test-password\n\nThe error message appears on the console with all attributes displayed.\n\nThe `EventHandler.Warn` method handles `erratt.Error` values in the same manner.\n\n\n### Widgets\n\n`xp-clifford` provides several CLI widgets to facilitate the interaction with the user.\n\nNote that for the widgets to run, the CLI tool must be executed in an interactive terminal. This is not always the case by default, when running or debugging an application within an IDE (like GoLand) using a Run Configuration. In such cases, make sure to configure the Run Configuration appropriately. Specifically for [GoLand](https://www.jetbrains.com/help/go/run-debug-configuration.html) it can be done by selecting `Emulate terminal in output console`.\n\n\n#### TextInput widget\n\nThe TextInput widget prompts the user for a single line of text. Create a TextInput widget using the `TextInput` function from the `widget` package.\n\n```go\nfunc TextInput(ctx context.Context, title, placeholder string, sensitive bool) (string, error)\n```\n\nParameters:\n\n-   **ctx:** Go context for handling Ctrl-C interrupts or timeouts\n-   **title:** The prompt question displayed to the user\n-   **placeholder:** Placeholder text shown when the input is empty\n-   **sensitive:** When true, masks typed characters (useful for passwords)\n\nThe following example demonstrates an `exportLogic` function that prompts for a username and password:\n\n```go\nfunc exportLogic(ctx context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\")\n\n\tusername, err := widget.TextInput(ctx, \"Username\", \"anonymous\", false)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpassword, err := widget.TextInput(ctx, \"Password\", \"\", true)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tslog.Info(\"data acquired\",\n\t\t\"username\", username,\n\t\t\"password\", password,\n\t)\n\n\tevents.Stop()\n\treturn err\n}\n```\n\nComplete example:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\n\t\"github.com/SAP/xp-clifford/cli\"\n\t\"github.com/SAP/xp-clifford/cli/export\"\n\t\"github.com/SAP/xp-clifford/cli/widget\"\n)\n\nfunc exportLogic(ctx context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\")\n\n\tusername, err := widget.TextInput(ctx, \"Username\", \"anonymous\", false)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpassword, err := widget.TextInput(ctx, \"Password\", \"\", true)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tslog.Info(\"data acquired\",\n\t\t\"username\", username,\n\t\t\"password\", password,\n\t)\n\n\tevents.Stop()\n\treturn err\n}\n\nfunc main() {\n\tcli.Configuration.ShortName = \"test\"\n\tcli.Configuration.ObservedSystem = \"test system\"\n\texport.SetCommand(exportLogic)\n\tcli.Execute()\n}\n```\n\nSee the example in action:\n\n![img](examples/textinput/example.gif \"TextInput example\")\n\n\n#### MultiInput widget\n\nThe MultiInput widget creates a multi-selection interface that allows users to select multiple items from a predefined list of options:\n\n```go\nfunc MultiInput(ctx context.Context, title string, options []string) ([]string, error)\n```\n\nParameters:\n\n-   **ctx:** Go context for handling Ctrl-C interrupts or timeouts\n-   **title:** The selection prompt displayed to the user\n-   **options:** The list of selectable items\n\nThe following example demonstrates an `exportLogic` function that uses the `MultiInput` widget:\n\n```go\nfunc exportLogic(ctx context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\")\n\n\tprotocols, err := widget.MultiInput(ctx,\n\t\t\"Select the supported protocols\",\n\t\t[]string{\n\t\t\t\"FTP\",\n\t\t\t\"HTTP\",\n\t\t\t\"HTTPS\",\n\t\t\t\"SFTP\",\n\t\t\t\"SSH\",\n\t\t},\n\t)\n\n\tslog.Info(\"data acquired\",\n\t\t\"protocols\", protocols,\n\t)\n\n\tevents.Stop()\n\treturn err\n}\n```\n\nThe complete source code is assembled as follows:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\n\t\"github.com/SAP/xp-clifford/cli\"\n\t\"github.com/SAP/xp-clifford/cli/export\"\n\t\"github.com/SAP/xp-clifford/cli/widget\"\n)\n\nfunc exportLogic(ctx context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\")\n\n\tprotocols, err := widget.MultiInput(ctx,\n\t\t\"Select the supported protocols\",\n\t\t[]string{\n\t\t\t\"FTP\",\n\t\t\t\"HTTP\",\n\t\t\t\"HTTPS\",\n\t\t\t\"SFTP\",\n\t\t\t\"SSH\",\n\t\t},\n\t)\n\n\tslog.Info(\"data acquired\",\n\t\t\"protocols\", protocols,\n\t)\n\n\tevents.Stop()\n\treturn err\n}\n\nfunc main() {\n\tcli.Configuration.ShortName = \"test\"\n\tcli.Configuration.ObservedSystem = \"test system\"\n\texport.SetCommand(exportLogic)\n\tcli.Execute()\n}\n```\n\nRunning this example produces the following output:\n\n![img](examples/multiinput/example.gif \"MultiInput example\")\n\n\n### Configuration parameters\n\nCLI tools built using `xp-clifford` can be configured through several methods:\n\n-   Command-line flags\n-   Environment variables\n-   Configuration files\n\n`xp-clifford` provides types and functions to facilitate configuration and management of these parameters. Configuration parameter handling is also integrated with the widget capabilities of `xp-clifford`.\n\nCurrently, the following configuration parameter types are supported:\n\n-   `bool`\n-   `string`\n-   `[]string`\n\nAll configuration parameters managed by `xp-clifford` implement the `configparam.ConfigParam` interface.\n\n\n#### Global configuration parameters\n\nAny CLI tool built using `xp-clifford` includes the following global flags:\n\n-   **`-c` or `--config`:** Configuration file for setting additional parameters (string)\n-   **`-v` or `--verbose`:** Enable verbose logging (bool)\n-   **`-h` or `--help`:** Print help message (bool)\n\nThe verbose logging is explained in [Verbose logging](#verbose). The configuration file handling is elaborated in the [Configuration file](#config-file).\n\n\n\u003ca id=\"verbose\"\u003e\u003c/a\u003e\n\n##### Verbose logging\n\nEnable verbose logging with the `-v` or `--verbose` flag. When enabled, structured log messages at the *Debug* level are also printed to the console.\n\nAn example `exportLogic` function:\n\n```go\nfunc exportLogic(_ context.Context, events export.EventHandler) error {\n\tslog.Debug(\"export command invoked\")\n\tevents.Stop()\n\treturn nil\n}\n```\n\nThe complete example:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\n\t\"github.com/SAP/xp-clifford/cli\"\n\t\"github.com/SAP/xp-clifford/cli/export\"\n)\n\nfunc exportLogic(_ context.Context, events export.EventHandler) error {\n\tslog.Debug(\"export command invoked\")\n\tevents.Stop()\n\treturn nil\n}\n\nfunc main() {\n\tcli.Configuration.ShortName = \"test\"\n\tcli.Configuration.ObservedSystem = \"test system\"\n\texport.SetCommand(exportLogic)\n\tcli.Execute()\n}\n```\n\nExecuting the `export` subcommand without the `-v` flag produces no output:\n\n```sh\ngo run ./examples/verbose/main.go export\n```\n\nWith the `-v` flag, the debug-level message appears:\n\n```sh\ngo run ./examples/verbose/main.go export -v\n```\n\n    DEBU export command invoked\n\n\n#### Configuration parameters of the export subcommand\n\nThe `export` subcommand includes the following default configuration parameters:\n\n-   **`-k` or `--kind`:** Resource kinds to export ([]string)\n-   **`-o` or `--output`:** Redirect output to a file (string)\n\nYou can extend the `export` subcommand with additional configuration parameters using the `export.AddConfigParams` function:\n\n```go\nfunc AddConfigParams(param ...configparam.ConfigParam)\n```\n\n\n#### Bool configuration parameter\n\nCreate a new *bool* configuration parameter using the `configparam.Bool` function:\n\n```go\nfunc Bool(name, description string) *BoolParam\n```\n\nThe two mandatory arguments are *name* and *description*. Fine-tune the parameter with these methods:\n\n-   **`WithShortName`:** Single-character short command-line flag\n-   **`WithFlagName`:** Long format of the command-line flag (defaults to *name*)\n-   **`WithEnvVarName`:** Environment variable name for the parameter\n-   **`WithDefaultValue`:** Default value of the parameter\n\nUse the `Value()` method to retrieve the parameter value. The `IsSet()` method returns true if the user has explicitly set the value.\n\nHere is a bool configuration parameter definition:\n\n```go\nvar testParam = configparam.Bool(\"test\", \"test bool parameter\").\n        WithShortName(\"t\").\n        WithEnvVarName(\"CLIFFORD_TEST\")\n```\n\nAdd the parameter to the `export` subcommand:\n\n```go\nexport.AddConfigParams(testParam)\n```\n\nA complete working example:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\n\t\"github.com/SAP/xp-clifford/cli\"\n\t\"github.com/SAP/xp-clifford/cli/configparam\"\n\t\"github.com/SAP/xp-clifford/cli/export\"\n)\n\nfunc exportLogic(_ context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\", \"test-value\", testParam.Value())\n\tevents.Stop()\n\treturn nil\n}\n\nvar testParam = configparam.Bool(\"test\", \"test bool parameter\").\n        WithShortName(\"t\").\n        WithEnvVarName(\"CLIFFORD_TEST\")\n\nfunc main() {\n\tcli.Configuration.ShortName = \"test\"\n\tcli.Configuration.ObservedSystem = \"test system\"\n\texport.AddConfigParams(testParam)\n\texport.SetCommand(exportLogic)\n\tcli.Execute()\n}\n```\n\nThe new parameter appears in the help output:\n\n```sh\ngo run ./examples/boolparam/main.go export --help\n```\n\n```\nExport test system resources and transform them into managed resources that the Crossplane provider can consume\n\nUsage:\n  test-exporter export [flags]\n\nFlags:\n  -h, --help            help for export\n  -k, --kind strings    Resource kinds to export\n  -o, --output string   redirect the YAML output to a file\n  -t, --test            test bool parameter\n\nGlobal Flags:\n  -c, --config string   Configuration file\n  -v, --verbose         Verbose output\n```\n\nBy default, test is `false`:\n\n```sh\ngo run ./examples/boolparam/main.go export\n```\n\n    INFO export command invoked test-value=false\n\nEnable it using the `--test` flag:\n\n```sh\ngo run ./examples/boolparam/main.go export --test\n```\n\n    INFO export command invoked test-value=true\n\nOr using the shorthand `-t` flag:\n\n```sh\ngo run ./examples/boolparam/main.go export -t\n```\n\n    INFO export command invoked test-value=true\n\nOr using the `CLIFFORD_TEST` environment variable:\n\n```sh\nCLIFFORD_TEST=1 go run ./examples/boolparam/main.go export\n```\n\n    INFO export command invoked test-value=true\n\n\n#### String configuration parameter\n\nCreate a new *string* configuration parameter using the `configparam.String` function:\n\n```go\nfunc String(name, description string) *StringParam\n```\n\nThe two mandatory arguments are *name* and *description*. Fine-tune the parameter with these methods:\n\n-   **`WithShortName`:** Single-character short command-line flag\n-   **`WithFlagName`:** Long format of the command-line flag (defaults to *name*)\n-   **`WithEnvVarName`:** Environment variable name for the parameter\n-   **`WithDefaultValue`:** Default value of the parameter\n\nUse the `Value()` method to retrieve the parameter value. The `IsSet()` method returns true if the user has explicitly set the value.\n\nThe `ValueOrAsk` method returns the value if set. Otherwise, it prompts for the value interactively using the `TextInput` widget.\n\nConsider the following string configuration parameter:\n\n```go\nvar testParam = configparam.String(\"username\", \"username used for authentication\").\n\tWithShortName(\"u\").\n\tWithEnvVarName(\"USERNAME\").\n\tWithDefaultValue(\"testuser\")\n```\n\nA complete example:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\n\t\"github.com/SAP/xp-clifford/cli\"\n\t\"github.com/SAP/xp-clifford/cli/configparam\"\n\t\"github.com/SAP/xp-clifford/cli/export\"\n)\n\nfunc exportLogic(ctx context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\",\n\t\t\"username\", testParam.Value(),\n\t\t\"is-set\", testParam.IsSet(),\n\t)\n\n\t// If not set, ask the value\n\tusername, err := testParam.ValueOrAsk(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tslog.Info(\"value set by user\", \"value\", username)\n\n\tevents.Stop()\n\treturn nil\n}\n\nvar testParam = configparam.String(\"username\", \"username used for authentication\").\n\tWithShortName(\"u\").\n\tWithEnvVarName(\"USERNAME\").\n\tWithDefaultValue(\"testuser\")\n\nfunc main() {\n\tcli.Configuration.ShortName = \"test\"\n\tcli.Configuration.ObservedSystem = \"test system\"\n\texport.AddConfigParams(testParam)\n\texport.SetCommand(exportLogic)\n        cli.Execute()\n}\n```\n\nThe new parameter appears in the help output:\n\n```sh\ngo run ./examples/stringparam/main.go export --help\n```\n\n```\nExport test system resources and transform them into managed resources that the Crossplane provider can consume\n\nUsage:\n  test-exporter export [flags]\n\nFlags:\n  -h, --help              help for export\n  -k, --kind strings      Resource kinds to export\n  -o, --output string     redirect the YAML output to a file\n  -u, --username string   username used for authentication\n\nGlobal Flags:\n  -c, --config string   Configuration file\n  -v, --verbose         Verbose output\n```\n\nSet the value using the `--username` flag:\n\n```sh\ngo run ./examples/stringparam/main.go export --username anonymous\n```\n\n    INFO export command invoked username=anonymous is-set=true\n    INFO value set by user value=anonymous\n\nOr using the shorthand `-u` flag:\n\n```sh\ngo run ./examples/stringparam/main.go export -u anonymous\n```\n\n    INFO export command invoked username=anonymous is-set=true\n    INFO value set by user value=anonymous\n\nOr using the `USERNAME` environment variable:\n\n```sh\nUSERNAME=anonymous go run ./examples/stringparam/main.go export\n```\n\n    INFO export command invoked username=anonymous is-set=true\n    INFO value set by user value=anonymous\n\nWhen no value is provided, the `TextInput` widget prompts for it interactively:\n\n![img](examples/stringparam/example.gif \"Asking a string config parameter value\")\n\n\n#### String slice configuration parameter\n\nA string slice configuration parameter configures values of type `[]string`.\n\nCreate a new *string slice* configuration parameter using the `configparam.StringSlice` function:\n\n```go\nfunc StringSlice(name, description string) *StringSliceParam\n```\n\nThe two mandatory arguments are *name* and *description*. Fine-tune the parameter with these methods:\n\n-   **`WithShortName`:** Single-character short command-line flag\n-   **`WithFlagName`:** Long format of the command-line flag (defaults to *name*)\n-   **`WithEnvVarName`:** Environment variable name for the parameter\n-   **`WithDefaultValue`:** Default value of the parameter\n-   **`WithPossibleValues`:** Limit the selection options offered during `ValueOrAsk`\n-   **`WithPossibleValuesFn`:** Function that provides the selection options offered during `ValueOrAsk`\n\nUse the `Value()` method to retrieve the parameter value. The `IsSet()` method returns true if the user has explicitly set the value.\n\nThe `ValueOrAsk` method returns the value if set. Otherwise, it prompts for the value interactively using the `MultiInput` widget. Interactive prompting requires setting possible values with `WithPossibleValues` or `WithPossibleValuesFn`.\n\n\n##### Without possible values\n\nThe following example configures a *StringSlice* parameter:\n\n```go\nvar testParam = configparam.StringSlice(\"protocol\", \"list of supported protocols\").\n\tWithShortName(\"p\").\n\tWithEnvVarName(\"PROTOCOLS\")\n```\n\nComplete example:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\n\t\"github.com/SAP/xp-clifford/cli\"\n\t\"github.com/SAP/xp-clifford/cli/configparam\"\n\t\"github.com/SAP/xp-clifford/cli/export\"\n)\n\nfunc exportLogic(ctx context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\",\n\t\t\"protocols\", testParam.Value(),\n\t\t\"num-of-protos\", len(testParam.Value()),\n\t\t\"is-set\", testParam.IsSet(),\n\t)\n\n\tevents.Stop()\n\treturn nil\n}\n\nvar testParam = configparam.StringSlice(\"protocol\", \"list of supported protocols\").\n\tWithShortName(\"p\").\n\tWithEnvVarName(\"PROTOCOLS\")\n\nfunc main() {\n\tcli.Configuration.ShortName = \"test\"\n\tcli.Configuration.ObservedSystem = \"test system\"\n\texport.AddConfigParams(testParam)\n\texport.SetCommand(exportLogic)\n        cli.Execute()\n}\n```\n\nThe new parameter appears in the help output:\n\n```sh\ngo run ./examples/stringslice/main.go export --help\n```\n\n```\nExport test system resources and transform them into managed resources that the Crossplane provider can consume\n\nUsage:\n  test-exporter export [flags]\n\nFlags:\n  -h, --help               help for export\n  -k, --kind strings       Resource kinds to export\n  -o, --output string      redirect the YAML output to a file\n  -p, --protocol strings   list of supported protocols\n\nGlobal Flags:\n  -c, --config string   Configuration file\n  -v, --verbose         Verbose output\n```\n\nWithout setting the value:\n\n```sh\ngo run ./examples/stringslice/main.go export\n```\n\n    INFO export command invoked protocols=[] num-of-protos=0 is-set=false\n\nSet the value using the `--protocol` flag:\n\n```sh\ngo run ./examples/stringslice/main.go export --protocol HTTP --protocol HTTPS --protocol SSH\n```\n\n    INFO export command invoked protocols=\"[HTTP HTTPS SSH]\" num-of-protos=3 is-set=true\n\nSet the value using the `-p` flag:\n\n```sh\ngo run ./examples/stringslice/main.go export -p HTTP -p SFTP -p FTP\n```\n\n    INFO export command invoked protocols=\"[HTTP SFTP FTP]\" num-of-protos=3 is-set=true\n\nSet the value using the `PROTOCOLS` environment variable:\n\n```sh\nPROTOCOLS=\"HTTP HTTPS FTP\" go run ./examples/stringslice/main.go export\n```\n\n    INFO export command invoked protocols=\"[HTTP HTTPS FTP]\" num-of-protos=3 is-set=true\n\n\n##### With static possible values\n\nTo enable interactive prompting with *StringSlice* configuration parameters, add static selection options using the `WithPossibleValues` method.\n\nDefine the configuration parameter:\n\n```go\nvar testParam = configparam.StringSlice(\"protocol\", \"list of supported protocols\").\n\tWithShortName(\"p\").\n\tWithEnvVarName(\"PROTOCOLS\").\n\tWithPossibleValues([]string{\"HTTP\", \"HTTPS\", \"FTP\", \"SSH\", \"SFTP\"})\n```\n\nComplete example:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\n\t\"github.com/SAP/xp-clifford/cli\"\n\t\"github.com/SAP/xp-clifford/cli/configparam\"\n\t\"github.com/SAP/xp-clifford/cli/export\"\n)\n\nfunc exportLogic(ctx context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\",\n\t\t\"protocols\", testParam.Value(),\n\t\t\"num-of-protos\", len(testParam.Value()),\n\t\t\"is-set\", testParam.IsSet(),\n\t)\n\n\tprotocols, err := testParam.ValueOrAsk(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tslog.Info(\"data acquired\", \"protocols\", protocols)\n\n\tevents.Stop()\n\treturn nil\n}\n\nvar testParam = configparam.StringSlice(\"protocol\", \"list of supported protocols\").\n\tWithShortName(\"p\").\n\tWithEnvVarName(\"PROTOCOLS\").\n\tWithPossibleValues([]string{\"HTTP\", \"HTTPS\", \"FTP\", \"SSH\", \"SFTP\"})\n\nfunc main() {\n\tcli.Configuration.ShortName = \"test\"\n\tcli.Configuration.ObservedSystem = \"test system\"\n\texport.AddConfigParams(testParam)\n\texport.SetCommand(exportLogic)\n        cli.Execute()\n}\n```\n\nYou can set values with flags or environment variables as before:\n\n```sh\ngo run ./examples/stringslicestatic/main.go export --protocol HTTP --protocol HTTPS --protocol SSH\n```\n\n    INFO export command invoked protocols=\"[HTTP HTTPS SSH]\" num-of-protos=3 is-set=true\n    INFO data acquired protocols=\"[HTTP HTTPS SSH]\"\n\n```sh\ngo run ./examples/stringslicestatic/main.go export -p HTTP -p SFTP -p FTP\n```\n\n    INFO export command invoked protocols=\"[HTTP SFTP FTP]\" num-of-protos=3 is-set=true\n    INFO data acquired protocols=\"[HTTP SFTP FTP]\"\n\n```sh\nPROTOCOLS=\"HTTP HTTPS FTP\" go run ./examples/stringslicestatic/main.go export\n```\n\n    INFO export command invoked protocols=\"[HTTP HTTPS FTP]\" num-of-protos=3 is-set=true\n    INFO data acquired protocols=\"[HTTP HTTPS FTP]\"\n\nWhen you omit the parameter values, the CLI tool prompts for them interactively:\n\n![img](examples/stringslicestatic/example.gif \"Prompting for StringSlice value\")\n\n\n##### With dynamic possible values\n\nSometimes the set of possible *StringSlice* parameter values cannot be defined at build time. The value set may depend on a previous interactive selection or the result of an API request.\n\nIn such cases, set the possible values dynamically using the `WithPossibleValuesFn` method.\n\nConsider a simple *Bool* configuration parameter:\n\n```go\nvar secureParam = configparam.Bool(\"secure\", \"secure protocol\").\n        WithShortName(\"s\").\n        WithEnvVarName(\"SECURE\")\n```\n\nBased on the value of `secureParam`, the `possibleProtocols` function suggests different protocol names:\n\n```go\nfunc possibleProtocols() ([]string, error) {\n\tif secureParam.Value() {\n\t\treturn []string{\"HTTPS\", \"SFTP\", \"SSH\"}, nil\n\t}\n\treturn []string{\"FTP\", \"HTTP\"}, nil\n}\n```\n\nThe `protocolsParam` configuration parameter uses `possibleProtocols` when prompting the user with the `ValueOrAsk` method:\n\n```go\nvar protocolsParam = configparam.StringSlice(\"protocol\", \"list of supported protocols\").\n\tWithShortName(\"p\").\n\tWithEnvVarName(\"PROTOCOLS\").\n\tWithPossibleValuesFn(possibleProtocols)\n```\n\nComplete example:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\n\t\"github.com/SAP/xp-clifford/cli\"\n\t\"github.com/SAP/xp-clifford/cli/configparam\"\n\t\"github.com/SAP/xp-clifford/cli/export\"\n)\n\nfunc exportLogic(ctx context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\",\n\t        \"secure\", secureParam.Value(),\n\t\t\"secure-is-set\", secureParam.IsSet(),\n\t\t\"protocols\", protocolsParam.Value(),\n\t\t\"num-of-protos\", len(protocolsParam.Value()),\n\t\t\"protocols-is-set\", protocolsParam.IsSet(),\n\t)\n\n\tprotocols, err := protocolsParam.ValueOrAsk(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tslog.Info(\"data acquired\", \"protocols\", protocols)\n\n\tevents.Stop()\n\treturn nil\n}\n\nfunc possibleProtocols() ([]string, error) {\n\tif secureParam.Value() {\n\t\treturn []string{\"HTTPS\", \"SFTP\", \"SSH\"}, nil\n\t}\n\treturn []string{\"FTP\", \"HTTP\"}, nil\n}\n\nvar secureParam = configparam.Bool(\"secure\", \"secure protocol\").\n        WithShortName(\"s\").\n        WithEnvVarName(\"SECURE\")\n\nvar protocolsParam = configparam.StringSlice(\"protocol\", \"list of supported protocols\").\n\tWithShortName(\"p\").\n\tWithEnvVarName(\"PROTOCOLS\").\n\tWithPossibleValuesFn(possibleProtocols)\n\nfunc main() {\n\tcli.Configuration.ShortName = \"test\"\n\tcli.Configuration.ObservedSystem = \"test system\"\n\texport.AddConfigParams(secureParam, protocolsParam)\n\texport.SetCommand(exportLogic)\n        cli.Execute()\n}\n```\n\nBoth parameters appear in the help output:\n\n```sh\ngo run ./examples/stringslicedynamic/main.go export --help\n```\n\n```\nExport test system resources and transform them into managed resources that the Crossplane provider can consume\n\nUsage:\n  test-exporter export [flags]\n\nFlags:\n  -h, --help               help for export\n  -k, --kind strings       Resource kinds to export\n  -o, --output string      redirect the YAML output to a file\n  -p, --protocol strings   list of supported protocols\n  -s, --secure             secure protocol\n\nGlobal Flags:\n  -c, --config string   Configuration file\n  -v, --verbose         Verbose output\n```\n\nSet the values using flags as usual:\n\n```sh\ngo run ./examples/stringslicedynamic/main.go export -s --protocol HTTPS --protocol SFTP\n```\n\n    INFO export command invoked secure=true secure-is-set=true protocols=\"[HTTPS SFTP]\" num-of-protos=2 protocols-is-set=true\n    INFO data acquired protocols=\"[HTTPS SFTP]\"\n\nWhen the *protocol* configuration parameter is not set, the CLI prompts for its value interactively. The available options depend on the value of *secure*.\n\nIf *secure* is not set:\n\n![img](examples/stringslicedynamic/example1.gif \"Prompting for StringSlice dynamically - secure is off\")\n\nIf *secure* is set:\n\n![img](examples/stringslicedynamic/example2.gif \"Prompting for StringSlice dynamically - secure is on\")\n\n\n#### Subcommands\n\nCLI tools created with `xp-clifford` include the mandatory `export` subcommand. You can also define additional subcommands by creating a value that implements the `cli.SubCommand` interface.\n\nYou can implement your own type, or use the `cli.BasicSubCommand` type, which already implements the `cli.SubCommand` interface.\n\nThe business logic executed when the subcommand is invoked must have the following function signature:\n\n```go\nfunc(context.Context) error\n```\n\nLet's consider the following logic function for an imaginary `login` subcommand:\n\n```go\nfunc login(_ context.Context) error {\n\tslog.Info(\"login invoked\")\n\treturn nil\n}\n```\n\nA `BasicSubcommand` value can be created for the `login` subcommand:\n\n```go\nvar loginSubCommand = \u0026cli.BasicSubCommand{\n\tName:         \"login\",\n\tShort:        \"Login demo subcommand\",\n\tLong:         \"A subcommand demonstrating xp-clifford capabilities\",\n\tConfigParams: []configparam.ConfigParam{},\n\tRun:          login,\n}\n```\n\nA subcommand can be registered using the `cli.RegisterCommand` function:\n\n```go\ncli.RegisterSubCommand(loginSubCommand)\n```\n\nComplete example:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\n\t\"github.com/SAP/xp-clifford/cli\"\n\t\"github.com/SAP/xp-clifford/cli/configparam\"\n\t_ \"github.com/SAP/xp-clifford/cli/export\"\n)\n\nfunc login(_ context.Context) error {\n\tslog.Info(\"login invoked\")\n\treturn nil\n}\n\nfunc main() {\n\tcli.Configuration.ShortName = \"test\"\n\tcli.Configuration.ObservedSystem = \"test system\"\n\n\tvar loginSubCommand = \u0026cli.BasicSubCommand{\n\t\tName:         \"login\",\n\t\tShort:        \"Login demo subcommand\",\n\t\tLong:         \"A subcommand demonstrating xp-clifford capabilities\",\n\t\tConfigParams: []configparam.ConfigParam{},\n\t\tRun:          login,\n\t}\n\n\tcli.RegisterSubCommand(loginSubCommand)\n\n\tcli.Execute()\n}\n```\n\nThe `login` subcommand appears when we run the CLI application with the `--help` flag:\n\n```sh\ngo run ./examples/loginsubcommand/main.go --help\n```\n\n```\ntest system exporting tool is a CLI tool for exporting existing resources as Crossplane managed resources\n\nUsage:\n  test-exporter [command]\n\nAvailable Commands:\n  completion  Generate the autocompletion script for the specified shell\n  export      Export test system resources\n  help        Help about any command\n  login       Login demo subcommand\n\nFlags:\n  -c, --config string   Configuration file\n  -h, --help            help for test-exporter\n  -v, --verbose         Verbose output\n\nUse \"test-exporter [command] --help\" for more information about a command.\n```\n\nThe `--help` flag also works for the new `login` subcommand:\n\n```sh\ngo run ./examples/loginsubcommand/main.go login --help\n```\n\n```\nA subcommand demonstrating xp-clifford capabilities\n\nUsage:\n  test-exporter login [flags]\n\nFlags:\n  -h, --help   help for login\n\nGlobal Flags:\n  -c, --config string   Configuration file\n  -v, --verbose         Verbose output\n```\n\nWe can also run the `login` subcommand:\n\n```sh\ngo run ./examples/loginsubcommand/main.go login\n```\n\n    INFO login invoked\n\n\n##### Subcommand with configuration parameters\n\nCustom subcommands can be extended with configuration parameters using the `GetConfigParams()` method of the `cli.SubCommand` interface, or by setting the `ConfigParams` field of a `BasicSubCommand` value.\n\nLet's update the `loginSubCommand` value:\n\n```go\nvar loginSubCommand = \u0026cli.BasicSubCommand{\n\tName:         \"login\",\n\tShort:        \"Login demo subcommand\",\n\tLong:         \"A subcommand demonstrating xp-clifford capabilities\",\n\tConfigParams: []configparam.ConfigParam{\n\t\ttestParam,\n\t},\n\tRun:          login,\n}\n```\n\nHere, `testParam` is defined as follows:\n\n```go\nvar testParam = configparam.Bool(\"test\", \"test bool parameter\").\n        WithShortName(\"t\").\n        WithEnvVarName(\"CLIFFORD_TEST\")\n```\n\nLet's extend the `login` function to print the value of `testParam`:\n\n```go\nfunc login(_ context.Context) error {\n\tslog.Info(\"login invoked\", \"test\", testParam.Value())\n\treturn nil\n}\n```\n\nComplete example:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\n\t\"github.com/SAP/xp-clifford/cli\"\n\t\"github.com/SAP/xp-clifford/cli/configparam\"\n\t_ \"github.com/SAP/xp-clifford/cli/export\"\n)\n\nfunc login(_ context.Context) error {\n\tslog.Info(\"login invoked\", \"test\", testParam.Value())\n\treturn nil\n}\n\nvar testParam = configparam.Bool(\"test\", \"test bool parameter\").\n        WithShortName(\"t\").\n        WithEnvVarName(\"CLIFFORD_TEST\")\n\nfunc main() {\n\tcli.Configuration.ShortName = \"test\"\n\tcli.Configuration.ObservedSystem = \"test system\"\n\n\tvar loginSubCommand = \u0026cli.BasicSubCommand{\n\t\tName:         \"login\",\n\t\tShort:        \"Login demo subcommand\",\n\t\tLong:         \"A subcommand demonstrating xp-clifford capabilities\",\n\t\tConfigParams: []configparam.ConfigParam{\n\t\t\ttestParam,\n\t\t},\n\t\tRun:          login,\n\t}\n\n\tcli.RegisterSubCommand(loginSubCommand)\n\n\tcli.Execute()\n}\n```\n\nThe `--help` flag for the `login` subcommand now shows the `-t` / `--test` parameter:\n\n```sh\ngo run ./examples/loginsubcommandparam/main.go login --help\n```\n\n```\nA subcommand demonstrating xp-clifford capabilities\n\nUsage:\n  test-exporter login [flags]\n\nFlags:\n  -h, --help   help for login\n  -t, --test   test bool parameter\n\nGlobal Flags:\n  -c, --config string   Configuration file\n  -v, --verbose         Verbose output\n```\n\nLet's invoke the `login` command:\n\n```sh\ngo run ./examples/loginsubcommandparam/main.go login\n```\n\n    INFO login invoked test=false\n\nLet's see the configuration parameter in action:\n\n```sh\ngo run ./examples/loginsubcommandparam/main.go login -t\n```\n\n    INFO login invoked test=true\n\n\n\u003ca id=\"config-file\"\u003e\u003c/a\u003e\n\n#### Configuration file\n\nIn addition to CLI flags and environment variables, a CLI tool built with `xp-clifford` can read configuration from a YAML file.\n\nYou can specify the configuration file path using the `--config` / `-c` global flag.\n\nIf you don't specify a configuration file, the CLI looks for one in these locations, in order:\n\n1.  `$XDG_CONFIG_HOME/\u003cconfig_file_name\u003e`\n2.  `$HOME/\u003cconfig_file_name\u003e`\n\nThe `config_file_name` is `export-cli-config-\u003cshortname\u003e`, where `shortname` is the value of `cli.Configuration.ShortName`.\n\nThe YAML file contains key-value pairs, where keys are configuration parameter names in lowercase.\n\nHere is a simple example CLI with three configuration parameters:\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\n\t\"github.com/SAP/xp-clifford/cli\"\n\t\"github.com/SAP/xp-clifford/cli/configparam\"\n\t\"github.com/SAP/xp-clifford/cli/export\"\n)\n\nfunc exportLogic(ctx context.Context, events export.EventHandler) error {\n\tslog.Info(\"export command invoked\",\n\t\t\"protocols\", protocolParam.Value(),\n\t\t\"username\", usernameParam.Value(),\n\t\t\"boolparam\", boolParam.Value(),\n\t)\n\n\tevents.Stop()\n\treturn nil\n}\n\nvar protocolParam = configparam.StringSlice(\"protocol\", \"list of supported protocols\").\n\tWithShortName(\"p\").\n\tWithEnvVarName(\"PROTOCOLS\")\n\nvar usernameParam = configparam.String(\"username\", \"username used for authentication\").\n\tWithShortName(\"u\").\n\tWithEnvVarName(\"USERNAME\")\n\nvar boolParam = configparam.Bool(\"bool\", \"test bool parameter\").\n\tWithShortName(\"b\").\n\tWithEnvVarName(\"CLIFFORD_BOOL\")\n\nfunc main() {\n\tcli.Configuration.ShortName = \"test\"\n\tcli.Configuration.ObservedSystem = \"test system\"\n\texport.AddConfigParams(protocolParam, usernameParam, boolParam)\n\texport.SetCommand(exportLogic)\n\tcli.Execute()\n}\n```\n\nFlag-based configuration works as expected:\n\n```sh\ngo run ./examples/configfile/main.go export -b --protocol HTTPS --protocol SFTP --username anonymous\n```\n\n    INFO export command invoked protocols=\"[HTTPS SFTP]\" username=anonymous boolparam=true\n\nWithout CLI flags:\n\n```sh\ngo run ./examples/configfile/main.go export\n```\n\n    INFO export command invoked protocols=[] username=\"\" boolparam=false\n\nNow let's create a configuration file:\n\n```yaml\nprotocol:\n  - HTTP\n  - FTP\nusername: config-user\nbool: true\n```\n\nThe CLI reads configuration parameter values from this file:\n\n```sh\ngo run ./examples/configfile/main.go export --config ./examples/configfile/config\n```\n\n    INFO export command invoked protocols=\"[HTTP FTP]\" username=config-user boolparam=true\n\nEnvironment variables override values from the configuration file:\n\n```sh\nPROTOCOLS=\"FTP\" go run ./examples/configfile/main.go export --config ./examples/configfile/config\n```\n\n    INFO export command invoked protocols=[FTP] username=config-user boolparam=true\n\nCLI flags take the highest precedence and override everything else:\n\n```sh\nPROTOCOLS=\"FTP\" go run ./examples/configfile/main.go export --config ./examples/configfile/config --protocol SSH -b=false\n```\n\n    INFO export command invoked protocols=[SSH] username=config-user boolparam=false\n\n\n### Parsing and sanitizing\n\nWhen creating Crossplane managed resource definitions, we frequently transform objects describing external resources into a different schema. Usually the values are preserved, but the data structure differs.\n\nSometimes we cannot preserve values exactly because they must conform to certain rules.\n\nAn example is the `metadata.name` field of Kubernetes resources\u003csup\u003e\u003ca id=\"fnr.1\" class=\"footref\" href=\"#fn.1\" role=\"doc-backlink\"\u003e1\u003c/a\u003e\u003c/sup\u003e. The Kubernetes documentation references various RFCs and extends those requirements with additional rules.\n\nThe `parsan` package in `xp-clifford` provides functions that transform strings into formats satisfying different Kubernetes object name requirements. This process is called sanitization. The `ParseAndSanitize` function performs this action:\n\n```go\nfunc ParseAndSanitize(input string, rule Rule) []string\n```\n\nThe `ParseAndSanitize` function takes an *input* string and a *rule*, then transforms the *input* to conform to the *rule*. Since multiple valid sanitized solutions may exist, the function returns all of them.\n\n\n#### Sanitizer rules\n\nThe following rules are available for sanitization.\n\n\n##### RFC1035Subdomain\n\nThe `RFC1035Subdomain` rule conforms to:\n\n```\n\u003csubdomain\u003e ::= \u003clabel\u003e | \u003csubdomain\u003e \".\" \u003clabel\u003e\n```\n\nA *subdomain* is either a single *label* or multiple *labels* separated by dots (e.g., *label.label.label*).\n\nA *label* is a string that:\n\n-   starts with a letter (lowercase or uppercase),\n-   ends with a letter (lowercase or uppercase) or a digit,\n-   contains only letters, digits, and `-` characters.\n\nA *label* cannot exceed 63 characters. A *subdomain* cannot exceed 253 characters.\n\nDuring sanitization, invalid characters are replaced with `-` or `x`. The `@` symbol is replaced with `-at-`. Labels and subdomains that are too long are trimmed.\n\nExamples:\n\n| input                  | sanitized              |\n|---------------------- |---------------------- |\n| `www.example.com`      | `www.example.com`      |\n| `Can you sanitize me?` | `Can-you-sanitize-mex` |\n| `99Luftballons`        | `x99Luftballons`       |\n| `admin@example.com`    | `admin-at-example.com` |\n\n\n##### RFC1035LowerSubdomain\n\nThe `RFC1035LowerSubdomain` rule is a variation of `RFC1035Subdomain` that requires lowercase letters only. Uppercase letters are converted to lowercase:\n\n| input                  | sanitized              |\n|---------------------- |---------------------- |\n| `www.example.com`      | `www.example.com`      |\n| `Can you sanitize me?` | `can-you-sanitize-mex` |\n| `99Luftballons`        | `x99luftballons`       |\n| `admin@example.com`    | `admin-at-example.com` |\n\n\n##### RFC1035SubdomainRelaxed\n\nThe `RFC1035SubdomainRelaxed` rule is a variation of `RFC1035Subdomain` that allows *labels* to start with digits:\n\n| input                  | sanitized              |\n|---------------------- |---------------------- |\n| `www.example.com`      | `www.example.com`      |\n| `Can you sanitize me?` | `Can-you-sanitize-mex` |\n| `99Luftballons`        | `99Luftballons`        |\n| `admin@example.com`    | `admin-at-example.com` |\n\n\n##### RFC1035LowerSubdomainRelaxed\n\nThe `RFC1035LowerSubdomainRelaxed` rule combines `RFC1035LowerSubdomain` and `RFC1035SubdomainRelaxed`. Uppercase characters are converted to lowercase, and *labels* may start with digits:\n\n| input                  | sanitized              |\n|---------------------- |---------------------- |\n| `www.example.com`      | `www.example.com`      |\n| `Can you sanitize me?` | `can-you-sanitize-mex` |\n| `99Luftballons`        | `99luftballons`        |\n| `admin@example.com`    | `admin-at-example.com` |\n\n### Footnotes\n\n\u003csup\u003e\u003ca id=\"fn.1\" class=\"footnum\" href=\"#fnr.1\"\u003e1\u003c/a\u003e\u003c/sup\u003e [Object Names and IDs - kubernetes.io](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsap%2Fxp-clifford","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsap%2Fxp-clifford","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsap%2Fxp-clifford/lists"}