diff --git a/.gitignore b/.gitignore index 1dc16f55a..b39713749 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,9 @@ ts/create-kpt-functions/bin *.swo *.swp +# rapid property-based testing failure reproductions +**/testdata/rapid/ + +# compiled binaries +go/get-started/get-started + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..6a6ad0cbc --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,126 @@ +# Contributing to krm-functions-sdk + +We'd love to accept your contributions to this project. There are just a few +small guidelines you need to follow. + +## Developer Certificate of Origin (DCO) + +Contributors to this project should state that they agree with the terms published +at https://developercertificate.org/ for their contribution. To do this when +creating a commit with the Git CLI, a sign-off can be added with +[the -s option](https://git-scm.com/docs/git-commit#git-commit--s). The sign-off +is stored as part of the commit message itself. + +## Copyright notices + +All files should have the copyright notice. +``` +// Copyright 2026 The kpt Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +``` + +If the file has never been modified: use the creation year only + +* Example: `Copyright 2026 The kpt Authors` + +If the file has been modified: use a year range from creation to last modification + +* Example: `Copyright 2024-2026 The kpt Authors` + +## Building and Testing + +The SDK uses a Makefile-based workflow. From the repository root: + +```bash +# Run all checks (fix, vet, fmt, test, lint) +make go + +# Run only tests +cd go && make test + +# Run only linting +cd go && make lint + +# Tidy all go.mod files +make tidy +``` + +The CI script (`hack/ci-validate-go.sh`) runs `make go` and then checks that no +files were modified. If CI fails with "files are not to date", run `make go` +locally and commit the changes. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. Consult [GitHub Help] for more +information on using pull requests. + +Process for code reviews. Before requesting human review, a PR must: + +* All tests passing +* All linting passing +* Meeting project code quality requirements, including passing all configured + static analysis and not reducing automated test coverage +* The comments from the first run of automatically generated comments (AI + generated comments, bot generated comments, etc.) of the PR are addressed + (addressing further re-runs of AI are optional) +* If it is not possible to resolve an automatic comment, please add a sub-comment + indicating why the automated comment cannot be resolved or ask for help in + resolving the comment +* The PR description states whether AI was used to help create the PR; if so, it + lists the AI tools used and the areas where they were used + +## Declare any use of AI + +> In addition to the above, the use of AI in the creation of PRs is allowed, but +> you must declare any use of AI and you must be able to explain the PR code +> independently of any AI tools. + +Update the PR description to state whether you used AI to help you create this +PR; if so, list the AI tools you have used and in what areas. + +For example: +```text +I have used AI in the creation of this PR. + +I have used the following AI tools: +- GitHub Copilot to analyse the code +- Kiro to generate the implementation and tests +``` + +### Attribute AI in the Git commit messages + +Following the [guidance of the Linux kernel](https://docs.kernel.org/process/coding-assistants.html#attribution) +we recommend the attribution of AI tools in the commit messages using the following format: + +```text +Assisted-by: AGENT_NAME:MODEL_VERSION [TOOL1] [TOOL2] +``` + +## Community Guidelines + +This project follows a [Code of Conduct]. + +## Community Discussion Groups + +1. Join our [Slack channel](https://kubernetes.slack.com/channels/kpt) +1. Join our [Discussions](https://github.com/kptdev/kpt/discussions) + +## Governance + +The governance of the kpt project is described in the +[governance repo](https://github.com/kptdev/governance). + +[GitHub Help]: https://help.github.com/articles/about-pull-requests/ +[Code of Conduct]: code-of-conduct.md diff --git a/README.md b/README.md index 15508b162..fcc91f610 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,111 @@ # KRM Functions SDK + [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fkptdev%2Fkrm-functions-sdk.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fkptdev%2Fkrm-functions-sdk?ref=badge_shield) [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/10658/badge)](https://www.bestpractices.dev/projects/10658) -An opinionated Go SDK for implementing KRM functions. +An opinionated Go SDK for implementing [KRM functions](https://kpt.dev/book/05-developing-functions/). -## Documentation +## Quick Start -[Documentation](https://kpt.dev/book/05-developing-functions/#developing-in-go) +A KRM function is a program that reads Kubernetes resources from STDIN, transforms or validates them, and writes the result to STDOUT. The SDK handles the I/O — you write the logic. -## Issues +```go +package main + +import ( + "context" + _ "embed" + "os" + + "github.com/kptdev/krm-functions-sdk/go/fn" +) + +//go:embed README.md +var readme []byte + +//go:embed metadata.yaml +var metadata []byte + +type SetLabels struct { + Labels map[string]string `json:"labels,omitempty"` +} + +func (r *SetLabels) Run(ctx *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects, results *fn.Results) bool { + for _, obj := range items { + for k, v := range r.Labels { + obj.SetLabel(k, v) + } + } + return true +} + +func main() { + runner := fn.WithContext(context.Background(), &SetLabels{}) + if err := fn.AsMain(runner, fn.WithDocs(readme, metadata)); err != nil { + os.Exit(1) + } +} +``` + +A starter template is available at [`go/get-started/`](go/get-started/main.go). For the full walkthrough, see the [Tutorial](docs/tutorial.md). + +## How It Works -Please [Open Issues](https://github.com/kptdev/kpt/issues) for this repo at [kptdev/kpt](https://github.com/kptdev/kpt/). +`fn.AsMain` is the single entrypoint. It handles: -## Pull requests +- **STDIN/STDOUT** (default) — reads a ResourceList, processes it, writes the result +- **File mode** — pass file paths as positional args for local debugging +- **`--help`** — prints human-readable docs from embedded README markers +- **`--doc`** — outputs machine-readable JSON (consumed by `kpt fn doc` and catalog pipelines) -Open pull requests [here](https://github.com/kptdev/krm-functions-sdk/pulls). +Register embedded documentation with `fn.WithDocs`: -## Discussions +```go +fn.AsMain(runner, fn.WithDocs(readme, metadata)) +``` -Discussions are [here](https://github.com/kptdev/kpt/discussions). +The SDK provides two interfaces for implementing functions: + +| Interface | Use for | Can add/remove items? | Auto-parses config? | +|---|---|---|---| +| `fn.Runner` | Transformers, validators | No | Yes | +| `fn.ResourceListProcessor` | Generators, complex functions | Yes | No | + +See [Interfaces](docs/interfaces.md) for details and code examples. + +## Documentation + +- [API Reference](https://pkg.go.dev/github.com/kptdev/krm-functions-sdk/go/fn) — Go API docs +- [Tutorial](docs/tutorial.md) — end-to-end function development +- [Interfaces](docs/interfaces.md) — Runner vs ResourceListProcessor +- [Testing](docs/testing.md) — golden test patterns +- [Containerizing](docs/containerizing.md) — building and running function images + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on DCO sign-off, copyright headers, and code review process. + +## Issues + +Please [open issues](https://github.com/kptdev/kpt/issues) at [kptdev/kpt](https://github.com/kptdev/kpt/). ## License Code is under the [Apache License 2.0](LICENSE), documentation is [CC BY 4.0](LICENSE-documentation). - [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fkptdev%2Fkrm-functions-sdk.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fkptdev%2Fkrm-functions-sdk?ref=badge_large) ## Governance -The governance of the kpt project and KRM Functiona Catalog are described in the +The governance of the kpt project is described in the [governance repo](https://github.com/kptdev/governance). ## Code of Conduct -The kpt project and the KRM Functions Catalog are following the +The kpt project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). -More information and links about the CNCF Code of Conduct are [here](code-of-conduct.md). +More information is [here](code-of-conduct.md). ## CNCF -The kpt project including the KRM Functions Catalog is a [CNCF Sandbox](https://www.cncf.io/sandbox-projects/) project. +The kpt project is a [CNCF Sandbox](https://www.cncf.io/sandbox-projects/) project. diff --git a/docs/.nojekyll b/docs/.nojekyll deleted file mode 100644 index 139597f9c..000000000 --- a/docs/.nojekyll +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/docs/api/.nojekyll b/docs/api/.nojekyll deleted file mode 100644 index e2ac6616a..000000000 --- a/docs/api/.nojekyll +++ /dev/null @@ -1 +0,0 @@ -TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. \ No newline at end of file diff --git a/docs/api/assets/highlight.css b/docs/api/assets/highlight.css deleted file mode 100644 index 23dd8667e..000000000 --- a/docs/api/assets/highlight.css +++ /dev/null @@ -1,71 +0,0 @@ -:root { - --light-hl-0: #0000FF; - --dark-hl-0: #569CD6; - --light-hl-1: #000000; - --dark-hl-1: #D4D4D4; - --light-hl-2: #0070C1; - --dark-hl-2: #4FC1FF; - --light-hl-3: #001080; - --dark-hl-3: #9CDCFE; - --light-hl-4: #795E26; - --dark-hl-4: #DCDCAA; - --light-hl-5: #A31515; - --dark-hl-5: #CE9178; - --light-hl-6: #AF00DB; - --dark-hl-6: #C586C0; - --light-code-background: #FFFFFF; - --dark-code-background: #1E1E1E; -} - -@media (prefers-color-scheme: light) { :root { - --hl-0: var(--light-hl-0); - --hl-1: var(--light-hl-1); - --hl-2: var(--light-hl-2); - --hl-3: var(--light-hl-3); - --hl-4: var(--light-hl-4); - --hl-5: var(--light-hl-5); - --hl-6: var(--light-hl-6); - --code-background: var(--light-code-background); -} } - -@media (prefers-color-scheme: dark) { :root { - --hl-0: var(--dark-hl-0); - --hl-1: var(--dark-hl-1); - --hl-2: var(--dark-hl-2); - --hl-3: var(--dark-hl-3); - --hl-4: var(--dark-hl-4); - --hl-5: var(--dark-hl-5); - --hl-6: var(--dark-hl-6); - --code-background: var(--dark-code-background); -} } - -body.light { - --hl-0: var(--light-hl-0); - --hl-1: var(--light-hl-1); - --hl-2: var(--light-hl-2); - --hl-3: var(--light-hl-3); - --hl-4: var(--light-hl-4); - --hl-5: var(--light-hl-5); - --hl-6: var(--light-hl-6); - --code-background: var(--light-code-background); -} - -body.dark { - --hl-0: var(--dark-hl-0); - --hl-1: var(--dark-hl-1); - --hl-2: var(--dark-hl-2); - --hl-3: var(--dark-hl-3); - --hl-4: var(--dark-hl-4); - --hl-5: var(--dark-hl-5); - --hl-6: var(--dark-hl-6); - --code-background: var(--dark-code-background); -} - -.hl-0 { color: var(--hl-0); } -.hl-1 { color: var(--hl-1); } -.hl-2 { color: var(--hl-2); } -.hl-3 { color: var(--hl-3); } -.hl-4 { color: var(--hl-4); } -.hl-5 { color: var(--hl-5); } -.hl-6 { color: var(--hl-6); } -pre, code { background: var(--code-background); } diff --git a/docs/api/assets/icons.css b/docs/api/assets/icons.css deleted file mode 100644 index 776a3562d..000000000 --- a/docs/api/assets/icons.css +++ /dev/null @@ -1,1043 +0,0 @@ -.tsd-kind-icon { - display: block; - position: relative; - padding-left: 20px; - text-indent: -20px; -} -.tsd-kind-icon:before { - content: ""; - display: inline-block; - vertical-align: middle; - width: 17px; - height: 17px; - margin: 0 3px 2px 0; - background-image: url(./icons.png); -} -@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { - .tsd-kind-icon:before { - background-image: url(./icons@2x.png); - background-size: 238px 204px; - } -} - -.tsd-signature.tsd-kind-icon:before { - background-position: 0 -153px; -} - -.tsd-kind-object-literal > .tsd-kind-icon:before { - background-position: 0px -17px; -} -.tsd-kind-object-literal.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -17px; -} -.tsd-kind-object-literal.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -17px; -} - -.tsd-kind-class > .tsd-kind-icon:before { - background-position: 0px -34px; -} -.tsd-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -34px; -} -.tsd-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -34px; -} - -.tsd-kind-class.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: 0px -51px; -} -.tsd-kind-class.tsd-has-type-parameter.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -17px -51px; -} -.tsd-kind-class.tsd-has-type-parameter.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -51px; -} - -.tsd-kind-interface > .tsd-kind-icon:before { - background-position: 0px -68px; -} -.tsd-kind-interface.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -68px; -} -.tsd-kind-interface.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -68px; -} - -.tsd-kind-interface.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: 0px -85px; -} -.tsd-kind-interface.tsd-has-type-parameter.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -17px -85px; -} -.tsd-kind-interface.tsd-has-type-parameter.tsd-is-private - > .tsd-kind-icon:before { - background-position: -34px -85px; -} - -.tsd-kind-namespace > .tsd-kind-icon:before { - background-position: 0px -102px; -} -.tsd-kind-namespace.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -102px; -} -.tsd-kind-namespace.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -102px; -} - -.tsd-kind-module > .tsd-kind-icon:before { - background-position: 0px -102px; -} -.tsd-kind-module.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -102px; -} -.tsd-kind-module.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -102px; -} - -.tsd-kind-enum > .tsd-kind-icon:before { - background-position: 0px -119px; -} -.tsd-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -119px; -} -.tsd-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -119px; -} - -.tsd-kind-enum-member > .tsd-kind-icon:before { - background-position: 0px -136px; -} -.tsd-kind-enum-member.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -136px; -} -.tsd-kind-enum-member.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -136px; -} - -.tsd-kind-signature > .tsd-kind-icon:before { - background-position: 0px -153px; -} -.tsd-kind-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -153px; -} -.tsd-kind-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -153px; -} - -.tsd-kind-type-alias > .tsd-kind-icon:before { - background-position: 0px -170px; -} -.tsd-kind-type-alias.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -170px; -} -.tsd-kind-type-alias.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -170px; -} - -.tsd-kind-type-alias.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: 0px -187px; -} -.tsd-kind-type-alias.tsd-has-type-parameter.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -17px -187px; -} -.tsd-kind-type-alias.tsd-has-type-parameter.tsd-is-private - > .tsd-kind-icon:before { - background-position: -34px -187px; -} - -.tsd-kind-variable > .tsd-kind-icon:before { - background-position: -136px -0px; -} -.tsd-kind-variable.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -0px; -} -.tsd-kind-variable.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-variable.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -0px; -} -.tsd-kind-variable.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -0px; -} -.tsd-kind-variable.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-variable.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -0px; -} -.tsd-kind-variable.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -0px; -} - -.tsd-kind-property > .tsd-kind-icon:before { - background-position: -136px -0px; -} -.tsd-kind-property.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -0px; -} -.tsd-kind-property.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-property.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -0px; -} -.tsd-kind-property.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -0px; -} -.tsd-kind-property.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -0px; -} -.tsd-kind-property.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -0px; -} -.tsd-kind-property.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-property.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -0px; -} -.tsd-kind-property.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -0px; -} -.tsd-kind-property.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-property.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -0px; -} -.tsd-kind-property.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -0px; -} - -.tsd-kind-get-signature > .tsd-kind-icon:before { - background-position: -136px -17px; -} -.tsd-kind-get-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -17px; -} -.tsd-kind-get-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -17px; -} - -.tsd-kind-set-signature > .tsd-kind-icon:before { - background-position: -136px -34px; -} -.tsd-kind-set-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -34px; -} -.tsd-kind-set-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -34px; -} - -.tsd-kind-accessor > .tsd-kind-icon:before { - background-position: -136px -51px; -} -.tsd-kind-accessor.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -51px; -} -.tsd-kind-accessor.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -51px; -} - -.tsd-kind-function > .tsd-kind-icon:before { - background-position: -136px -68px; -} -.tsd-kind-function.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -68px; -} -.tsd-kind-function.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-function.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -68px; -} -.tsd-kind-function.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -68px; -} -.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -68px; -} -.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -68px; -} -.tsd-kind-function.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-function.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -68px; -} -.tsd-kind-function.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -68px; -} -.tsd-kind-function.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-function.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -68px; -} -.tsd-kind-function.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -68px; -} - -.tsd-kind-method > .tsd-kind-icon:before { - background-position: -136px -68px; -} -.tsd-kind-method.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -68px; -} -.tsd-kind-method.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-method.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -68px; -} -.tsd-kind-method.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -68px; -} -.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -68px; -} -.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -68px; -} -.tsd-kind-method.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-method.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -68px; -} -.tsd-kind-method.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -68px; -} -.tsd-kind-method.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-method.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -68px; -} -.tsd-kind-method.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -68px; -} - -.tsd-kind-call-signature > .tsd-kind-icon:before { - background-position: -136px -68px; -} -.tsd-kind-call-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -68px; -} -.tsd-kind-call-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -68px; -} - -.tsd-kind-function.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: -136px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -153px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class - > .tsd-kind-icon:before { - background-position: -51px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-enum - > .tsd-kind-icon:before { - background-position: -170px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -85px; -} - -.tsd-kind-method.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: -136px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -153px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class - > .tsd-kind-icon:before { - background-position: -51px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-enum - > .tsd-kind-icon:before { - background-position: -170px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -85px; -} - -.tsd-kind-constructor > .tsd-kind-icon:before { - background-position: -136px -102px; -} -.tsd-kind-constructor.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -102px; -} -.tsd-kind-constructor.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -102px; -} - -.tsd-kind-constructor-signature > .tsd-kind-icon:before { - background-position: -136px -102px; -} -.tsd-kind-constructor-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -102px; -} -.tsd-kind-constructor-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -102px; -} - -.tsd-kind-index-signature > .tsd-kind-icon:before { - background-position: -136px -119px; -} -.tsd-kind-index-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -119px; -} -.tsd-kind-index-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -119px; -} - -.tsd-kind-event > .tsd-kind-icon:before { - background-position: -136px -136px; -} -.tsd-kind-event.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -136px; -} -.tsd-kind-event.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -136px; -} -.tsd-kind-event.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -136px; -} -.tsd-kind-event.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -136px; -} -.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -136px; -} -.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -136px; -} -.tsd-kind-event.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -136px; -} -.tsd-kind-event.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -136px; -} -.tsd-kind-event.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -136px; -} -.tsd-kind-event.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -136px; -} -.tsd-kind-event.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -136px; -} -.tsd-kind-event.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -136px; -} - -.tsd-is-static > .tsd-kind-icon:before { - background-position: -136px -153px; -} -.tsd-is-static.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -153px; -} -.tsd-is-static.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -153px; -} -.tsd-is-static.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -153px; -} -.tsd-is-static.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -153px; -} -.tsd-is-static.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -153px; -} -.tsd-is-static.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -153px; -} -.tsd-is-static.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -153px; -} -.tsd-is-static.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -153px; -} -.tsd-is-static.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -153px; -} -.tsd-is-static.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -153px; -} -.tsd-is-static.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -153px; -} -.tsd-is-static.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -153px; -} - -.tsd-is-static.tsd-kind-function > .tsd-kind-icon:before { - background-position: -136px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -170px; -} - -.tsd-is-static.tsd-kind-method > .tsd-kind-icon:before { - background-position: -136px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -170px; -} - -.tsd-is-static.tsd-kind-call-signature > .tsd-kind-icon:before { - background-position: -136px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -153px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class - > .tsd-kind-icon:before { - background-position: -51px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-enum - > .tsd-kind-icon:before { - background-position: -170px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -170px; -} - -.tsd-is-static.tsd-kind-event > .tsd-kind-icon:before { - background-position: -136px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-interface - > .tsd-kind-icon:before { - background-position: -204px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -187px; -} diff --git a/docs/api/assets/icons.png b/docs/api/assets/icons.png deleted file mode 100644 index 3836d5fe4..000000000 Binary files a/docs/api/assets/icons.png and /dev/null differ diff --git a/docs/api/assets/icons@2x.png b/docs/api/assets/icons@2x.png deleted file mode 100644 index 5a209e2f6..000000000 Binary files a/docs/api/assets/icons@2x.png and /dev/null differ diff --git a/docs/api/assets/main.js b/docs/api/assets/main.js deleted file mode 100644 index 1cc526e6e..000000000 --- a/docs/api/assets/main.js +++ /dev/null @@ -1,52 +0,0 @@ -(()=>{var Ce=Object.create;var J=Object.defineProperty;var Pe=Object.getOwnPropertyDescriptor;var Oe=Object.getOwnPropertyNames;var Re=Object.getPrototypeOf,_e=Object.prototype.hasOwnProperty;var Me=t=>J(t,"__esModule",{value:!0});var Fe=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var De=(t,e,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of Oe(e))!_e.call(t,n)&&n!=="default"&&J(t,n,{get:()=>e[n],enumerable:!(r=Pe(e,n))||r.enumerable});return t},Ae=t=>De(Me(J(t!=null?Ce(Re(t)):{},"default",t&&t.__esModule&&"default"in t?{get:()=>t.default,enumerable:!0}:{value:t,enumerable:!0})),t);var de=Fe((ue,he)=>{(function(){var t=function(e){var r=new t.Builder;return r.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),r.searchPipeline.add(t.stemmer),e.call(r,r),r.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(r){e.console&&console.warn&&console.warn(r)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var r=Object.create(null),n=Object.keys(e),i=0;i0){var h=t.utils.clone(r)||{};h.position=[a,l],h.index=s.length,s.push(new t.Token(n.slice(a,o),h))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,r){r in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+r),e.label=r,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var r=e.label&&e.label in this.registeredFunctions;r||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. -`,e)},t.Pipeline.load=function(e){var r=new t.Pipeline;return e.forEach(function(n){var i=t.Pipeline.registeredFunctions[n];if(i)r.add(i);else throw new Error("Cannot load unregistered function: "+n)}),r},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(r){t.Pipeline.warnIfFunctionNotRegistered(r),this._stack.push(r)},this)},t.Pipeline.prototype.after=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");n=n+1,this._stack.splice(n,0,r)},t.Pipeline.prototype.before=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");this._stack.splice(n,0,r)},t.Pipeline.prototype.remove=function(e){var r=this._stack.indexOf(e);r!=-1&&this._stack.splice(r,1)},t.Pipeline.prototype.run=function(e){for(var r=this._stack.length,n=0;n1&&(oe&&(n=s),o!=e);)i=n-r,s=r+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(oc?h+=2:a==c&&(r+=n[l+1]*i[h+1],l+=2,h+=2);return r},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),r=1,n=0;r0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var c=s.node.edges["*"];else{var c=new t.TokenSet;s.node.edges["*"]=c}if(s.str.length==0&&(c.final=!0),i.push({node:c,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}s.str.length==1&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var h=s.str.charAt(0),f=s.str.charAt(1),v;f in s.node.edges?v=s.node.edges[f]:(v=new t.TokenSet,s.node.edges[f]=v),s.str.length==1&&(v.final=!0),i.push({node:v,editsRemaining:s.editsRemaining-1,str:h+s.str.slice(2)})}}}return n},t.TokenSet.fromString=function(e){for(var r=new t.TokenSet,n=r,i=0,s=e.length;i=e;r--){var n=this.uncheckedNodes[r],i=n.child.toString();i in this.minimizedNodes?n.parent.edges[n.char]=this.minimizedNodes[i]:(n.child._str=i,this.minimizedNodes[i]=n.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(r){var n=new t.QueryParser(e,r);n.parse()})},t.Index.prototype.query=function(e){for(var r=new t.Query(this.fields),n=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),c=0;c1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,r){var n=e[this._ref],i=Object.keys(this._fields);this._documents[n]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,r;do e=this.next(),r=e.charCodeAt(0);while(r>47&&r<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var r=e.next();if(r==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(r.charCodeAt(0)==92){e.escapeCharacter();continue}if(r==":")return t.QueryLexer.lexField;if(r=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(r=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(r=="+"&&e.width()===1||r=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(r.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,r){this.lexer=new t.QueryLexer(e),this.query=r,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var r=e.peekLexeme();if(r!=null)switch(r.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(n+=" with value '"+r.str+"'"),new t.QueryParseError(n,r.start,r.end)}},t.QueryParser.parsePresence=function(e){var r=e.consumeLexeme();if(r!=null){switch(r.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+r.str+"'";throw new t.QueryParseError(n,r.start,r.end)}var i=e.peekLexeme();if(i==null){var n="expecting term or field, found nothing";throw new t.QueryParseError(n,r.start,r.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(n,i.start,i.end)}}},t.QueryParser.parseField=function(e){var r=e.consumeLexeme();if(r!=null){if(e.query.allFields.indexOf(r.str)==-1){var n=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+r.str+"', possible fields: "+n;throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.fields=[r.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,r.start,r.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var r=e.consumeLexeme();if(r!=null){e.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(n==null){e.nextClause();return}switch(n.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+n.type+"'";throw new t.QueryParseError(i,n.start,n.end)}}},t.QueryParser.parseEditDistance=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="edit distance must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.editDistance=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="boost must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.boost=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,r){typeof define=="function"&&define.amd?define(r):typeof ue=="object"?he.exports=r():e.lunr=r()}(this,function(){return t})})()});var le=[];function N(t,e){le.push({selector:e,constructor:t})}var X=class{constructor(){this.createComponents(document.body)}createComponents(e){le.forEach(r=>{e.querySelectorAll(r.selector).forEach(n=>{n.dataset.hasInstance||(new r.constructor({el:n}),n.dataset.hasInstance=String(!0))})})}};var Q=class{constructor(e){this.el=e.el}};var Z=class{constructor(){this.listeners={}}addEventListener(e,r){e in this.listeners||(this.listeners[e]=[]),this.listeners[e].push(r)}removeEventListener(e,r){if(!(e in this.listeners))return;let n=this.listeners[e];for(let i=0,s=n.length;i{let r=Date.now();return(...n)=>{r+e-Date.now()<0&&(t(...n),r=Date.now())}};var ee=class extends Z{constructor(){super();this.scrollTop=0;this.lastY=0;this.width=0;this.height=0;this.showToolbar=!0;this.toolbar=document.querySelector(".tsd-page-toolbar"),this.secondaryNav=document.querySelector(".tsd-navigation.secondary"),window.addEventListener("scroll",K(()=>this.onScroll(),10)),window.addEventListener("resize",K(()=>this.onResize(),10)),this.onResize(),this.onScroll()}triggerResize(){let e=new CustomEvent("resize",{detail:{width:this.width,height:this.height}});this.dispatchEvent(e)}onResize(){this.width=window.innerWidth||0,this.height=window.innerHeight||0;let e=new CustomEvent("resize",{detail:{width:this.width,height:this.height}});this.dispatchEvent(e)}onScroll(){this.scrollTop=window.scrollY||0;let e=new CustomEvent("scroll",{detail:{scrollTop:this.scrollTop}});this.dispatchEvent(e),this.hideShowToolbar()}hideShowToolbar(){let e=this.showToolbar;this.showToolbar=this.lastY>=this.scrollTop||this.scrollTop<=0,e!==this.showToolbar&&(this.toolbar.classList.toggle("tsd-page-toolbar--hide"),this.secondaryNav.classList.toggle("tsd-navigation--toolbar-hide")),this.lastY=this.scrollTop}},I=ee;I.instance=new ee;var te=class extends Q{constructor(e){super(e);this.anchors=[];this.index=-1;I.instance.addEventListener("resize",()=>this.onResize()),I.instance.addEventListener("scroll",r=>this.onScroll(r)),this.createAnchors()}createAnchors(){let e=window.location.href;e.indexOf("#")!=-1&&(e=e.substr(0,e.indexOf("#"))),this.el.querySelectorAll("a").forEach(r=>{let n=r.href;if(n.indexOf("#")==-1||n.substr(0,e.length)!=e)return;let i=n.substr(n.indexOf("#")+1),s=document.querySelector("a.tsd-anchor[name="+i+"]"),o=r.parentNode;!s||!o||this.anchors.push({link:o,anchor:s,position:0})}),this.onResize()}onResize(){let e;for(let n=0,i=this.anchors.length;nn.position-i.position);let r=new CustomEvent("scroll",{detail:{scrollTop:I.instance.scrollTop}});this.onScroll(r)}onScroll(e){let r=e.detail.scrollTop+5,n=this.anchors,i=n.length-1,s=this.index;for(;s>-1&&n[s].position>r;)s-=1;for(;s-1&&this.anchors[this.index].link.classList.remove("focus"),this.index=s,this.index>-1&&this.anchors[this.index].link.classList.add("focus"))}};var ce=(t,e=100)=>{let r;return(...n)=>{clearTimeout(r),r=setTimeout(()=>t(n),e)}};var pe=Ae(de());function fe(){let t=document.getElementById("tsd-search");if(!t)return;let e=document.getElementById("search-script");t.classList.add("loading"),e&&(e.addEventListener("error",()=>{t.classList.remove("loading"),t.classList.add("failure")}),e.addEventListener("load",()=>{t.classList.remove("loading"),t.classList.add("ready")}),window.searchData&&t.classList.remove("loading"));let r=document.querySelector("#tsd-search input"),n=document.querySelector("#tsd-search .results");if(!r||!n)throw new Error("The input field or the result list wrapper was not found");let i=!1;n.addEventListener("mousedown",()=>i=!0),n.addEventListener("mouseup",()=>{i=!1,t.classList.remove("has-focus")}),r.addEventListener("focus",()=>t.classList.add("has-focus")),r.addEventListener("blur",()=>{i||(i=!1,t.classList.remove("has-focus"))});let s={base:t.dataset.base+"/"};Ve(t,n,r,s)}function Ve(t,e,r,n){r.addEventListener("input",ce(()=>{ze(t,e,r,n)},200));let i=!1;r.addEventListener("keydown",s=>{i=!0,s.key=="Enter"?Ne(e,r):s.key=="Escape"?r.blur():s.key=="ArrowUp"?me(e,-1):s.key==="ArrowDown"?me(e,1):i=!1}),r.addEventListener("keypress",s=>{i&&s.preventDefault()}),document.body.addEventListener("keydown",s=>{s.altKey||s.ctrlKey||s.metaKey||!r.matches(":focus")&&s.key==="/"&&(r.focus(),s.preventDefault())})}function He(t,e){t.index||window.searchData&&(e.classList.remove("loading"),e.classList.add("ready"),t.data=window.searchData,t.index=pe.Index.load(window.searchData.index))}function ze(t,e,r,n){if(He(n,t),!n.index||!n.data)return;e.textContent="";let i=r.value.trim(),s=n.index.search(`*${i}*`);for(let o=0,a=Math.min(10,s.length);o${ve(c.parent,i)}.${l}`);let h=document.createElement("li");h.classList.value=c.classes;let f=document.createElement("a");f.href=n.base+c.url,f.classList.add("tsd-kind-icon"),f.innerHTML=l,h.append(f),e.appendChild(h)}}function me(t,e){let r=t.querySelector(".current");if(!r)r=t.querySelector(e==1?"li:first-child":"li:last-child"),r&&r.classList.add("current");else{let n=r;if(e===1)do n=n.nextElementSibling;while(n instanceof HTMLElement&&n.offsetParent==null);else do n=n.previousElementSibling;while(n instanceof HTMLElement&&n.offsetParent==null);n&&(r.classList.remove("current"),n.classList.add("current"))}}function Ne(t,e){let r=t.querySelector(".current");if(r||(r=t.querySelector("li:first-child")),r){let n=r.querySelector("a");n&&(window.location.href=n.href),e.blur()}}function ve(t,e){if(e==="")return t;let r=t.toLocaleLowerCase(),n=e.toLocaleLowerCase(),i=[],s=0,o=r.indexOf(n);for(;o!=-1;)i.push(re(t.substring(s,o)),`${re(t.substring(o,o+n.length))}`),s=o+n.length,o=r.indexOf(n,s);return i.push(re(t.substring(s))),i.join("")}var je={"&":"&","<":"<",">":">","'":"'",'"':"""};function re(t){return t.replace(/[&<>"'"]/g,e=>je[e])}var ge=class{constructor(e,r){this.signature=e,this.description=r}addClass(e){return this.signature.classList.add(e),this.description.classList.add(e),this}removeClass(e){return this.signature.classList.remove(e),this.description.classList.remove(e),this}},ne=class extends Q{constructor(e){super(e);this.groups=[];this.index=-1;this.createGroups(),this.container&&(this.el.classList.add("active"),Array.from(this.el.children).forEach(r=>{r.addEventListener("touchstart",n=>this.onClick(n)),r.addEventListener("click",n=>this.onClick(n))}),this.container.classList.add("active"),this.setIndex(0))}setIndex(e){if(e<0&&(e=0),e>this.groups.length-1&&(e=this.groups.length-1),this.index==e)return;let r=this.groups[e];if(this.index>-1){let n=this.groups[this.index];n.removeClass("current").addClass("fade-out"),r.addClass("current"),r.addClass("fade-in"),I.instance.triggerResize(),setTimeout(()=>{n.removeClass("fade-out"),r.removeClass("fade-in")},300)}else r.addClass("current"),I.instance.triggerResize();this.index=e}createGroups(){let e=this.el.children;if(e.length<2)return;this.container=this.el.nextElementSibling;let r=this.container.children;this.groups=[];for(let n=0;n{r.signature===e.currentTarget&&this.setIndex(n)})}};var C="mousedown",ye="mousemove",_="mouseup",G={x:0,y:0},xe=!1,ie=!1,Be=!1,A=!1,Le=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(Le?"is-mobile":"not-mobile");Le&&"ontouchstart"in document.documentElement&&(Be=!0,C="touchstart",ye="touchmove",_="touchend");document.addEventListener(C,t=>{ie=!0,A=!1;let e=C=="touchstart"?t.targetTouches[0]:t;G.y=e.pageY||0,G.x=e.pageX||0});document.addEventListener(ye,t=>{if(!!ie&&!A){let e=C=="touchstart"?t.targetTouches[0]:t,r=G.x-(e.pageX||0),n=G.y-(e.pageY||0);A=Math.sqrt(r*r+n*n)>10}});document.addEventListener(_,()=>{ie=!1});document.addEventListener("click",t=>{xe&&(t.preventDefault(),t.stopImmediatePropagation(),xe=!1)});var se=class extends Q{constructor(e){super(e);this.className=this.el.dataset.toggle||"",this.el.addEventListener(_,r=>this.onPointerUp(r)),this.el.addEventListener("click",r=>r.preventDefault()),document.addEventListener(C,r=>this.onDocumentPointerDown(r)),document.addEventListener(_,r=>this.onDocumentPointerUp(r))}setActive(e){if(this.active==e)return;this.active=e,document.documentElement.classList.toggle("has-"+this.className,e),this.el.classList.toggle("active",e);let r=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(r),setTimeout(()=>document.documentElement.classList.remove(r),500)}onPointerUp(e){A||(this.setActive(!0),e.preventDefault())}onDocumentPointerDown(e){if(this.active){if(e.target.closest(".col-menu, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(e){if(!A&&this.active&&e.target.closest(".col-menu")){let r=e.target.closest("a");if(r){let n=window.location.href;n.indexOf("#")!=-1&&(n=n.substr(0,n.indexOf("#"))),r.href.substr(0,n.length)==n&&setTimeout(()=>this.setActive(!1),250)}}}};var oe=class{constructor(e,r){this.key=e,this.value=r,this.defaultValue=r,this.initialize(),window.localStorage[this.key]&&this.setValue(this.fromLocalStorage(window.localStorage[this.key]))}initialize(){}setValue(e){if(this.value==e)return;let r=this.value;this.value=e,window.localStorage[this.key]=this.toLocalStorage(e),this.handleValueChange(r,e)}},ae=class extends oe{initialize(){let e=document.querySelector("#tsd-filter-"+this.key);!e||(this.checkbox=e,this.checkbox.addEventListener("change",()=>{this.setValue(this.checkbox.checked)}))}handleValueChange(e,r){!this.checkbox||(this.checkbox.checked=this.value,document.documentElement.classList.toggle("toggle-"+this.key,this.value!=this.defaultValue))}fromLocalStorage(e){return e=="true"}toLocalStorage(e){return e?"true":"false"}},Ee=class extends oe{initialize(){document.documentElement.classList.add("toggle-"+this.key+this.value);let e=document.querySelector("#tsd-filter-"+this.key);if(!e)return;this.select=e;let r=()=>{this.select.classList.add("active")},n=()=>{this.select.classList.remove("active")};this.select.addEventListener(C,r),this.select.addEventListener("mouseover",r),this.select.addEventListener("mouseleave",n),this.select.querySelectorAll("li").forEach(i=>{i.addEventListener(_,s=>{e.classList.remove("active"),this.setValue(s.target.dataset.value||"")})}),document.addEventListener(C,i=>{this.select.contains(i.target)||this.select.classList.remove("active")})}handleValueChange(e,r){this.select.querySelectorAll("li.selected").forEach(s=>{s.classList.remove("selected")});let n=this.select.querySelector('li[data-value="'+r+'"]'),i=this.select.querySelector(".tsd-select-label");n&&i&&(n.classList.add("selected"),i.textContent=n.textContent),document.documentElement.classList.remove("toggle-"+e),document.documentElement.classList.add("toggle-"+r)}fromLocalStorage(e){return e}toLocalStorage(e){return e}},Y=class extends Q{constructor(e){super(e);this.optionVisibility=new Ee("visibility","private"),this.optionInherited=new ae("inherited",!0),this.optionExternals=new ae("externals",!0)}static isSupported(){try{return typeof window.localStorage!="undefined"}catch(e){return!1}}};function be(t){let e=localStorage.getItem("tsd-theme")||"os";t.value=e,we(e),t.addEventListener("change",()=>{localStorage.setItem("tsd-theme",t.value),we(t.value)})}function we(t){switch(t){case"os":document.body.classList.remove("light","dark");break;case"light":document.body.classList.remove("dark"),document.body.classList.add("light");break;case"dark":document.body.classList.remove("light"),document.body.classList.add("dark");break}}fe();N(te,".menu-highlight");N(ne,".tsd-signatures");N(se,"a[data-toggle]");Y.isSupported()?N(Y,"#tsd-filter"):document.documentElement.classList.add("no-filter");var Te=document.getElementById("theme");Te&&be(Te);var qe=new X;Object.defineProperty(window,"app",{value:qe});})(); -/*! - * lunr.Builder - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.Index - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.Pipeline - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.Set - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.TokenSet - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.Vector - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.stemmer - * Copyright (C) 2020 Oliver Nightingale - * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt - */ -/*! - * lunr.stopWordFilter - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.tokenizer - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.trimmer - * Copyright (C) 2020 Oliver Nightingale - */ -/*! - * lunr.utils - * Copyright (C) 2020 Oliver Nightingale - */ -/** - * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.9 - * Copyright (C) 2020 Oliver Nightingale - * @license MIT - */ diff --git a/docs/api/assets/search.js b/docs/api/assets/search.js deleted file mode 100644 index 52a9f31b4..000000000 --- a/docs/api/assets/search.js +++ /dev/null @@ -1 +0,0 @@ -window.searchData = {"kinds":{"32":"Variable","64":"Function","128":"Class","256":"Interface","512":"Constructor","1024":"Property","2048":"Method","65536":"Type literal","4194304":"Type alias"},"rows":[{"id":0,"kind":128,"name":"FunctionConfigError","url":"classes/FunctionConfigError.html","classes":"tsd-kind-class"},{"id":1,"kind":512,"name":"constructor","url":"classes/FunctionConfigError.html#constructor","classes":"tsd-kind-constructor tsd-parent-kind-class tsd-is-overwrite","parent":"FunctionConfigError"},{"id":2,"kind":64,"name":"addAnnotation","url":"index.html#addAnnotation","classes":"tsd-kind-function"},{"id":3,"kind":64,"name":"removeAnnotation","url":"index.html#removeAnnotation","classes":"tsd-kind-function"},{"id":4,"kind":64,"name":"getAnnotation","url":"index.html#getAnnotation","classes":"tsd-kind-function"},{"id":5,"kind":64,"name":"addLabel","url":"index.html#addLabel","classes":"tsd-kind-function"},{"id":6,"kind":64,"name":"removeLabel","url":"index.html#removeLabel","classes":"tsd-kind-function"},{"id":7,"kind":64,"name":"getLabel","url":"index.html#getLabel","classes":"tsd-kind-function"},{"id":8,"kind":32,"name":"ANNOTATION_PREFIX","url":"index.html#ANNOTATION_PREFIX","classes":"tsd-kind-variable"},{"id":9,"kind":32,"name":"SOURCE_PATH_ANNOTATION","url":"index.html#SOURCE_PATH_ANNOTATION","classes":"tsd-kind-variable"},{"id":10,"kind":32,"name":"SOURCE_INDEX_ANNOTATION","url":"index.html#SOURCE_INDEX_ANNOTATION","classes":"tsd-kind-variable"},{"id":11,"kind":32,"name":"ID_ANNOTATION","url":"index.html#ID_ANNOTATION","classes":"tsd-kind-variable"},{"id":12,"kind":32,"name":"LEGACY_ANNOTATION_PREFIX","url":"index.html#LEGACY_ANNOTATION_PREFIX","classes":"tsd-kind-variable"},{"id":13,"kind":32,"name":"LEGACY_SOURCE_PATH_ANNOTATION","url":"index.html#LEGACY_SOURCE_PATH_ANNOTATION","classes":"tsd-kind-variable"},{"id":14,"kind":32,"name":"LEGACY_SOURCE_INDEX_ANNOTATION","url":"index.html#LEGACY_SOURCE_INDEX_ANNOTATION","classes":"tsd-kind-variable"},{"id":15,"kind":32,"name":"LEGACY_ID_ANNOTATION","url":"index.html#LEGACY_ID_ANNOTATION","classes":"tsd-kind-variable"},{"id":16,"kind":64,"name":"generalResult","url":"index.html#generalResult","classes":"tsd-kind-function"},{"id":17,"kind":64,"name":"configFileResult","url":"index.html#configFileResult","classes":"tsd-kind-function"},{"id":18,"kind":64,"name":"kubernetesObjectResult","url":"index.html#kubernetesObjectResult","classes":"tsd-kind-function"},{"id":19,"kind":64,"name":"run","url":"index.html#run","classes":"tsd-kind-function"},{"id":20,"kind":64,"name":"runFnWithConfigs","url":"index.html#runFnWithConfigs","classes":"tsd-kind-function"},{"id":21,"kind":128,"name":"TestRunner","url":"classes/TestRunner.html","classes":"tsd-kind-class"},{"id":22,"kind":512,"name":"constructor","url":"classes/TestRunner.html#constructor","classes":"tsd-kind-constructor tsd-parent-kind-class","parent":"TestRunner"},{"id":23,"kind":2048,"name":"assert","url":"classes/TestRunner.html#assert","classes":"tsd-kind-method tsd-parent-kind-class","parent":"TestRunner"},{"id":24,"kind":2048,"name":"assertCallback","url":"classes/TestRunner.html#assertCallback","classes":"tsd-kind-method tsd-parent-kind-class","parent":"TestRunner"},{"id":25,"kind":64,"name":"isKubernetesObject","url":"index.html#isKubernetesObject","classes":"tsd-kind-function"},{"id":26,"kind":64,"name":"kubernetesKey","url":"index.html#kubernetesKey","classes":"tsd-kind-function"},{"id":27,"kind":256,"name":"KptFunc","url":"interfaces/KptFunc.html","classes":"tsd-kind-interface"},{"id":28,"kind":1024,"name":"usage","url":"interfaces/KptFunc.html#usage","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"KptFunc"},{"id":29,"kind":128,"name":"Configs","url":"classes/Configs.html","classes":"tsd-kind-class"},{"id":30,"kind":512,"name":"constructor","url":"classes/Configs.html#constructor","classes":"tsd-kind-constructor tsd-parent-kind-class","parent":"Configs"},{"id":31,"kind":2048,"name":"getAll","url":"classes/Configs.html#getAll","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":32,"kind":2048,"name":"get","url":"classes/Configs.html#get","classes":"tsd-kind-method tsd-parent-kind-class tsd-has-type-parameter","parent":"Configs"},{"id":33,"kind":2048,"name":"insert","url":"classes/Configs.html#insert","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":34,"kind":2048,"name":"delete","url":"classes/Configs.html#delete","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":35,"kind":2048,"name":"deleteAll","url":"classes/Configs.html#deleteAll","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":36,"kind":2048,"name":"groupBy","url":"classes/Configs.html#groupBy","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":37,"kind":2048,"name":"getFunctionConfig","url":"classes/Configs.html#getFunctionConfig","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":38,"kind":2048,"name":"getFunctionConfigMap","url":"classes/Configs.html#getFunctionConfigMap","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":39,"kind":2048,"name":"getFunctionConfigValue","url":"classes/Configs.html#getFunctionConfigValue","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":40,"kind":2048,"name":"hasUnexpectedFunctionParameter","url":"classes/Configs.html#hasUnexpectedFunctionParameter","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":41,"kind":2048,"name":"getFunctionConfigValueOrThrow","url":"classes/Configs.html#getFunctionConfigValueOrThrow","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":42,"kind":2048,"name":"addResults","url":"classes/Configs.html#addResults","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":43,"kind":2048,"name":"getResults","url":"classes/Configs.html#getResults","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":44,"kind":2048,"name":"toResourceList","url":"classes/Configs.html#toResourceList","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":45,"kind":2048,"name":"deepCopy","url":"classes/Configs.html#deepCopy","classes":"tsd-kind-method tsd-parent-kind-class","parent":"Configs"},{"id":46,"kind":1024,"name":"logToStdErr","url":"classes/Configs.html#logToStdErr","classes":"tsd-kind-property tsd-parent-kind-class","parent":"Configs"},{"id":47,"kind":256,"name":"KubernetesObject","url":"interfaces/KubernetesObject.html","classes":"tsd-kind-interface"},{"id":48,"kind":1024,"name":"apiVersion","url":"interfaces/KubernetesObject.html#apiVersion","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"KubernetesObject"},{"id":49,"kind":1024,"name":"kind","url":"interfaces/KubernetesObject.html#kind","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"KubernetesObject"},{"id":50,"kind":1024,"name":"metadata","url":"interfaces/KubernetesObject.html#metadata","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"KubernetesObject"},{"id":51,"kind":128,"name":"ResourceList","url":"classes/ResourceList.html","classes":"tsd-kind-class"},{"id":52,"kind":512,"name":"constructor","url":"classes/ResourceList.html#constructor","classes":"tsd-kind-constructor tsd-parent-kind-class","parent":"ResourceList"},{"id":53,"kind":1024,"name":"apiVersion","url":"classes/ResourceList.html#apiVersion","classes":"tsd-kind-property tsd-parent-kind-class","parent":"ResourceList"},{"id":54,"kind":1024,"name":"kind","url":"classes/ResourceList.html#kind","classes":"tsd-kind-property tsd-parent-kind-class","parent":"ResourceList"},{"id":55,"kind":1024,"name":"metadata","url":"classes/ResourceList.html#metadata","classes":"tsd-kind-property tsd-parent-kind-class","parent":"ResourceList"},{"id":56,"kind":65536,"name":"__type","url":"classes/ResourceList.html#__type","classes":"tsd-kind-type-literal tsd-parent-kind-class","parent":"ResourceList"},{"id":57,"kind":1024,"name":"name","url":"classes/ResourceList.html#__type.name","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"ResourceList.__type"},{"id":58,"kind":1024,"name":"items","url":"classes/ResourceList.html#items","classes":"tsd-kind-property tsd-parent-kind-class","parent":"ResourceList"},{"id":59,"kind":1024,"name":"results","url":"classes/ResourceList.html#results","classes":"tsd-kind-property tsd-parent-kind-class","parent":"ResourceList"},{"id":60,"kind":256,"name":"Result","url":"interfaces/Result.html","classes":"tsd-kind-interface"},{"id":61,"kind":1024,"name":"severity","url":"interfaces/Result.html#severity","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"Result"},{"id":62,"kind":1024,"name":"message","url":"interfaces/Result.html#message","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"Result"},{"id":63,"kind":1024,"name":"tags","url":"interfaces/Result.html#tags","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"Result"},{"id":64,"kind":65536,"name":"__type","url":"interfaces/Result.html#__type-2","classes":"tsd-kind-type-literal tsd-parent-kind-interface","parent":"Result"},{"id":65,"kind":1024,"name":"resourceRef","url":"interfaces/Result.html#resourceRef","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"Result"},{"id":66,"kind":65536,"name":"__type","url":"interfaces/Result.html#__type-1","classes":"tsd-kind-type-literal tsd-parent-kind-interface","parent":"Result"},{"id":67,"kind":1024,"name":"apiVersion","url":"interfaces/Result.html#__type-1.apiVersion","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"Result.__type"},{"id":68,"kind":1024,"name":"kind","url":"interfaces/Result.html#__type-1.kind","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"Result.__type"},{"id":69,"kind":1024,"name":"namespace","url":"interfaces/Result.html#__type-1.namespace","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"Result.__type"},{"id":70,"kind":1024,"name":"name","url":"interfaces/Result.html#__type-1.name","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"Result.__type"},{"id":71,"kind":1024,"name":"file","url":"interfaces/Result.html#file","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"Result"},{"id":72,"kind":65536,"name":"__type","url":"interfaces/Result.html#__type","classes":"tsd-kind-type-literal tsd-parent-kind-interface","parent":"Result"},{"id":73,"kind":1024,"name":"path","url":"interfaces/Result.html#__type.path","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"Result.__type"},{"id":74,"kind":1024,"name":"index","url":"interfaces/Result.html#__type.index","classes":"tsd-kind-property tsd-parent-kind-type-literal","parent":"Result.__type"},{"id":75,"kind":1024,"name":"field","url":"interfaces/Result.html#field","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"Result"},{"id":76,"kind":4194304,"name":"Severity","url":"index.html#Severity","classes":"tsd-kind-type-alias"},{"id":77,"kind":256,"name":"JsonArray","url":"interfaces/JsonArray.html","classes":"tsd-kind-interface"},{"id":78,"kind":256,"name":"JsonMap","url":"interfaces/JsonMap.html","classes":"tsd-kind-interface"},{"id":79,"kind":4194304,"name":"Json","url":"index.html#Json","classes":"tsd-kind-type-alias"},{"id":80,"kind":256,"name":"FieldInfo","url":"interfaces/FieldInfo.html","classes":"tsd-kind-interface"},{"id":81,"kind":1024,"name":"path","url":"interfaces/FieldInfo.html#path","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"FieldInfo"},{"id":82,"kind":1024,"name":"currentValue","url":"interfaces/FieldInfo.html#currentValue","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"FieldInfo"},{"id":83,"kind":1024,"name":"suggestedValue","url":"interfaces/FieldInfo.html#suggestedValue","classes":"tsd-kind-property tsd-parent-kind-interface","parent":"FieldInfo"}],"index":{"version":"2.3.9","fields":["name","parent"],"fieldVectors":[["name/0",[0,35.264]],["parent/0",[]],["name/1",[1,29.386]],["parent/1",[0,2.788]],["name/2",[2,40.372]],["parent/2",[]],["name/3",[3,40.372]],["parent/3",[]],["name/4",[4,40.372]],["parent/4",[]],["name/5",[5,40.372]],["parent/5",[]],["name/6",[6,40.372]],["parent/6",[]],["name/7",[7,40.372]],["parent/7",[]],["name/8",[8,40.372]],["parent/8",[]],["name/9",[9,40.372]],["parent/9",[]],["name/10",[10,40.372]],["parent/10",[]],["name/11",[11,40.372]],["parent/11",[]],["name/12",[12,40.372]],["parent/12",[]],["name/13",[13,40.372]],["parent/13",[]],["name/14",[14,40.372]],["parent/14",[]],["name/15",[15,40.372]],["parent/15",[]],["name/16",[16,40.372]],["parent/16",[]],["name/17",[17,40.372]],["parent/17",[]],["name/18",[18,40.372]],["parent/18",[]],["name/19",[19,40.372]],["parent/19",[]],["name/20",[20,40.372]],["parent/20",[]],["name/21",[21,29.386]],["parent/21",[]],["name/22",[1,29.386]],["parent/22",[21,2.324]],["name/23",[22,40.372]],["parent/23",[21,2.324]],["name/24",[23,40.372]],["parent/24",[21,2.324]],["name/25",[24,40.372]],["parent/25",[]],["name/26",[25,40.372]],["parent/26",[]],["name/27",[26,35.264]],["parent/27",[]],["name/28",[27,40.372]],["parent/28",[26,2.788]],["name/29",[28,15.249]],["parent/29",[]],["name/30",[1,29.386]],["parent/30",[28,1.206]],["name/31",[29,40.372]],["parent/31",[28,1.206]],["name/32",[30,40.372]],["parent/32",[28,1.206]],["name/33",[31,40.372]],["parent/33",[28,1.206]],["name/34",[32,40.372]],["parent/34",[28,1.206]],["name/35",[33,40.372]],["parent/35",[28,1.206]],["name/36",[34,40.372]],["parent/36",[28,1.206]],["name/37",[35,40.372]],["parent/37",[28,1.206]],["name/38",[36,40.372]],["parent/38",[28,1.206]],["name/39",[37,40.372]],["parent/39",[28,1.206]],["name/40",[38,40.372]],["parent/40",[28,1.206]],["name/41",[39,40.372]],["parent/41",[28,1.206]],["name/42",[40,40.372]],["parent/42",[28,1.206]],["name/43",[41,40.372]],["parent/43",[28,1.206]],["name/44",[42,40.372]],["parent/44",[28,1.206]],["name/45",[43,40.372]],["parent/45",[28,1.206]],["name/46",[44,40.372]],["parent/46",[28,1.206]],["name/47",[45,29.386]],["parent/47",[]],["name/48",[46,31.899]],["parent/48",[45,2.324]],["name/49",[47,31.899]],["parent/49",[45,2.324]],["name/50",[48,35.264]],["parent/50",[45,2.324]],["name/51",[49,23.026]],["parent/51",[]],["name/52",[1,29.386]],["parent/52",[49,1.821]],["name/53",[46,31.899]],["parent/53",[49,1.821]],["name/54",[47,31.899]],["parent/54",[49,1.821]],["name/55",[48,35.264]],["parent/55",[49,1.821]],["name/56",[50,29.386]],["parent/56",[49,1.821]],["name/57",[51,35.264]],["parent/57",[52,3.192]],["name/58",[53,40.372]],["parent/58",[49,1.821]],["name/59",[54,40.372]],["parent/59",[49,1.821]],["name/60",[55,20.913]],["parent/60",[]],["name/61",[56,35.264]],["parent/61",[55,1.654]],["name/62",[57,40.372]],["parent/62",[55,1.654]],["name/63",[58,40.372]],["parent/63",[55,1.654]],["name/64",[50,29.386]],["parent/64",[55,1.654]],["name/65",[59,40.372]],["parent/65",[55,1.654]],["name/66",[50,29.386]],["parent/66",[55,1.654]],["name/67",[46,31.899]],["parent/67",[60,2.033]],["name/68",[47,31.899]],["parent/68",[60,2.033]],["name/69",[61,40.372]],["parent/69",[60,2.033]],["name/70",[51,35.264]],["parent/70",[60,2.033]],["name/71",[62,40.372]],["parent/71",[55,1.654]],["name/72",[50,29.386]],["parent/72",[55,1.654]],["name/73",[63,35.264]],["parent/73",[60,2.033]],["name/74",[64,40.372]],["parent/74",[60,2.033]],["name/75",[65,40.372]],["parent/75",[55,1.654]],["name/76",[56,35.264]],["parent/76",[]],["name/77",[66,40.372]],["parent/77",[]],["name/78",[67,40.372]],["parent/78",[]],["name/79",[68,40.372]],["parent/79",[]],["name/80",[69,29.386]],["parent/80",[]],["name/81",[63,35.264]],["parent/81",[69,2.324]],["name/82",[70,40.372]],["parent/82",[69,2.324]],["name/83",[71,40.372]],["parent/83",[69,2.324]]],"invertedIndex":[["__type",{"_index":50,"name":{"56":{},"64":{},"66":{},"72":{}},"parent":{}}],["addannotation",{"_index":2,"name":{"2":{}},"parent":{}}],["addlabel",{"_index":5,"name":{"5":{}},"parent":{}}],["addresults",{"_index":40,"name":{"42":{}},"parent":{}}],["annotation_prefix",{"_index":8,"name":{"8":{}},"parent":{}}],["apiversion",{"_index":46,"name":{"48":{},"53":{},"67":{}},"parent":{}}],["assert",{"_index":22,"name":{"23":{}},"parent":{}}],["assertcallback",{"_index":23,"name":{"24":{}},"parent":{}}],["configfileresult",{"_index":17,"name":{"17":{}},"parent":{}}],["configs",{"_index":28,"name":{"29":{}},"parent":{"30":{},"31":{},"32":{},"33":{},"34":{},"35":{},"36":{},"37":{},"38":{},"39":{},"40":{},"41":{},"42":{},"43":{},"44":{},"45":{},"46":{}}}],["constructor",{"_index":1,"name":{"1":{},"22":{},"30":{},"52":{}},"parent":{}}],["currentvalue",{"_index":70,"name":{"82":{}},"parent":{}}],["deepcopy",{"_index":43,"name":{"45":{}},"parent":{}}],["delete",{"_index":32,"name":{"34":{}},"parent":{}}],["deleteall",{"_index":33,"name":{"35":{}},"parent":{}}],["field",{"_index":65,"name":{"75":{}},"parent":{}}],["fieldinfo",{"_index":69,"name":{"80":{}},"parent":{"81":{},"82":{},"83":{}}}],["file",{"_index":62,"name":{"71":{}},"parent":{}}],["functionconfigerror",{"_index":0,"name":{"0":{}},"parent":{"1":{}}}],["generalresult",{"_index":16,"name":{"16":{}},"parent":{}}],["get",{"_index":30,"name":{"32":{}},"parent":{}}],["getall",{"_index":29,"name":{"31":{}},"parent":{}}],["getannotation",{"_index":4,"name":{"4":{}},"parent":{}}],["getfunctionconfig",{"_index":35,"name":{"37":{}},"parent":{}}],["getfunctionconfigmap",{"_index":36,"name":{"38":{}},"parent":{}}],["getfunctionconfigvalue",{"_index":37,"name":{"39":{}},"parent":{}}],["getfunctionconfigvalueorthrow",{"_index":39,"name":{"41":{}},"parent":{}}],["getlabel",{"_index":7,"name":{"7":{}},"parent":{}}],["getresults",{"_index":41,"name":{"43":{}},"parent":{}}],["groupby",{"_index":34,"name":{"36":{}},"parent":{}}],["hasunexpectedfunctionparameter",{"_index":38,"name":{"40":{}},"parent":{}}],["id_annotation",{"_index":11,"name":{"11":{}},"parent":{}}],["index",{"_index":64,"name":{"74":{}},"parent":{}}],["insert",{"_index":31,"name":{"33":{}},"parent":{}}],["iskubernetesobject",{"_index":24,"name":{"25":{}},"parent":{}}],["items",{"_index":53,"name":{"58":{}},"parent":{}}],["json",{"_index":68,"name":{"79":{}},"parent":{}}],["jsonarray",{"_index":66,"name":{"77":{}},"parent":{}}],["jsonmap",{"_index":67,"name":{"78":{}},"parent":{}}],["kind",{"_index":47,"name":{"49":{},"54":{},"68":{}},"parent":{}}],["kptfunc",{"_index":26,"name":{"27":{}},"parent":{"28":{}}}],["kuberneteskey",{"_index":25,"name":{"26":{}},"parent":{}}],["kubernetesobject",{"_index":45,"name":{"47":{}},"parent":{"48":{},"49":{},"50":{}}}],["kubernetesobjectresult",{"_index":18,"name":{"18":{}},"parent":{}}],["legacy_annotation_prefix",{"_index":12,"name":{"12":{}},"parent":{}}],["legacy_id_annotation",{"_index":15,"name":{"15":{}},"parent":{}}],["legacy_source_index_annotation",{"_index":14,"name":{"14":{}},"parent":{}}],["legacy_source_path_annotation",{"_index":13,"name":{"13":{}},"parent":{}}],["logtostderr",{"_index":44,"name":{"46":{}},"parent":{}}],["message",{"_index":57,"name":{"62":{}},"parent":{}}],["metadata",{"_index":48,"name":{"50":{},"55":{}},"parent":{}}],["name",{"_index":51,"name":{"57":{},"70":{}},"parent":{}}],["namespace",{"_index":61,"name":{"69":{}},"parent":{}}],["path",{"_index":63,"name":{"73":{},"81":{}},"parent":{}}],["removeannotation",{"_index":3,"name":{"3":{}},"parent":{}}],["removelabel",{"_index":6,"name":{"6":{}},"parent":{}}],["resourcelist",{"_index":49,"name":{"51":{}},"parent":{"52":{},"53":{},"54":{},"55":{},"56":{},"58":{},"59":{}}}],["resourcelist.__type",{"_index":52,"name":{},"parent":{"57":{}}}],["resourceref",{"_index":59,"name":{"65":{}},"parent":{}}],["result",{"_index":55,"name":{"60":{}},"parent":{"61":{},"62":{},"63":{},"64":{},"65":{},"66":{},"71":{},"72":{},"75":{}}}],["result.__type",{"_index":60,"name":{},"parent":{"67":{},"68":{},"69":{},"70":{},"73":{},"74":{}}}],["results",{"_index":54,"name":{"59":{}},"parent":{}}],["run",{"_index":19,"name":{"19":{}},"parent":{}}],["runfnwithconfigs",{"_index":20,"name":{"20":{}},"parent":{}}],["severity",{"_index":56,"name":{"61":{},"76":{}},"parent":{}}],["source_index_annotation",{"_index":10,"name":{"10":{}},"parent":{}}],["source_path_annotation",{"_index":9,"name":{"9":{}},"parent":{}}],["suggestedvalue",{"_index":71,"name":{"83":{}},"parent":{}}],["tags",{"_index":58,"name":{"63":{}},"parent":{}}],["testrunner",{"_index":21,"name":{"21":{}},"parent":{"22":{},"23":{},"24":{}}}],["toresourcelist",{"_index":42,"name":{"44":{}},"parent":{}}],["usage",{"_index":27,"name":{"28":{}},"parent":{}}]],"pipeline":[]}} \ No newline at end of file diff --git a/docs/api/assets/style.css b/docs/api/assets/style.css deleted file mode 100644 index ff488199d..000000000 --- a/docs/api/assets/style.css +++ /dev/null @@ -1,1384 +0,0 @@ -@import url("./icons.css"); - -:root { - /* Light */ - --light-color-background: #fcfcfc; - --light-color-secondary-background: #fff; - --light-color-text: #222; - --light-color-text-aside: #707070; - --light-color-link: #4da6ff; - --light-color-menu-divider: #eee; - --light-color-menu-divider-focus: #000; - --light-color-menu-label: #707070; - --light-color-panel: var(--light-color-secondary-background); - --light-color-panel-divider: #eee; - --light-color-comment-tag: #707070; - --light-color-comment-tag-text: #fff; - --light-color-ts: #9600ff; - --light-color-ts-interface: #647f1b; - --light-color-ts-enum: #937210; - --light-color-ts-class: #0672de; - --light-color-ts-private: #707070; - --light-color-toolbar: #fff; - --light-color-toolbar-text: #333; - --light-icon-filter: invert(0); - --light-external-icon: url("data:image/svg+xml;utf8,"); - - /* Dark */ - --dark-color-background: #36393f; - --dark-color-secondary-background: #2f3136; - --dark-color-text: #ffffff; - --dark-color-text-aside: #e6e4e4; - --dark-color-link: #00aff4; - --dark-color-menu-divider: #eee; - --dark-color-menu-divider-focus: #000; - --dark-color-menu-label: #707070; - --dark-color-panel: var(--dark-color-secondary-background); - --dark-color-panel-divider: #818181; - --dark-color-comment-tag: #dcddde; - --dark-color-comment-tag-text: #2f3136; - --dark-color-ts: #c97dff; - --dark-color-ts-interface: #9cbe3c; - --dark-color-ts-enum: #d6ab29; - --dark-color-ts-class: #3695f3; - --dark-color-ts-private: #e2e2e2; - --dark-color-toolbar: #34373c; - --dark-color-toolbar-text: #ffffff; - --dark-icon-filter: invert(1); - --dark-external-icon: url("data:image/svg+xml;utf8,"); -} - -@media (prefers-color-scheme: light) { - :root { - --color-background: var(--light-color-background); - --color-secondary-background: var(--light-color-secondary-background); - --color-text: var(--light-color-text); - --color-text-aside: var(--light-color-text-aside); - --color-link: var(--light-color-link); - --color-menu-divider: var(--light-color-menu-divider); - --color-menu-divider-focus: var(--light-color-menu-divider-focus); - --color-menu-label: var(--light-color-menu-label); - --color-panel: var(--light-color-panel); - --color-panel-divider: var(--light-color-panel-divider); - --color-comment-tag: var(--light-color-comment-tag); - --color-comment-tag-text: var(--light-color-comment-tag-text); - --color-ts: var(--light-color-ts); - --color-ts-interface: var(--light-color-ts-interface); - --color-ts-enum: var(--light-color-ts-enum); - --color-ts-class: var(--light-color-ts-class); - --color-ts-private: var(--light-color-ts-private); - --color-toolbar: var(--light-color-toolbar); - --color-toolbar-text: var(--light-color-toolbar-text); - --icon-filter: var(--light-icon-filter); - --external-icon: var(--light-external-icon); - } -} - -@media (prefers-color-scheme: dark) { - :root { - --color-background: var(--dark-color-background); - --color-secondary-background: var(--dark-color-secondary-background); - --color-text: var(--dark-color-text); - --color-text-aside: var(--dark-color-text-aside); - --color-link: var(--dark-color-link); - --color-menu-divider: var(--dark-color-menu-divider); - --color-menu-divider-focus: var(--dark-color-menu-divider-focus); - --color-menu-label: var(--dark-color-menu-label); - --color-panel: var(--dark-color-panel); - --color-panel-divider: var(--dark-color-panel-divider); - --color-comment-tag: var(--dark-color-comment-tag); - --color-comment-tag-text: var(--dark-color-comment-tag-text); - --color-ts: var(--dark-color-ts); - --color-ts-interface: var(--dark-color-ts-interface); - --color-ts-enum: var(--dark-color-ts-enum); - --color-ts-class: var(--dark-color-ts-class); - --color-ts-private: var(--dark-color-ts-private); - --color-toolbar: var(--dark-color-toolbar); - --color-toolbar-text: var(--dark-color-toolbar-text); - --icon-filter: var(--dark-icon-filter); - --external-icon: var(--dark-external-icon); - } -} - -body { - margin: 0; -} - -body.light { - --color-background: var(--light-color-background); - --color-secondary-background: var(--light-color-secondary-background); - --color-text: var(--light-color-text); - --color-text-aside: var(--light-color-text-aside); - --color-link: var(--light-color-link); - --color-menu-divider: var(--light-color-menu-divider); - --color-menu-divider-focus: var(--light-color-menu-divider-focus); - --color-menu-label: var(--light-color-menu-label); - --color-panel: var(--light-color-panel); - --color-panel-divider: var(--light-color-panel-divider); - --color-comment-tag: var(--light-color-comment-tag); - --color-comment-tag-text: var(--light-color-comment-tag-text); - --color-ts: var(--light-color-ts); - --color-ts-interface: var(--light-color-ts-interface); - --color-ts-enum: var(--light-color-ts-enum); - --color-ts-class: var(--light-color-ts-class); - --color-ts-private: var(--light-color-ts-private); - --color-toolbar: var(--light-color-toolbar); - --color-toolbar-text: var(--light-color-toolbar-text); - --icon-filter: var(--light-icon-filter); - --external-icon: var(--light-external-icon); -} - -body.dark { - --color-background: var(--dark-color-background); - --color-secondary-background: var(--dark-color-secondary-background); - --color-text: var(--dark-color-text); - --color-text-aside: var(--dark-color-text-aside); - --color-link: var(--dark-color-link); - --color-menu-divider: var(--dark-color-menu-divider); - --color-menu-divider-focus: var(--dark-color-menu-divider-focus); - --color-menu-label: var(--dark-color-menu-label); - --color-panel: var(--dark-color-panel); - --color-panel-divider: var(--dark-color-panel-divider); - --color-comment-tag: var(--dark-color-comment-tag); - --color-comment-tag-text: var(--dark-color-comment-tag-text); - --color-ts: var(--dark-color-ts); - --color-ts-interface: var(--dark-color-ts-interface); - --color-ts-enum: var(--dark-color-ts-enum); - --color-ts-class: var(--dark-color-ts-class); - --color-ts-private: var(--dark-color-ts-private); - --color-toolbar: var(--dark-color-toolbar); - --color-toolbar-text: var(--dark-color-toolbar-text); - --icon-filter: var(--dark-icon-filter); - --external-icon: var(--dark-external-icon); -} - -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -h2 { - font-size: 1.5em; - margin: 0.83em 0; -} - -h3 { - font-size: 1.17em; - margin: 1em 0; -} - -h4, -.tsd-index-panel h3 { - font-size: 1em; - margin: 1.33em 0; -} - -h5 { - font-size: 0.83em; - margin: 1.67em 0; -} - -h6 { - font-size: 0.67em; - margin: 2.33em 0; -} - -pre { - white-space: pre; - white-space: pre-wrap; - word-wrap: break-word; -} - -dl, -menu, -ol, -ul { - margin: 1em 0; -} - -dd { - margin: 0 0 0 40px; -} - -.container { - max-width: 1200px; - margin: 0 auto; - padding: 0 40px; -} -@media (max-width: 640px) { - .container { - padding: 0 20px; - } -} - -.container-main { - padding-bottom: 200px; -} - -.row { - display: flex; - position: relative; - margin: 0 -10px; -} -.row:after { - visibility: hidden; - display: block; - content: ""; - clear: both; - height: 0; -} - -.col-4, -.col-8 { - box-sizing: border-box; - float: left; - padding: 0 10px; -} - -.col-4 { - width: 33.3333333333%; -} -.col-8 { - width: 66.6666666667%; -} - -ul.tsd-descriptions > li > :first-child, -.tsd-panel > :first-child, -.col-8 > :first-child, -.col-4 > :first-child, -ul.tsd-descriptions > li > :first-child > :first-child, -.tsd-panel > :first-child > :first-child, -.col-8 > :first-child > :first-child, -.col-4 > :first-child > :first-child, -ul.tsd-descriptions > li > :first-child > :first-child > :first-child, -.tsd-panel > :first-child > :first-child > :first-child, -.col-8 > :first-child > :first-child > :first-child, -.col-4 > :first-child > :first-child > :first-child { - margin-top: 0; -} -ul.tsd-descriptions > li > :last-child, -.tsd-panel > :last-child, -.col-8 > :last-child, -.col-4 > :last-child, -ul.tsd-descriptions > li > :last-child > :last-child, -.tsd-panel > :last-child > :last-child, -.col-8 > :last-child > :last-child, -.col-4 > :last-child > :last-child, -ul.tsd-descriptions > li > :last-child > :last-child > :last-child, -.tsd-panel > :last-child > :last-child > :last-child, -.col-8 > :last-child > :last-child > :last-child, -.col-4 > :last-child > :last-child > :last-child { - margin-bottom: 0; -} - -@keyframes fade-in { - from { - opacity: 0; - } - to { - opacity: 1; - } -} -@keyframes fade-out { - from { - opacity: 1; - visibility: visible; - } - to { - opacity: 0; - } -} -@keyframes fade-in-delayed { - 0% { - opacity: 0; - } - 33% { - opacity: 0; - } - 100% { - opacity: 1; - } -} -@keyframes fade-out-delayed { - 0% { - opacity: 1; - visibility: visible; - } - 66% { - opacity: 0; - } - 100% { - opacity: 0; - } -} -@keyframes shift-to-left { - from { - transform: translate(0, 0); - } - to { - transform: translate(-25%, 0); - } -} -@keyframes unshift-to-left { - from { - transform: translate(-25%, 0); - } - to { - transform: translate(0, 0); - } -} -@keyframes pop-in-from-right { - from { - transform: translate(100%, 0); - } - to { - transform: translate(0, 0); - } -} -@keyframes pop-out-to-right { - from { - transform: translate(0, 0); - visibility: visible; - } - to { - transform: translate(100%, 0); - } -} -body { - background: var(--color-background); - font-family: "Segoe UI", sans-serif; - font-size: 16px; - color: var(--color-text); -} - -a { - color: var(--color-link); - text-decoration: none; -} -a:hover { - text-decoration: underline; -} -a.external[target="_blank"] { - background-image: var(--external-icon); - background-position: top 3px right; - background-repeat: no-repeat; - padding-right: 13px; -} - -code, -pre { - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; - padding: 0.2em; - margin: 0; - font-size: 14px; -} - -pre { - padding: 10px; -} -pre code { - padding: 0; - font-size: 100%; -} - -blockquote { - margin: 1em 0; - padding-left: 1em; - border-left: 4px solid gray; -} - -.tsd-typography { - line-height: 1.333em; -} -.tsd-typography ul { - list-style: square; - padding: 0 0 0 20px; - margin: 0; -} -.tsd-typography h4, -.tsd-typography .tsd-index-panel h3, -.tsd-index-panel .tsd-typography h3, -.tsd-typography h5, -.tsd-typography h6 { - font-size: 1em; - margin: 0; -} -.tsd-typography h5, -.tsd-typography h6 { - font-weight: normal; -} -.tsd-typography p, -.tsd-typography ul, -.tsd-typography ol { - margin: 1em 0; -} - -@media (min-width: 901px) and (max-width: 1024px) { - html .col-content { - width: 72%; - } - html .col-menu { - width: 28%; - } - html .tsd-navigation { - padding-left: 10px; - } -} -@media (max-width: 900px) { - html .col-content { - float: none; - width: 100%; - } - html .col-menu { - position: fixed !important; - overflow: auto; - -webkit-overflow-scrolling: touch; - z-index: 1024; - top: 0 !important; - bottom: 0 !important; - left: auto !important; - right: 0 !important; - width: 100%; - padding: 20px 20px 0 0; - max-width: 450px; - visibility: hidden; - background-color: var(--color-panel); - transform: translate(100%, 0); - } - html .col-menu > *:last-child { - padding-bottom: 20px; - } - html .overlay { - content: ""; - display: block; - position: fixed; - z-index: 1023; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: rgba(0, 0, 0, 0.75); - visibility: hidden; - } - - .to-has-menu .overlay { - animation: fade-in 0.4s; - } - - .to-has-menu :is(header, footer, .col-content) { - animation: shift-to-left 0.4s; - } - - .to-has-menu .col-menu { - animation: pop-in-from-right 0.4s; - } - - .from-has-menu .overlay { - animation: fade-out 0.4s; - } - - .from-has-menu :is(header, footer, .col-content) { - animation: unshift-to-left 0.4s; - } - - .from-has-menu .col-menu { - animation: pop-out-to-right 0.4s; - } - - .has-menu body { - overflow: hidden; - } - .has-menu .overlay { - visibility: visible; - } - .has-menu :is(header, footer, .col-content) { - transform: translate(-25%, 0); - } - .has-menu .col-menu { - visibility: visible; - transform: translate(0, 0); - display: grid; - grid-template-rows: auto 1fr; - max-height: 100vh; - } - .has-menu .tsd-navigation { - max-height: 100%; - } -} - -.tsd-page-title { - padding: 70px 0 20px 0; - margin: 0 0 40px 0; - background: var(--color-panel); - box-shadow: 0 0 5px rgba(0, 0, 0, 0.35); -} -.tsd-page-title h1 { - margin: 0; -} - -.tsd-breadcrumb { - margin: 0; - padding: 0; - color: var(--color-text-aside); -} -.tsd-breadcrumb a { - color: var(--color-text-aside); - text-decoration: none; -} -.tsd-breadcrumb a:hover { - text-decoration: underline; -} -.tsd-breadcrumb li { - display: inline; -} -.tsd-breadcrumb li:after { - content: " / "; -} - -dl.tsd-comment-tags { - overflow: hidden; -} -dl.tsd-comment-tags dt { - float: left; - padding: 1px 5px; - margin: 0 10px 0 0; - border-radius: 4px; - border: 1px solid var(--color-comment-tag); - color: var(--color-comment-tag); - font-size: 0.8em; - font-weight: normal; -} -dl.tsd-comment-tags dd { - margin: 0 0 10px 0; -} -dl.tsd-comment-tags dd:before, -dl.tsd-comment-tags dd:after { - display: table; - content: " "; -} -dl.tsd-comment-tags dd pre, -dl.tsd-comment-tags dd:after { - clear: both; -} -dl.tsd-comment-tags p { - margin: 0; -} - -.tsd-panel.tsd-comment .lead { - font-size: 1.1em; - line-height: 1.333em; - margin-bottom: 2em; -} -.tsd-panel.tsd-comment .lead:last-child { - margin-bottom: 0; -} - -.toggle-protected .tsd-is-private { - display: none; -} - -.toggle-public .tsd-is-private, -.toggle-public .tsd-is-protected, -.toggle-public .tsd-is-private-protected { - display: none; -} - -.toggle-inherited .tsd-is-inherited { - display: none; -} - -.toggle-externals .tsd-is-external { - display: none; -} - -#tsd-filter { - position: relative; - display: inline-block; - height: 40px; - vertical-align: bottom; -} -.no-filter #tsd-filter { - display: none; -} -#tsd-filter .tsd-filter-group { - display: inline-block; - height: 40px; - vertical-align: bottom; - white-space: nowrap; -} -#tsd-filter input { - display: none; -} -@media (max-width: 900px) { - #tsd-filter .tsd-filter-group { - display: block; - position: absolute; - top: 40px; - right: 20px; - height: auto; - background-color: var(--color-panel); - visibility: hidden; - transform: translate(50%, 0); - box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); - } - .has-options #tsd-filter .tsd-filter-group { - visibility: visible; - } - .to-has-options #tsd-filter .tsd-filter-group { - animation: fade-in 0.2s; - } - .from-has-options #tsd-filter .tsd-filter-group { - animation: fade-out 0.2s; - } - #tsd-filter label, - #tsd-filter .tsd-select { - display: block; - padding-right: 20px; - } -} - -footer { - border-top: 1px solid var(--color-panel-divider); - background-color: var(--color-panel); -} -footer.with-border-bottom { - border-bottom: 1px solid var(--color-panel-divider); -} -footer .tsd-legend-group { - font-size: 0; -} -footer .tsd-legend { - display: inline-block; - width: 25%; - padding: 0; - font-size: 16px; - list-style: none; - line-height: 1.333em; - vertical-align: top; -} -@media (max-width: 900px) { - footer .tsd-legend { - width: 50%; - } -} - -.tsd-hierarchy { - list-style: square; - padding: 0 0 0 20px; - margin: 0; -} -.tsd-hierarchy .target { - font-weight: bold; -} - -.tsd-index-panel .tsd-index-content { - margin-bottom: -30px !important; -} -.tsd-index-panel .tsd-index-section { - margin-bottom: 30px !important; -} -.tsd-index-panel h3 { - margin: 0 -20px 10px -20px; - padding: 0 20px 10px 20px; - border-bottom: 1px solid var(--color-panel-divider); -} -.tsd-index-panel ul.tsd-index-list { - -webkit-column-count: 3; - -moz-column-count: 3; - -ms-column-count: 3; - -o-column-count: 3; - column-count: 3; - -webkit-column-gap: 20px; - -moz-column-gap: 20px; - -ms-column-gap: 20px; - -o-column-gap: 20px; - column-gap: 20px; - padding: 0; - list-style: none; - line-height: 1.333em; -} -@media (max-width: 900px) { - .tsd-index-panel ul.tsd-index-list { - -webkit-column-count: 1; - -moz-column-count: 1; - -ms-column-count: 1; - -o-column-count: 1; - column-count: 1; - } -} -@media (min-width: 901px) and (max-width: 1024px) { - .tsd-index-panel ul.tsd-index-list { - -webkit-column-count: 2; - -moz-column-count: 2; - -ms-column-count: 2; - -o-column-count: 2; - column-count: 2; - } -} -.tsd-index-panel ul.tsd-index-list li { - -webkit-page-break-inside: avoid; - -moz-page-break-inside: avoid; - -ms-page-break-inside: avoid; - -o-page-break-inside: avoid; - page-break-inside: avoid; -} -.tsd-index-panel a, -.tsd-index-panel .tsd-parent-kind-module a { - color: var(--color-ts); -} -.tsd-index-panel .tsd-parent-kind-interface a { - color: var(--color-ts-interface); -} -.tsd-index-panel .tsd-parent-kind-enum a { - color: var(--color-ts-enum); -} -.tsd-index-panel .tsd-parent-kind-class a { - color: var(--color-ts-class); -} -.tsd-index-panel .tsd-kind-module a { - color: var(--color-ts); -} -.tsd-index-panel .tsd-kind-interface a { - color: var(--color-ts-interface); -} -.tsd-index-panel .tsd-kind-enum a { - color: var(--color-ts-enum); -} -.tsd-index-panel .tsd-kind-class a { - color: var(--color-ts-class); -} -.tsd-index-panel .tsd-is-private a { - color: var(--color-ts-private); -} - -.tsd-flag { - display: inline-block; - padding: 1px 5px; - border-radius: 4px; - color: var(--color-comment-tag-text); - background-color: var(--color-comment-tag); - text-indent: 0; - font-size: 14px; - font-weight: normal; -} - -.tsd-anchor { - position: absolute; - top: -100px; -} - -.tsd-member { - position: relative; -} -.tsd-member .tsd-anchor + h3 { - margin-top: 0; - margin-bottom: 0; - border-bottom: none; -} -.tsd-member [data-tsd-kind] { - color: var(--color-ts); -} -.tsd-member [data-tsd-kind="Interface"] { - color: var(--color-ts-interface); -} -.tsd-member [data-tsd-kind="Enum"] { - color: var(--color-ts-enum); -} -.tsd-member [data-tsd-kind="Class"] { - color: var(--color-ts-class); -} -.tsd-member [data-tsd-kind="Private"] { - color: var(--color-ts-private); -} - -.tsd-navigation { - margin: 0 0 0 40px; -} -.tsd-navigation a { - display: block; - padding-top: 2px; - padding-bottom: 2px; - border-left: 2px solid transparent; - color: var(--color-text); - text-decoration: none; - transition: border-left-color 0.1s; -} -.tsd-navigation a:hover { - text-decoration: underline; -} -.tsd-navigation ul { - margin: 0; - padding: 0; - list-style: none; -} -.tsd-navigation li { - padding: 0; -} - -.tsd-navigation.primary { - padding-bottom: 40px; -} -.tsd-navigation.primary a { - display: block; - padding-top: 6px; - padding-bottom: 6px; -} -.tsd-navigation.primary ul li a { - padding-left: 5px; -} -.tsd-navigation.primary ul li li a { - padding-left: 25px; -} -.tsd-navigation.primary ul li li li a { - padding-left: 45px; -} -.tsd-navigation.primary ul li li li li a { - padding-left: 65px; -} -.tsd-navigation.primary ul li li li li li a { - padding-left: 85px; -} -.tsd-navigation.primary ul li li li li li li a { - padding-left: 105px; -} -.tsd-navigation.primary > ul { - border-bottom: 1px solid var(--color-panel-divider); -} -.tsd-navigation.primary li { - border-top: 1px solid var(--color-panel-divider); -} -.tsd-navigation.primary li.current > a { - font-weight: bold; -} -.tsd-navigation.primary li.label span { - display: block; - padding: 20px 0 6px 5px; - color: var(--color-menu-label); -} -.tsd-navigation.primary li.globals + li > span, -.tsd-navigation.primary li.globals + li > a { - padding-top: 20px; -} - -.tsd-navigation.secondary { - max-height: calc(100vh - 1rem - 40px); - overflow: auto; - position: sticky; - top: calc(0.5rem + 40px); - transition: 0.3s; -} -.tsd-navigation.secondary.tsd-navigation--toolbar-hide { - max-height: calc(100vh - 1rem); - top: 0.5rem; -} -.tsd-navigation.secondary ul { - transition: opacity 0.2s; -} -.tsd-navigation.secondary ul li a { - padding-left: 25px; -} -.tsd-navigation.secondary ul li li a { - padding-left: 45px; -} -.tsd-navigation.secondary ul li li li a { - padding-left: 65px; -} -.tsd-navigation.secondary ul li li li li a { - padding-left: 85px; -} -.tsd-navigation.secondary ul li li li li li a { - padding-left: 105px; -} -.tsd-navigation.secondary ul li li li li li li a { - padding-left: 125px; -} -.tsd-navigation.secondary ul.current a { - border-left-color: var(--color-panel-divider); -} -.tsd-navigation.secondary li.focus > a, -.tsd-navigation.secondary ul.current li.focus > a { - border-left-color: var(--color-menu-divider-focus); -} -.tsd-navigation.secondary li.current { - margin-top: 20px; - margin-bottom: 20px; - border-left-color: var(--color-panel-divider); -} -.tsd-navigation.secondary li.current > a { - font-weight: bold; -} - -@media (min-width: 901px) { - .menu-sticky-wrap { - position: static; - } -} - -.tsd-panel { - margin: 20px 0; - padding: 20px; - background-color: var(--color-panel); - box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); -} -.tsd-panel:empty { - display: none; -} -.tsd-panel > h1, -.tsd-panel > h2, -.tsd-panel > h3 { - margin: 1.5em -20px 10px -20px; - padding: 0 20px 10px 20px; - border-bottom: 1px solid var(--color-panel-divider); -} -.tsd-panel > h1.tsd-before-signature, -.tsd-panel > h2.tsd-before-signature, -.tsd-panel > h3.tsd-before-signature { - margin-bottom: 0; - border-bottom: 0; -} -.tsd-panel table { - display: block; - width: 100%; - overflow: auto; - margin-top: 10px; - word-break: normal; - word-break: keep-all; - border-collapse: collapse; -} -.tsd-panel table th { - font-weight: bold; -} -.tsd-panel table th, -.tsd-panel table td { - padding: 6px 13px; - border: 1px solid var(--color-panel-divider); -} -.tsd-panel table tr { - background: var(--color-background); -} -.tsd-panel table tr:nth-child(even) { - background: var(--color-secondary-background); -} - -.tsd-panel-group { - margin: 60px 0; -} -.tsd-panel-group > h1, -.tsd-panel-group > h2, -.tsd-panel-group > h3 { - padding-left: 20px; - padding-right: 20px; -} - -#tsd-search { - transition: background-color 0.2s; -} -#tsd-search .title { - position: relative; - z-index: 2; -} -#tsd-search .field { - position: absolute; - left: 0; - top: 0; - right: 40px; - height: 40px; -} -#tsd-search .field input { - box-sizing: border-box; - position: relative; - top: -50px; - z-index: 1; - width: 100%; - padding: 0 10px; - opacity: 0; - outline: 0; - border: 0; - background: transparent; - color: var(--color-text); -} -#tsd-search .field label { - position: absolute; - overflow: hidden; - right: -40px; -} -#tsd-search .field input, -#tsd-search .title { - transition: opacity 0.2s; -} -#tsd-search .results { - position: absolute; - visibility: hidden; - top: 40px; - width: 100%; - margin: 0; - padding: 0; - list-style: none; - box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); -} -#tsd-search .results li { - padding: 0 10px; - background-color: var(--color-background); -} -#tsd-search .results li:nth-child(even) { - background-color: var(--color-panel); -} -#tsd-search .results li.state { - display: none; -} -#tsd-search .results li.current, -#tsd-search .results li:hover { - background-color: var(--color-panel-divider); -} -#tsd-search .results a { - display: block; -} -#tsd-search .results a:before { - top: 10px; -} -#tsd-search .results span.parent { - color: var(--color-text-aside); - font-weight: normal; -} -#tsd-search.has-focus { - background-color: var(--color-panel-divider); -} -#tsd-search.has-focus .field input { - top: 0; - opacity: 1; -} -#tsd-search.has-focus .title { - z-index: 0; - opacity: 0; -} -#tsd-search.has-focus .results { - visibility: visible; -} -#tsd-search.loading .results li.state.loading { - display: block; -} -#tsd-search.failure .results li.state.failure { - display: block; -} - -.tsd-signature { - margin: 0 0 1em 0; - padding: 10px; - border: 1px solid var(--color-panel-divider); - font-family: Menlo, Monaco, Consolas, "Courier New", monospace; - font-size: 14px; - overflow-x: auto; -} -.tsd-signature.tsd-kind-icon { - padding-left: 30px; -} -.tsd-signature.tsd-kind-icon:before { - top: 10px; - left: 10px; -} -.tsd-panel > .tsd-signature { - margin-left: -20px; - margin-right: -20px; - border-width: 1px 0; -} -.tsd-panel > .tsd-signature.tsd-kind-icon { - padding-left: 40px; -} -.tsd-panel > .tsd-signature.tsd-kind-icon:before { - left: 20px; -} - -.tsd-signature-symbol { - color: var(--color-text-aside); - font-weight: normal; -} - -.tsd-signature-type { - font-style: italic; - font-weight: normal; -} - -.tsd-signatures { - padding: 0; - margin: 0 0 1em 0; - border: 1px solid var(--color-panel-divider); -} -.tsd-signatures .tsd-signature { - margin: 0; - border-width: 1px 0 0 0; - transition: background-color 0.1s; -} -.tsd-signatures .tsd-signature:first-child { - border-top-width: 0; -} -.tsd-signatures .tsd-signature.current { - background-color: var(--color-panel-divider); -} -.tsd-signatures.active > .tsd-signature { - cursor: pointer; -} -.tsd-panel > .tsd-signatures { - margin-left: -20px; - margin-right: -20px; - border-width: 1px 0; -} -.tsd-panel > .tsd-signatures .tsd-signature.tsd-kind-icon { - padding-left: 40px; -} -.tsd-panel > .tsd-signatures .tsd-signature.tsd-kind-icon:before { - left: 20px; -} -.tsd-panel > a.anchor + .tsd-signatures { - border-top-width: 0; - margin-top: -20px; -} - -ul.tsd-descriptions { - position: relative; - overflow: hidden; - padding: 0; - list-style: none; -} -ul.tsd-descriptions.active > .tsd-description { - display: none; -} -ul.tsd-descriptions.active > .tsd-description.current { - display: block; -} -ul.tsd-descriptions.active > .tsd-description.fade-in { - animation: fade-in-delayed 0.3s; -} -ul.tsd-descriptions.active > .tsd-description.fade-out { - animation: fade-out-delayed 0.3s; - position: absolute; - display: block; - top: 0; - left: 0; - right: 0; - opacity: 0; - visibility: hidden; -} -ul.tsd-descriptions h4, -ul.tsd-descriptions .tsd-index-panel h3, -.tsd-index-panel ul.tsd-descriptions h3 { - font-size: 16px; - margin: 1em 0 0.5em 0; -} - -ul.tsd-parameters, -ul.tsd-type-parameters { - list-style: square; - margin: 0; - padding-left: 20px; -} -ul.tsd-parameters > li.tsd-parameter-signature, -ul.tsd-type-parameters > li.tsd-parameter-signature { - list-style: none; - margin-left: -20px; -} -ul.tsd-parameters h5, -ul.tsd-type-parameters h5 { - font-size: 16px; - margin: 1em 0 0.5em 0; -} -ul.tsd-parameters .tsd-comment, -ul.tsd-type-parameters .tsd-comment { - margin-top: -0.5em; -} - -.tsd-sources { - font-size: 14px; - color: var(--color-text-aside); - margin: 0 0 1em 0; -} -.tsd-sources a { - color: var(--color-text-aside); - text-decoration: underline; -} -.tsd-sources ul, -.tsd-sources p { - margin: 0 !important; -} -.tsd-sources ul { - list-style: none; - padding: 0; -} - -.tsd-page-toolbar { - position: fixed; - z-index: 1; - top: 0; - left: 0; - width: 100%; - height: 40px; - color: var(--color-toolbar-text); - background: var(--color-toolbar); - border-bottom: 1px solid var(--color-panel-divider); - transition: transform 0.3s linear; -} -.tsd-page-toolbar a { - color: var(--color-toolbar-text); - text-decoration: none; -} -.tsd-page-toolbar a.title { - font-weight: bold; -} -.tsd-page-toolbar a.title:hover { - text-decoration: underline; -} -.tsd-page-toolbar .table-wrap { - display: table; - width: 100%; - height: 40px; -} -.tsd-page-toolbar .table-cell { - display: table-cell; - position: relative; - white-space: nowrap; - line-height: 40px; -} -.tsd-page-toolbar .table-cell:first-child { - width: 100%; -} - -.tsd-page-toolbar--hide { - transform: translateY(-100%); -} - -.tsd-select .tsd-select-list li:before, -.tsd-select .tsd-select-label:before, -.tsd-widget:before { - content: ""; - display: inline-block; - width: 40px; - height: 40px; - margin: 0 -8px 0 0; - background-image: url(./widgets.png); - background-repeat: no-repeat; - text-indent: -1024px; - vertical-align: bottom; - filter: var(--icon-filter); -} -@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { - .tsd-select .tsd-select-list li:before, - .tsd-select .tsd-select-label:before, - .tsd-widget:before { - background-image: url(./widgets@2x.png); - background-size: 320px 40px; - } -} - -.tsd-widget { - display: inline-block; - overflow: hidden; - opacity: 0.8; - height: 40px; - transition: opacity 0.1s, background-color 0.2s; - vertical-align: bottom; - cursor: pointer; -} -.tsd-widget:hover { - opacity: 0.9; -} -.tsd-widget.active { - opacity: 1; - background-color: var(--color-panel-divider); -} -.tsd-widget.no-caption { - width: 40px; -} -.tsd-widget.no-caption:before { - margin: 0; -} -.tsd-widget.search:before { - background-position: 0 0; -} -.tsd-widget.menu:before { - background-position: -40px 0; -} -.tsd-widget.options:before { - background-position: -80px 0; -} -.tsd-widget.options, -.tsd-widget.menu { - display: none; -} -@media (max-width: 900px) { - .tsd-widget.options, - .tsd-widget.menu { - display: inline-block; - } -} -input[type="checkbox"] + .tsd-widget:before { - background-position: -120px 0; -} -input[type="checkbox"]:checked + .tsd-widget:before { - background-position: -160px 0; -} - -.tsd-select { - position: relative; - display: inline-block; - height: 40px; - transition: opacity 0.1s, background-color 0.2s; - vertical-align: bottom; - cursor: pointer; -} -.tsd-select .tsd-select-label { - opacity: 0.6; - transition: opacity 0.2s; -} -.tsd-select .tsd-select-label:before { - background-position: -240px 0; -} -.tsd-select.active .tsd-select-label { - opacity: 0.8; -} -.tsd-select.active .tsd-select-list { - visibility: visible; - opacity: 1; - transition-delay: 0s; -} -.tsd-select .tsd-select-list { - position: absolute; - visibility: hidden; - top: 40px; - left: 0; - margin: 0; - padding: 0; - opacity: 0; - list-style: none; - box-shadow: 0 0 4px rgba(0, 0, 0, 0.25); - transition: visibility 0s 0.2s, opacity 0.2s; -} -.tsd-select .tsd-select-list li { - padding: 0 20px 0 0; - background-color: var(--color-background); -} -.tsd-select .tsd-select-list li:before { - background-position: 40px 0; -} -.tsd-select .tsd-select-list li:nth-child(even) { - background-color: var(--color-panel); -} -.tsd-select .tsd-select-list li:hover { - background-color: var(--color-panel-divider); -} -.tsd-select .tsd-select-list li.selected:before { - background-position: -200px 0; -} -@media (max-width: 900px) { - .tsd-select .tsd-select-list { - top: 0; - left: auto; - right: 100%; - margin-right: -5px; - } - .tsd-select .tsd-select-label:before { - background-position: -280px 0; - } -} - -img { - max-width: 100%; -} diff --git a/docs/api/assets/widgets.png b/docs/api/assets/widgets.png deleted file mode 100644 index c7380532a..000000000 Binary files a/docs/api/assets/widgets.png and /dev/null differ diff --git a/docs/api/assets/widgets@2x.png b/docs/api/assets/widgets@2x.png deleted file mode 100644 index 4bbbd5727..000000000 Binary files a/docs/api/assets/widgets@2x.png and /dev/null differ diff --git a/docs/api/classes/Configs.html b/docs/api/classes/Configs.html deleted file mode 100644 index 7aa0a1dce..000000000 --- a/docs/api/classes/Configs.html +++ /dev/null @@ -1,79 +0,0 @@ -Configs | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu
-

Configs is an in-memory document store for Kubernetes objects populated from/to configuration files.

-

It enables performing rich query and mutation operations.

-

Hierarchy

  • Configs

Index

Constructors

constructor

  • -

    Creates a Config.

    -

    Parameters

    • input: KubernetesObject[] = []
      -

      Input Kubernetes objects. -If supplied multiple objects with the same kubernetesKey discards all but the last one. -Does not preserve insertion order of the given objects.

      -
    • Optional functionConfig: KubernetesObject
      -

      Kubernetes object used to parameterize the function's behavior.

      -
    • Optional results: Result[]
      -

      For testing only: List of Results returned by the function.

      -

    Returns Configs

Properties

logToStdErr

logToStdErr: undefined | boolean
-

Determines whether addResults should also log to stderr.

-

Methods

addResults

  • addResults(...results: Result[]): void
  • -

    Adds given result(s) representing structured findings by the function.

    -

    Parameters

    Returns void

deepCopy

delete

  • -

    Deletes all objects with the same kubernetesKey as any of the given objects.

    -

    Does not throw if given duplicates or keys which are not present in the Configs.

    -

    Parameters

    Returns void

deleteAll

  • deleteAll(): void

get

  • -

    Returns an array of objects matching the given Kind type predicate.

    -

    Casts to an array of Kind. May throw if isKind is incorrect.

    -

    The ordering of objects is deterministic.

    -

    Returned objects are pass-by-reference; mutating them results in changes being persisted.

    -

    Type parameters

    Parameters

    Returns Kind[]

getAll

  • -

    Returns an array of all the objects in this Configs.

    -

    The ordering of objects is deterministic.

    -

    Returned objects are pass-by-reference; mutating them results in changes being persisted.

    -

    Returns KubernetesObject[]

getFunctionConfig

getFunctionConfigMap

  • getFunctionConfigMap(): undefined | Map<string, string>
  • -

    Returns the map of data values if functionConfig is of kind ConfigMap.

    -

    Throws a FunctionConfigError if functionConfig is undefined OR -if the kind is not a v1/ConfigMap.

    -

    Returns undefined | Map<string, string>

getFunctionConfigValue

  • getFunctionConfigValue(key: string): undefined | string
  • -

    Returns the value for the given key if functionConfig is of kind ConfigMap.

    -

    Throws a FunctionConfigError if functionConfig kind is not a ConfigMap.

    -

    Returns undefined if functionConfig is undefined OR -if the ConfigMap has no such key in the 'data' section.

    -
    key

    key The key in the 'data' field in the ConfigMap object given as the functionConfig.

    -

    Parameters

    • key: string

    Returns undefined | string

getFunctionConfigValueOrThrow

  • getFunctionConfigValueOrThrow(key: string): string

getResults

  • -

    Get result(s) representing structured findings by the function.

    -

    Returns Result[]

groupBy

  • -

    Partitions the objects using the provided key function

    -

    The ordering of objects with the same key is deterministic.

    -

    Example: Partition configs by Namespace:

    -
    const configsByNamespace = configs.groupBy((o) => o.metadata.namespace)
    -
    -

    Parameters

    Returns [string, KubernetesObject[]][]

hasUnexpectedFunctionParameter

  • hasUnexpectedFunctionParameter(expectedDataKeys: string[]): undefined | string[]
  • -

    Detects if an unknown value has been provided to the config map

    -

    Parameters

    • expectedDataKeys: string[]
      -

      The set of keys expected in the Configs data

      -

    Returns undefined | string[]

    Returns undefined if the config map is undefined. Otherwise -returns a string[] containing the invalid keys. The string[] will be empty -if all of the config maps keys are members of the expecteKeys.

    -

insert

  • -

    Inserts objects into the Configs.

    -

    If another object already in Configs has the same kubernetesKey, replaces that one with the -given object.

    -

    If inserting multiple objects with the same kubernetesKey, discards all but the last one.

    -

    Does not preserve insertion order of the given objects.

    -

    Parameters

    Returns void

toResourceList

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/classes/FunctionConfigError.html b/docs/api/classes/FunctionConfigError.html deleted file mode 100644 index 647579181..000000000 --- a/docs/api/classes/FunctionConfigError.html +++ /dev/null @@ -1,3 +0,0 @@ -FunctionConfigError | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

Class FunctionConfigError

-

Represents an error with the functionConfig used to parametrize the function.

-

Hierarchy

  • Error
    • FunctionConfigError

Index

Constructors

Constructors

constructor

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/classes/ResourceList.html b/docs/api/classes/ResourceList.html deleted file mode 100644 index b110156d8..000000000 --- a/docs/api/classes/ResourceList.html +++ /dev/null @@ -1,8 +0,0 @@ -ResourceList | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

Class ResourceList

-

ResourceList is the wire format for the output of the kpt function as defined by the spec: -https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md

-

Hierarchy

  • ResourceList

Implements

Index

Constructors

constructor

Properties

Readonly apiVersion

apiVersion: "config.kubernetes.io/v1" = 'config.kubernetes.io/v1'

Readonly items

Readonly kind

kind: "ResourceList" = 'ResourceList'

Readonly metadata

metadata: { name: string } = ...

Type declaration

  • name: string

Optional Readonly results

results?: Result[]

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/classes/TestRunner.html b/docs/api/classes/TestRunner.html deleted file mode 100644 index 0833a6a6f..000000000 --- a/docs/api/classes/TestRunner.html +++ /dev/null @@ -1,38 +0,0 @@ -TestRunner | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

Class TestRunner

-

TestRunner makes it easy to write unit tests for kpt functions.

-

Hierarchy

  • TestRunner

Index

Constructors

constructor

Methods

assert

  • assert(input?: Configs, expectedOutput?: Configs | "unchanged", expectedErrorType?: new (...args: any[]) => Error, expectedErrorMessage?: string | RegExp): Promise<void>
  • -

    Runs the KptFunc and asserts the expected output or error.

    -

    Example usage:

    -
    const RUNNER = new TestRunner(myFunc);

    it('function is a NO OP', async () => {
    await RUNNER.assert());
    }; -
    -

    Parameters

    • input: Configs = ...
      -

      input Configs passed to the function. It is deep-copied before running the function. - If undefined, assumes an empty Configs.

      -
    • Optional expectedOutput: Configs | "unchanged"
      -

      expected resultant Configs after running the function regardless of success or failure. - Use 'unchanged' if the function is not expected to change input Configs.

      -
    • Optional expectedErrorType: new (...args: any[]) => Error
      -

      expected error type to be thrown.

      -
        • new (...args: any[]): Error
        • Parameters

          • Rest ...args: any[]

          Returns Error

    • Optional expectedErrorMessage: string | RegExp
      -

      expected message of expection to be thrown.

      -

    Returns Promise<void>

assertCallback

  • assertCallback(input?: Configs, expectedOutput?: Configs | "unchanged", expectedErrorType?: new (...args: any[]) => Error, expectedErrorMessage?: string | RegExp): () => Promise<void>
  • -

    Similar to assert method, but instead returns an assertion function that can be passed directly to 'it'.

    -

    Example usage:

    -
    const RUNNER = new TestRunner(myFunc);

    it('function is a NO OP', RUNNER.assertCallback()); -
    -

    Parameters

    • input: Configs = ...
      -

      input Configs passed to the function. It is deep-copied before running the function. - If undefined, assumes an empty Configs.

      -
    • Optional expectedOutput: Configs | "unchanged"
      -

      expected resultant Configs after running the function regardless of success or failure. - Use 'unchanged' if the function is not expected to change input Configs.

      -
    • Optional expectedErrorType: new (...args: any[]) => Error
      -

      expected error type to be thrown.

      -
        • new (...args: any[]): Error
        • Parameters

          • Rest ...args: any[]

          Returns Error

    • Optional expectedErrorMessage: string | RegExp
      -

      expected message of expection to be thrown.

      -

    Returns () => Promise<void>

      • (): Promise<void>
      • -

        Similar to assert method, but instead returns an assertion function that can be passed directly to 'it'.

        -

        Example usage:

        -
        const RUNNER = new TestRunner(myFunc);

        it('function is a NO OP', RUNNER.assertCallback()); -
        -

        Returns Promise<void>

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/index.html b/docs/api/index.html deleted file mode 100644 index aababc671..000000000 --- a/docs/api/index.html +++ /dev/null @@ -1,64 +0,0 @@ -kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

kpt-functions

Index

Type aliases

Json

Json: null | boolean | number | string | JsonArray | JsonMap
-

Any plain old JSON value according to ECMA-404.

-

Severity

Severity: "error" | "warn" | "info"
-

Severity of a configuration result.

-

Variables

ANNOTATION_PREFIX

ANNOTATION_PREFIX: "internal.config.kubernetes.io" = 'internal.config.kubernetes.io'

ID_ANNOTATION

ID_ANNOTATION: string = ...

LEGACY_ANNOTATION_PREFIX

LEGACY_ANNOTATION_PREFIX: "config.kubernetes.io" = 'config.kubernetes.io'

LEGACY_ID_ANNOTATION

LEGACY_ID_ANNOTATION: "config.k8s.io/id" = ...

LEGACY_SOURCE_INDEX_ANNOTATION

LEGACY_SOURCE_INDEX_ANNOTATION: string = ...

LEGACY_SOURCE_PATH_ANNOTATION

LEGACY_SOURCE_PATH_ANNOTATION: string = ...

SOURCE_INDEX_ANNOTATION

SOURCE_INDEX_ANNOTATION: string = ...

SOURCE_PATH_ANNOTATION

SOURCE_PATH_ANNOTATION: string = ...

Functions

addAnnotation

  • -

    Add an annotation to a KubernetesObject's metadata. Overwrites the previously existing annotation if it exists. -Return the resulting object.

    -

    Parameters

    • o: KubernetesObject
      -

      The object to add the annotation to.

      -
    • annotation: string
      -

      The annotation to set.

      -
    • value: string
      -

      The value to set the annotation to.

      -

    Returns KubernetesObject

addLabel

  • -

    Add a label to a KubernetesObject's metadata. Overwrites the previously existing label if it exists. -Return the resulting object.

    -

    Parameters

    • o: KubernetesObject
      -

      The object to add the label to.

      -
    • label: string
      -

      The label to set.

      -
    • value: string
      -

      The value to set the label to.

      -

    Returns KubernetesObject

configFileResult

  • configFileResult(message: string, path: string, severity?: Severity, tags?: {}): Result
  • -

    A result relating to a configuration file.

    -

    Parameters

    • message: string
    • path: string
    • severity: Severity = 'error'
    • Optional tags: {}
      • [key: string]: string

    Returns Result

generalResult

  • generalResult(message: string, severity?: Severity, tags?: {}): Result

getAnnotation

  • -

    Get the value of the object's annotation, or undefined if it is not set.

    -

    Parameters

    • o: KubernetesObject
      -

      The object to get the annotation from.

      -
    • annotation: string
      -

      The annotation to get.

      -

    Returns string | undefined

getLabel

  • -

    Get the value of the object's label, or undefined if it is not set.

    -

    Parameters

    • o: KubernetesObject
      -

      The object to get the label from.

      -
    • label: string
      -

      The label to get.

      -

    Returns string | undefined

isKubernetesObject

kubernetesKey

  • -

    A unique key for a Kubernetes object defined as tuple of (apiVersion, kind, namespace, name).

    -

    Parameters

    Returns string

kubernetesObjectResult

removeAnnotation

  • -

    Remove an annotation from a KubernetesObject's metadata. If the resulting metadata.annotations is empty, removes -it. Return the resulting object.

    -

    Parameters

    • o: KubernetesObject
      -

      The object to remove the annotation from.

      -
    • annotation: string
      -

      The annotation to remove.

      -

    Returns KubernetesObject

removeLabel

  • -

    Remove a label from a KubernetesObject's metadata. If the resulting metadata.labels is empty, removes -it. Return the resulting object.

    -

    Parameters

    • o: KubernetesObject
      -

      The object to remove the label from.

      -
    • label: string
      -

      The label to remove.

      -

    Returns KubernetesObject

run

  • -

    This is the main entrypoint for running a kpt function.

    -

    This method does not throw any errors and can be invoked at the top-level without getting -an unhandled promise rejection error.

    -

    Parameters

    Returns Promise<void>

runFnWithConfigs

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/interfaces/FieldInfo.html b/docs/api/interfaces/FieldInfo.html deleted file mode 100644 index 25e04d36f..000000000 --- a/docs/api/interfaces/FieldInfo.html +++ /dev/null @@ -1,3 +0,0 @@ -FieldInfo | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface FieldInfo

-

Metadata about a specific field in a Kubernetes object.

-

Hierarchy

  • FieldInfo

Index

Properties

Optional currentValue

currentValue?: Json

path

path: string

Optional suggestedValue

suggestedValue?: Json

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/interfaces/JsonArray.html b/docs/api/interfaces/JsonArray.html deleted file mode 100644 index 1713f622c..000000000 --- a/docs/api/interfaces/JsonArray.html +++ /dev/null @@ -1,3 +0,0 @@ -JsonArray | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface JsonArray

-

A plain old JSON array according to ECMA-404.

-

Hierarchy

  • Array<Json>
    • JsonArray

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/interfaces/JsonMap.html b/docs/api/interfaces/JsonMap.html deleted file mode 100644 index b099f7368..000000000 --- a/docs/api/interfaces/JsonMap.html +++ /dev/null @@ -1,3 +0,0 @@ -JsonMap | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface JsonMap

-

A plain old JSON object/map according to ECMA-404.

-

Hierarchy

  • JsonMap

Indexable

[field: string]: Json

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/interfaces/KptFunc.html b/docs/api/interfaces/KptFunc.html deleted file mode 100644 index aed6c5ef2..000000000 --- a/docs/api/interfaces/KptFunc.html +++ /dev/null @@ -1,11 +0,0 @@ -KptFunc | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface KptFunc

Hierarchy

  • KptFunc

Callable

  • KptFunc(configs: Configs): Promise<void>
  • -

    A function consumes and optionally mutates Kubernetes configurations using the given Configs object.

    -

    The function should:

    -
      -
    • Throw errors when encountering operational issues such as IO exceptions.
    • -
    • Avoid writing to stdout (e.g. using process.stdout) as it is used for chaining functions. -Use stderr instead.
    • -
    -

    Parameters

    Returns Promise<void>

Index

Properties

Properties

usage

usage: string
-

Usage message describing what the function does, how to use it, and how to configure it.

-

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/interfaces/KubernetesObject.html b/docs/api/interfaces/KubernetesObject.html deleted file mode 100644 index a7a3c7c9d..000000000 --- a/docs/api/interfaces/KubernetesObject.html +++ /dev/null @@ -1,3 +0,0 @@ -KubernetesObject | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface KubernetesObject

-

Interface implemented by Kubernetes objects.

-

Hierarchy

  • KubernetesObject

Implemented by

Index

Properties

apiVersion

apiVersion: string

kind

kind: string

metadata

metadata: ObjectMeta

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/api/interfaces/Result.html b/docs/api/interfaces/Result.html deleted file mode 100644 index 07148d9b3..000000000 --- a/docs/api/interfaces/Result.html +++ /dev/null @@ -1,9 +0,0 @@ -Result | kpt-functions
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface Result

-

Result represents a configuration-related issue returned by a function.

-

It can be at the following granularities:

-
    -
  • A file containing multiple objects
  • -
  • A specific kubernetes object
  • -
  • A specific field of a kubernetes object
  • -
-

Hierarchy

  • Result

Index

Properties

Optional field

field?: FieldInfo

Optional file

file?: { index?: number; path?: string }

Type declaration

  • Optional index?: number
  • Optional path?: string

message

message: string

Optional resourceRef

resourceRef?: { apiVersion: string; kind: string; name: string; namespace: string }

Type declaration

  • apiVersion: string
  • kind: string
  • name: string
  • namespace: string

severity

severity: Severity

Optional tags

tags?: {}

Type declaration

  • [key: string]: string

Legend

  • Constructor
  • Property
  • Method
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/docs/containerizing.md b/docs/containerizing.md new file mode 100644 index 000000000..b1844781d --- /dev/null +++ b/docs/containerizing.md @@ -0,0 +1,136 @@ +# Containerizing KRM Functions + +KRM functions are distributed as container images. This guide covers building +and running containerized functions. + +## Dockerfile + +The [krm-functions-catalog](https://github.com/kptdev/krm-functions-catalog) +provides a shared Dockerfile at `build/docker/go/Dockerfile` that all catalog +functions use. It accepts `BUILDER_IMAGE` and `BASE_IMAGE` as build args. + +For standalone functions or local development, use a multi-stage build with a +minimal base image. The function binary should be statically linked (no CGO) so +it can run on `scratch` or `distroless`: + +```dockerfile +FROM golang:1.26-alpine AS builder +ENV CGO_ENABLED=0 +WORKDIR /go/src/ +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN go build -o /usr/local/bin/function ./ + +FROM scratch +COPY --from=builder /usr/local/bin/function /usr/local/bin/function +ENTRYPOINT ["function"] +``` + +Key points: +- `CGO_ENABLED=0` produces a static binary that runs on `scratch`. +- The `scratch` base image has zero overhead — no shell, no OS packages. +- If you need TLS certificates (e.g., for network calls), use `gcr.io/distroless/static` instead of `scratch`. +- Copy only the binary to the final image to minimize size. + +### Alternative with distroless + +```dockerfile +FROM golang:1.26-alpine AS builder +ENV CGO_ENABLED=0 +WORKDIR /go/src/ +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN go build -o /usr/local/bin/function ./ + +FROM gcr.io/distroless/static:nonroot +COPY --from=builder /usr/local/bin/function /usr/local/bin/function +ENTRYPOINT ["function"] +``` + +## Building + +```bash +docker build -t ghcr.io/kptdev/krm-functions-catalog/my-function:v0.1 . +``` + +### Image Naming Convention + +Follow this pattern for function images: + +``` +ghcr.io/kptdev/krm-functions-catalog/{function-name}:{version} +``` + +Examples: +- `ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1` +- `ghcr.io/kptdev/krm-functions-catalog/enforce-namespace:v1.0` +- `ghcr.io/kptdev/krm-functions-catalog/generate-configmap:v0.3` + +Use semantic versioning for tags. Avoid `latest` in production pipelines. + +## Running + +KRM functions read from STDIN and write to STDOUT: + +```bash +docker run --rm -i ghcr.io/kptdev/krm-functions-catalog/my-function:v0.1 < input.yaml > output.yaml +``` + +### With file mode + +```bash +docker run --rm -v $(pwd):/data ghcr.io/kptdev/krm-functions-catalog/my-function:v0.1 /data/deployment.yaml +``` + +### Help and doc flags + +```bash +docker run --rm ghcr.io/kptdev/krm-functions-catalog/my-function:v0.1 --help +docker run --rm ghcr.io/kptdev/krm-functions-catalog/my-function:v0.1 --doc +``` + +## Using with kpt + +In a `Kptfile` pipeline, `kpt fn render` will pull the image from the registry +and run it against your package resources: + +```yaml +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: my-package +pipeline: + mutators: + - image: ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1 + configMap: + app: my-app + validators: + - image: ghcr.io/kptdev/krm-functions-catalog/enforce-namespace:v1.0 + configMap: + namespace: production +``` + +Note: the image must be published and accessible from the machine running +`kpt fn render`. For local development, build the image locally first and it +will be used from the local Docker cache without pulling. + +## Tips + +- Keep images small — a typical Go KRM function image is 5–15 MB with `scratch`. +- Pin dependency versions in `go.mod` for reproducible builds. +- Use `.dockerignore` to exclude test data, docs, and other non-build files. +- Test the container locally before publishing: + ```bash + echo '{"apiVersion":"config.kubernetes.io/v1","kind":"ResourceList","items":[]}' | \ + docker run --rm -i ghcr.io/kptdev/krm-functions-catalog/my-function:v0.1 + ``` + +## Publishing + +Publishing function images to a registry is handled by the +[krm-functions-catalog](https://github.com/kptdev/krm-functions-catalog) +CI pipeline. See the catalog's +[CONTRIBUTING.md](https://github.com/kptdev/krm-functions-catalog/blob/main/CONTRIBUTING.md) +for the release workflow. \ No newline at end of file diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 6b76b1e2a..000000000 --- a/docs/index.html +++ /dev/null @@ -1,5 +0,0 @@ - - -Redirecting to https://googlecontainertools.github.io/kpt - - \ No newline at end of file diff --git a/docs/interfaces.md b/docs/interfaces.md new file mode 100644 index 000000000..53a89d5b7 --- /dev/null +++ b/docs/interfaces.md @@ -0,0 +1,178 @@ +# Interfaces + +The SDK provides two interfaces for implementing KRM functions. Choose based on +what your function needs to do. + +## fn.Runner + +Use `fn.Runner` for **transformers** (mutators) and **validators**. This is the +recommended interface for most functions. + +```go +type Runner interface { + Run(context *Context, functionConfig *KubeObject, items KubeObjects, results *Results) bool +} +``` + +Characteristics: +- The SDK automatically parses `functionConfig` into your struct's exported fields (via JSON tags). +- You can **modify** existing items but cannot add or remove items from the slice. +- Return `true` for success, `false` for failure. +- Use `results` to report structured info/warning/error messages. + +### Example: Validator + +```go +var _ fn.Runner = &EnforceNamespace{} + +type EnforceNamespace struct { + Namespace string `json:"namespace"` +} + +func (r *EnforceNamespace) Run(ctx *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects, results *fn.Results) bool { + for _, obj := range items { + if obj.GetNamespace() != r.Namespace { + results.Errorf("resource %s/%s has namespace %q, expected %q", + obj.GetKind(), obj.GetName(), obj.GetNamespace(), r.Namespace) + } + } + return results.ExitCode() == 0 +} + +func main() { + runner := fn.WithContext(context.Background(), &EnforceNamespace{}) + if err := fn.AsMain(runner); err != nil { + os.Exit(1) + } +} +``` + +### Example: Transformer (Mutator) + +```go +var _ fn.Runner = &SetAnnotations{} + +type SetAnnotations struct { + Annotations map[string]string `json:"annotations,omitempty"` +} + +func (r *SetAnnotations) Run(ctx *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects, results *fn.Results) bool { + for _, obj := range items { + for k, v := range r.Annotations { + if err := obj.SetAnnotation(k, v); err != nil { + results.ErrorE(err) + } + } + } + return results.ExitCode() == 0 +} +``` + +## fn.ResourceListProcessor + +Use `fn.ResourceListProcessor` for **generators** and **complex functions** that +need full control over the ResourceList. + +```go +type ResourceListProcessor interface { + Process(rl *ResourceList) (bool, error) +} +``` + +Characteristics: +- Full access to `ResourceList.Items` — you can add, remove, or modify items. +- You must parse `functionConfig` manually from `rl.FunctionConfig`. +- You can modify `rl.Results` directly. +- Return `(true, nil)` for success, `(false, err)` for failure. + +### Example: Generator + +```go +type ConfigMapGenerator struct{} + +func (g *ConfigMapGenerator) Process(rl *fn.ResourceList) (bool, error) { + // Parse functionConfig manually + name, _, _ := rl.FunctionConfig.NestedString("metadata", "name") + + // Generate a new ConfigMap + cm := fn.NewEmptyKubeObject() + if err := cm.SetAPIVersion("v1"); err != nil { + return false, err + } + if err := cm.SetKind("ConfigMap"); err != nil { + return false, err + } + if err := cm.SetName(name + "-generated"); err != nil { + return false, err + } + if err := cm.SetNamespace("default"); err != nil { + return false, err + } + + // Add to items + rl.Items = append(rl.Items, cm) + return true, nil +} + +func main() { + if err := fn.AsMain(&ConfigMapGenerator{}); err != nil { + os.Exit(1) + } +} +``` + +### ResourceListProcessorFunc + +For simple cases, use the function adapter instead of defining a struct: + +```go +type ResourceListProcessorFunc func(rl *ResourceList) (bool, error) +``` + +Example: + +```go +func main() { + processor := fn.ResourceListProcessorFunc(func(rl *fn.ResourceList) (bool, error) { + for _, obj := range rl.Items { + if err := obj.SetLabel("managed-by", "my-function"); err != nil { + return false, err + } + } + return true, nil + }) + if err := fn.AsMain(processor); err != nil { + os.Exit(1) + } +} +``` + +## Choosing Between Interfaces + +| Capability | fn.Runner | fn.ResourceListProcessor | +|---|---|---| +| Auto-parse functionConfig | ✅ | ❌ (manual) | +| Modify existing items | ✅ | ✅ | +| Add new items | ❌ | ✅ | +| Remove items | ❌ | ✅ | +| Access full ResourceList | ❌ | ✅ | +| Best for | Transformers, Validators | Generators, Complex functions | + +## Wrapping a Runner + +`fn.Runner` is wrapped into a `ResourceListProcessor` internally using +`fn.WithContext`: + +```go +runner := fn.WithContext(context.Background(), &MyFunction{}) +// runner implements ResourceListProcessor and can be passed to fn.AsMain +``` + +This wrapper handles: +1. Parsing `functionConfig` into your struct fields +2. Calling your `Run` method with the parsed context +3. Collecting results and determining success/failure + +--- + +Next: [Testing](testing.md) — golden test patterns for verifying your function. diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 000000000..a917528b0 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,217 @@ +# Testing KRM Functions + +The SDK provides a golden test framework in `fn/testhelpers` for snapshot-based +testing of KRM functions. + +## Golden Test Pattern + +Golden tests compare function output against expected baseline files. This +approach catches regressions and makes it easy to review output changes. + +### Directory Structure + +``` +testdata/ +├── test-case-1/ +│ ├── _expected.yaml # Expected output (full ResourceList YAML) +│ ├── _fnconfig.yaml # FunctionConfig for this test case +│ └── resources.yaml # Input KRM resources +└── test-case-2/ + ├── _expected.yaml + ├── _fnconfig.yaml + └── resources.yaml +``` + +Conventions: +- Files prefixed with `_` are special — they are not included in the input items. +- `_fnconfig.yaml` contains the functionConfig passed to your function. +- `_expected.yaml` contains the expected ResourceList output. +- All other `.yaml` files in the directory are parsed as input resources. +- You can have multiple input files (e.g., `deployments.yaml`, `services.yaml`). + +### Writing a Golden Test + +```go +package main + +import ( + "context" + "testing" + + "github.com/kptdev/krm-functions-sdk/go/fn" + "github.com/kptdev/krm-functions-sdk/go/fn/testhelpers" +) + +func TestFunction(t *testing.T) { + runner := fn.WithContext(context.TODO(), &YourFunction{}) + testhelpers.RunGoldenTests(t, "testdata", runner) +} +``` + +`RunGoldenTests` will: +1. Discover all subdirectories under `testdata/`. +2. For each subdirectory, parse all non-`_` prefixed YAML files as input items. +3. Parse `_fnconfig.yaml` as the functionConfig. +4. Run your processor against the assembled ResourceList. +5. Compare the output against `_expected.yaml`. + +### Example Test Data + +The following example is illustrative — it shows what test data looks like for a +function that sets labels. The [`go/get-started/`](../go/get-started/) example +provides a minimal working skeleton you can build from. + +`testdata/add-labels/_fnconfig.yaml`: +```yaml +apiVersion: fn.kpt.dev/v1alpha1 +kind: SetLabels +metadata: + name: my-config +labels: + app: my-app +``` + +`testdata/add-labels/resources.yaml`: +```yaml +apiVersion: v1 +kind: Service +metadata: + name: my-service +spec: + selector: + app: my-app +``` + +`testdata/add-labels/_expected.yaml`: +```yaml +apiVersion: config.kubernetes.io/v1 +kind: ResourceList +items: +- apiVersion: v1 + kind: Service + metadata: + name: my-service + labels: + app: my-app + spec: + selector: + app: my-app +functionConfig: + apiVersion: fn.kpt.dev/v1alpha1 + kind: SetLabels + metadata: + name: my-config + labels: + app: my-app +results: +- message: updated labels + severity: info +``` + +## Running Tests + +From your function's module root (where `go.mod` lives): + +```bash +go test ./... +``` + +If the function output doesn't match `_expected.yaml`, the test fails with a +diff showing what changed. See [`go/get-started/`](../go/get-started/) for a +complete working example. + +## Updating Expected Output + +When your function's output changes intentionally, regenerate the expected files: + +```bash +WRITE_GOLDEN_OUTPUT=1 go test ./... +``` + +This overwrites all `_expected.yaml` files with the actual output. Review the +diffs in version control before committing. + +**Caution:** `WRITE_GOLDEN_OUTPUT` accepts whatever the function currently +produces as "correct." If the function has a bug, you've just blessed buggy +output. Golden tests verify *stability* (did the output change?), not +*correctness* (is the output right?). Always review the diffs carefully. +For correctness guarantees, complement golden tests with property-based tests +that assert invariants (e.g., "all resources have the expected label"). + +Note: other kpt ecosystem projects use different env var names for the same +purpose (`KPT_E2E_UPDATE_EXPECTED` in kpt, `UPDATE_GOLDEN_FILES` in porch). +`WRITE_GOLDEN_OUTPUT` is the standard for the SDK and catalog functions. + +## Testing a ResourceListProcessor + +`RunGoldenTests` accepts any `fn.ResourceListProcessor`, so it works with both +`fn.Runner` (wrapped via `fn.WithContext`) and direct `ResourceListProcessor` +implementations: + +```go +func TestGenerator(t *testing.T) { + testhelpers.RunGoldenTests(t, "testdata", &MyGenerator{}) +} +``` + +## Unit Testing Without Golden Files + +For simpler unit tests, you can construct a ResourceList directly: + +```go +func TestSetLabels(t *testing.T) { + input := []byte(` +apiVersion: config.kubernetes.io/v1 +kind: ResourceList +items: +- apiVersion: v1 + kind: ConfigMap + metadata: + name: test +functionConfig: + apiVersion: fn.kpt.dev/v1alpha1 + kind: SetLabels + labels: + env: prod +`) + runner := fn.WithContext(context.TODO(), &SetLabels{}) + output, err := fn.Run(runner, input) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + rl, err := fn.ParseResourceList(output) + if err != nil { + t.Fatalf("failed to parse output: %v", err) + } + + label, _, _ := rl.Items[0].NestedString("metadata", "labels", "env") + if label != "prod" { + t.Errorf("expected label env=prod, got %q", label) + } +} +``` + +## Tips + +- Keep test cases focused — one behavior per test directory. +- Use descriptive directory names (e.g., `empty-input`, `missing-namespace`, `multiple-resources`). +- The `_fnconfig.yaml` can be empty if your function doesn't require configuration. +- Golden tests catch unintentional formatting changes too, which helps maintain stable output. + +## End-to-End Testing + +The SDK's `testhelpers.RunGoldenTests` tests function logic in isolation — no +container, no kpt CLI. For full integration testing (container execution, +`kpt fn eval`/`kpt fn render` pipelines), the kpt repo provides a separate e2e +test runner at +[`pkg/test/runner`](https://github.com/kptdev/kpt/tree/main/pkg/test/runner). + +The e2e runner uses a different test structure (`.expected/` directories with +`config.yaml`, `diff.patch`, `results.yaml`) and is used by the +[krm-functions-catalog](https://github.com/kptdev/krm-functions-catalog) `tests/` +directory to validate functions running inside containers against `kpt fn render`. + +--- + +Next: [Containerizing](containerizing.md) — packaging your function as a container image. diff --git a/docs/tutorial.md b/docs/tutorial.md new file mode 100644 index 000000000..158bbc815 --- /dev/null +++ b/docs/tutorial.md @@ -0,0 +1,229 @@ +# Tutorial: Developing a KRM Function + +This tutorial walks through the end-to-end workflow for building a KRM function +using the Go SDK. By the end, you'll have a working function with embedded +documentation, golden tests, and support for `--help`, `--doc`, and standalone +file mode. + +For a complete working example, see [`go/get-started/`](../go/get-started/). + +## 1. Create Your Function + +A KRM function implements the `fn.Runner` interface: + +```go +type Runner interface { + Run(context *Context, functionConfig *KubeObject, items KubeObjects, results *Results) bool +} +``` + +Here's a minimal function that sets labels on all resources: + +```go +package main + +import ( + "context" + _ "embed" + "os" + + "github.com/kptdev/krm-functions-sdk/go/fn" +) + +//go:embed README.md +var readme []byte + +//go:embed metadata.yaml +var metadata []byte + +var _ fn.Runner = &SetLabels{} + +type SetLabels struct { + Labels map[string]string `json:"labels,omitempty"` +} + +func (r *SetLabels) Run(ctx *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects, results *fn.Results) bool { + for _, obj := range items { + for k, v := range r.Labels { + if err := obj.SetLabel(k, v); err != nil { + results.ErrorE(err) + } + } + } + return results.ExitCode() == 0 +} + +func main() { + runner := fn.WithContext(context.Background(), &SetLabels{}) + if err := fn.AsMain(runner, fn.WithDocs(readme, metadata)); err != nil { + os.Exit(1) + } +} +``` + +Key points: +- Your struct fields are automatically populated from `functionConfig` (JSON unmarshaling). +- Return `true` for success, `false` for failure. +- Use `results` to report structured messages (info, warning, error). + +## 2. Embed Documentation with `//go:embed` + +The SDK uses Go's embed directive to bundle documentation into the binary. +Two files are needed: + +### README.md + +Use `` markers to define sections that `--help` and `--doc` extract: + + # set-labels + + + Set labels on all resources in the package. + + + + ## Usage + + The `set-labels` function adds or updates labels on all KRM resources. + It accepts a `SetLabels` functionConfig with a `labels` map. + + ### FunctionConfig + + ```yaml + apiVersion: fn.kpt.dev/v1alpha1 + kind: SetLabels + metadata: + name: my-config + labels: + app: my-app + env: production + ``` + + + + + + Set a single label on all resources: + + ```yaml + apiVersion: fn.kpt.dev/v1alpha1 + kind: SetLabels + labels: + team: platform + ``` + + + +### metadata.yaml + +```yaml +image: ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1 +description: Set labels on all resources +tags: + - mutator + - labels +sourceURL: https://github.com/kptdev/krm-functions/tree/main/functions/go/set-labels +examplePackageURLs: + - https://github.com/kptdev/krm-functions/tree/main/examples/set-labels-simple +license: Apache-2.0 +hidden: false +``` + +### Wire it up + +In your `main.go`: + +```go +//go:embed README.md +var readme []byte + +//go:embed metadata.yaml +var metadata []byte + +func main() { + runner := fn.WithContext(context.Background(), &SetLabels{}) + if err := fn.AsMain(runner, fn.WithDocs(readme, metadata)); err != nil { + os.Exit(1) + } +} +``` + +## 3. Running Your Function + +### Standard mode (STDIN/STDOUT) + +Pipe a ResourceList through your function: + +```bash +cat input.yaml | go run . > output.yaml +``` + +### Help mode + +View human-readable documentation: + +```bash +go run . --help +``` + +This prints the Short, Long, and Examples sections extracted from your README markers. + +### Doc mode + +Get machine-readable JSON documentation (consumed by `kpt fn doc` and catalog pipelines): + +```bash +go run . --doc +``` + +### File mode + +Process KRM files directly without constructing a ResourceList: + +```bash +go run . deployment.yaml service.yaml +``` + +This reads the YAML files, assembles them into a ResourceList with an empty +functionConfig, processes them, and writes the result to STDOUT. + +## 4. Testing with Golden Tests + +The SDK provides `testhelpers.RunGoldenTests` for snapshot-based testing. + +Create a test directory structure: + +``` +testdata/ +├── test-case-1/ +│ ├── _expected.yaml # Expected output (ResourceList YAML) +│ ├── _fnconfig.yaml # FunctionConfig for this test case +│ └── resources.yaml # Input resources +└── test-case-2/ + ├── _expected.yaml + ├── _fnconfig.yaml + └── resources.yaml +``` + +Write your test: + +```go +func TestFunction(t *testing.T) { + runner := fn.WithContext(context.TODO(), &SetLabels{}) + testhelpers.RunGoldenTests(t, "testdata", runner) +} +``` + +Update expected output after changes: + +```bash +WRITE_GOLDEN_OUTPUT=1 go test ./... +``` + +See [testing](testing.md) for more details. + +## 5. Next Steps + +- [Interfaces](interfaces.md) — when to use `fn.Runner` vs `fn.ResourceListProcessor` +- [Testing](testing.md) — golden test patterns in depth +- [Containerizing](containerizing.md) — packaging your function as a container image diff --git a/go/fn/examples/go.mod b/go/fn/examples/go.mod index 9942ac8e8..ba2023be9 100644 --- a/go/fn/examples/go.mod +++ b/go/fn/examples/go.mod @@ -2,7 +2,9 @@ module github.com/kptdev/krm-functions-sdk/go/fn/examples go 1.26.2 -replace github.com/kptdev/krm-functions-sdk/go/fn v0.0.0 => ../ +// NOTE: replace directive is for in-repo development only. +// External consumers should use: require github.com/kptdev/krm-functions-sdk/go/fn v1.x.x +replace github.com/kptdev/krm-functions-sdk/go/fn => ../ require ( github.com/kptdev/krm-functions-sdk/go/fn v1.0.0 @@ -33,11 +35,11 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kptdev/kpt v1.0.0-beta.61 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect @@ -51,8 +53,6 @@ require ( k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect - sigs.k8s.io/kustomize/api v0.20.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect - sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go/fn/examples/go.sum b/go/fn/examples/go.sum index f49375ef5..d6f179e5c 100644 --- a/go/fn/examples/go.sum +++ b/go/fn/examples/go.sum @@ -49,10 +49,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kptdev/kpt v1.0.0-beta.61 h1:uTMcRVmLnVTUUXS1PYoTbc0QEda8sL9J3ymU96RhMDk= -github.com/kptdev/kpt v1.0.0-beta.61/go.mod h1:EvyF48ocsuwB1XO+2KAwjqMX+qYVYbZ8h/HKufXRmcE= -github.com/kptdev/krm-functions-sdk/go/fn v1.0.0 h1:2xTAEw0/mWNnPNvBR7K3rvrnjmBMxVbtTyu2ZHJjQxo= -github.com/kptdev/krm-functions-sdk/go/fn v1.0.0/go.mod h1:GxUbq9hEUYUtl2rGyQfzxz++xV+dSRrHpRxsx5l0PvA= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -132,8 +128,6 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= @@ -144,10 +138,10 @@ k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZ k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +pgregory.net/rapid v1.3.0 h1:vBvO0VSqti75J1jjYqpgPNBLKMd1+gxa9fYo7vk/Exc= +pgregory.net/rapid v1.3.0/go.mod h1:dPlE4OBBxgXPqkP79flB6sJL1dx5azpI7HQ9MY9Z7uk= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= -sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= diff --git a/go/fn/go.mod b/go/fn/go.mod index 52df2e854..92b1f9106 100644 --- a/go/fn/go.mod +++ b/go/fn/go.mod @@ -45,4 +45,5 @@ require ( google.golang.org/protobuf v1.36.10 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect + pgregory.net/rapid v1.3.0 // indirect ) diff --git a/go/fn/go.sum b/go/fn/go.sum index 601bb84db..2dbc38314 100644 --- a/go/fn/go.sum +++ b/go/fn/go.sum @@ -113,6 +113,8 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +pgregory.net/rapid v1.3.0 h1:vBvO0VSqti75J1jjYqpgPNBLKMd1+gxa9fYo7vk/Exc= +pgregory.net/rapid v1.3.0/go.mod h1:dPlE4OBBxgXPqkP79flB6sJL1dx5azpI7HQ9MY9Z7uk= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= diff --git a/go/fn/internal/docs/markers.go b/go/fn/internal/docs/markers.go new file mode 100644 index 000000000..04efb9e91 --- /dev/null +++ b/go/fn/internal/docs/markers.go @@ -0,0 +1,82 @@ +// Copyright 2026 The kpt Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package docs + +import "strings" + +// Sections holds parsed README marker content. +type Sections struct { + Short string + Long string + Examples string +} + +const ( + markerShort = "" + markerLong = "" + markerExamples = "" + markerEnd = "" +) + +// ParseMarkers extracts mdtogo marker sections from README content. +// Missing markers result in empty strings for the corresponding sections. +// When no markers are present at all, the full content (trimmed) is returned +// as the Long description. +func ParseMarkers(readme []byte) Sections { + content := string(readme) + + short := extractSection(content, markerShort) + long := extractSection(content, markerLong) + examples := extractSection(content, markerExamples) + + // Fallback: if no markers are present at all, use full content as Long. + if !hasAnyMarker(content) { + return Sections{ + Long: strings.TrimSpace(content), + } + } + + return Sections{ + Short: short, + Long: long, + Examples: examples, + } +} + +// extractSection finds text between the given start marker and the next +// end marker. Returns empty string if either marker is missing. +func extractSection(content, startMarker string) string { + startIdx := strings.Index(content, startMarker) + if startIdx < 0 { + return "" + } + afterStart := startIdx + len(startMarker) + remaining := content[afterStart:] + + before, _, ok := strings.Cut(remaining, markerEnd) + if !ok { + return "" + } + + return strings.TrimSpace(before) +} + +// hasAnyMarker reports whether the content contains any mdtogo marker. +func hasAnyMarker(content string) bool { + return strings.Contains(content, markerShort) || + strings.Contains(content, markerLong) || + strings.Contains(content, markerExamples) || + strings.Contains(content, markerEnd) +} diff --git a/go/fn/internal/docs/markers_test.go b/go/fn/internal/docs/markers_test.go new file mode 100644 index 000000000..a3501f876 --- /dev/null +++ b/go/fn/internal/docs/markers_test.go @@ -0,0 +1,119 @@ +// Copyright 2026 The kpt Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package docs + +import ( + "fmt" + "strings" + "testing" + + "pgregory.net/rapid" +) + +// Feature: sdk-alignment, Property 1: Marker parser round-trip +// +// For any three strings (short, long, examples), formatting them into a README +// with mdtogo markers and then parsing that README with ParseMarkers SHALL +// produce a Sections struct with fields equal to the original strings (after trimming). +// +// Validates: Requirements 6.1, 6.2, 6.3, 6.5, 5.2 + +// genSectionContent generates arbitrary non-empty strings that do not contain +// mdtogo markers (which would confuse the parser). +func genSectionContent() *rapid.Generator[string] { + return rapid.Custom(func(t *rapid.T) string { + s := rapid.StringMatching(`[a-zA-Z0-9 \t\n.,;:!?(){}\[\]'"/_-]{1,200}`).Draw(t, "content") + // Ensure the generated content does not accidentally contain marker strings. + s = strings.ReplaceAll(s, " +%s + + + +%s + + + +%s + +`, short, long, examples) +} + +// Feature: sdk-alignment, Property 7: Missing markers fallback +// +// For any README content that does NOT contain mdtogo markers, ParseMarkers +// SHALL return empty strings for Short and Examples, and the full content +// (trimmed) as Long. +// +// Validates: Requirements 5.4, 6.4 + +// genNoMarkerContent generates arbitrary strings guaranteed not to contain +// any mdtogo marker substrings. +func genNoMarkerContent() *rapid.Generator[string] { + return rapid.Custom(func(t *rapid.T) string { + s := rapid.StringMatching(`[a-zA-Z0-9 \t\n.,;:!?(){}\[\]'"/_-]{0,300}`).Draw(t, "content") + // Strip anything that could form a marker. + s = strings.ReplaceAll(s, " +%s + + + +%s + + + +%s + +`, short, long, examples) + + // Parse the README to get sections. + sections := ParseMarkers([]byte(readme)) + + // Generate non-empty metadata fields to ensure all appear in output. + meta := Metadata{ + Image: rapid.StringMatching(`gcr\.io/[a-z0-9-]{3,20}/[a-z0-9-]{3,20}:v[0-9]+\.[0-9]+`).Draw(t, "image"), + Description: genNonEmptySectionContent().Draw(t, "description"), + Tags: rapid.SliceOfN( + rapid.StringMatching(`[a-z]{3,10}`), 1, 5, + ).Draw(t, "tags"), + SourceURL: rapid.StringMatching(`https://github\.com/[a-z0-9-]{3,20}/[a-z0-9-]{3,20}`).Draw(t, "sourceURL"), + ExamplePackageURLs: rapid.SliceOfN(rapid.StringMatching(`https://github\.com/[a-z0-9-]{3,20}/[a-z0-9-]{3,20}`), 1, 3).Draw(t, "exampleURLs"), + License: rapid.SampledFrom([]string{"Apache-2.0", "MIT", "BSD-3-Clause"}).Draw(t, "license"), + Hidden: rapid.Bool().Draw(t, "hidden"), + } + + // Render doc JSON output. + var buf bytes.Buffer + err := RenderDoc(&buf, sections, meta) + if err != nil { + t.Fatalf("RenderDoc returned error: %v", err) + } + + // Decode the JSON output. + var output DocOutput + if err := json.Unmarshal(buf.Bytes(), &output); err != nil { + t.Fatalf("failed to decode doc JSON: %v\n Raw: %s", err, buf.String()) + } + + // Assert all non-empty source values from README appear in output. + if output.Short != sections.Short { + t.Fatalf("doc JSON 'short' mismatch\n expected: %q\n got: %q", sections.Short, output.Short) + } + if output.Long != sections.Long { + t.Fatalf("doc JSON 'long' mismatch\n expected: %q\n got: %q", sections.Long, output.Long) + } + if output.Examples != sections.Examples { + t.Fatalf("doc JSON 'examples' mismatch\n expected: %q\n got: %q", sections.Examples, output.Examples) + } + + // Assert all non-empty source values from metadata appear in output. + if output.Image != meta.Image { + t.Fatalf("doc JSON 'image' mismatch\n expected: %q\n got: %q", meta.Image, output.Image) + } + if output.Description != meta.Description { + t.Fatalf("doc JSON 'description' mismatch\n expected: %q\n got: %q", meta.Description, output.Description) + } + if len(output.Tags) != len(meta.Tags) { + t.Fatalf("doc JSON 'tags' length mismatch\n expected: %v\n got: %v", meta.Tags, output.Tags) + } + for i, tag := range meta.Tags { + if output.Tags[i] != tag { + t.Fatalf("doc JSON 'tags[%d]' mismatch\n expected: %q\n got: %q", i, tag, output.Tags[i]) + } + } + if output.SourceURL != meta.SourceURL { + t.Fatalf("doc JSON 'sourceURL' mismatch\n expected: %q\n got: %q", meta.SourceURL, output.SourceURL) + } + if len(output.ExamplePackageURLs) != len(meta.ExamplePackageURLs) { + t.Fatalf("doc JSON 'examplePackageURLs' length mismatch\n expected: %v\n got: %v", meta.ExamplePackageURLs, output.ExamplePackageURLs) + } + for i, url := range meta.ExamplePackageURLs { + if output.ExamplePackageURLs[i] != url { + t.Fatalf("doc JSON 'examplePackageURLs[%d]' mismatch\n expected: %q\n got: %q", i, url, output.ExamplePackageURLs[i]) + } + } + if output.License != meta.License { + t.Fatalf("doc JSON 'license' mismatch\n expected: %q\n got: %q", meta.License, output.License) + } + if output.Hidden != meta.Hidden { + t.Fatalf("doc JSON 'hidden' mismatch\n expected: %v\n got: %v", meta.Hidden, output.Hidden) + } + }) +} + +func TestProperty3_HelpOutputContainsParsedSections(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + short := genNonEmptySectionContent().Draw(t, "short") + long := genNonEmptySectionContent().Draw(t, "long") + examples := genNonEmptySectionContent().Draw(t, "examples") + + // Format a README with valid mdtogo markers. + readme := fmt.Sprintf(` +%s + + + +%s + + + +%s + +`, short, long, examples) + + // Parse the README to get sections (same as runtime would). + sections := ParseMarkers([]byte(readme)) + + // Render help output. + var buf bytes.Buffer + RenderHelp(&buf, sections, Metadata{}) + output := buf.String() + + // Assert that the help output contains each parsed section. + if !strings.Contains(output, sections.Short) { + t.Fatalf("help output does not contain Short section\n Short: %q\n Output: %q", sections.Short, output) + } + if !strings.Contains(output, sections.Long) { + t.Fatalf("help output does not contain Long section\n Long: %q\n Output: %q", sections.Long, output) + } + if !strings.Contains(output, sections.Examples) { + t.Fatalf("help output does not contain Examples section\n Examples: %q\n Output: %q", sections.Examples, output) + } + }) +} + +// --- Unit Tests for Renderers --- +// Validates: Requirements 2.3, 3.3, 3.6 + +func TestRenderHelp_FullSectionsAndMetadata(t *testing.T) { + sections := Sections{ + Short: "Set labels on all resources", + Long: "The set-labels function adds or updates labels on all resources in the package.", + Examples: " kpt fn eval --image ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1 -- label_name=label_value", + } + meta := Metadata{ + Image: "ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1", + Description: "Set labels on all resources", + Tags: []string{"mutator", "labels"}, + } + + var buf bytes.Buffer + RenderHelp(&buf, sections, meta) + output := buf.String() + + // Verify output contains the Short description. + if !strings.Contains(output, sections.Short) { + t.Errorf("expected output to contain Short %q, got:\n%s", sections.Short, output) + } + // Verify output contains the Long description. + if !strings.Contains(output, sections.Long) { + t.Errorf("expected output to contain Long %q, got:\n%s", sections.Long, output) + } + // Verify output contains the Examples content. + if !strings.Contains(output, sections.Examples) { + t.Errorf("expected output to contain Examples %q, got:\n%s", sections.Examples, output) + } + // Verify the "Examples:" header is present. + if !strings.Contains(output, "Examples:") { + t.Errorf("expected output to contain 'Examples:' header, got:\n%s", output) + } + // Verify no cobra boilerplate. + if strings.Contains(output, "Usage:") { + t.Errorf("output should not contain 'Usage:', got:\n%s", output) + } + if strings.Contains(output, "Flags:") { + t.Errorf("output should not contain 'Flags:', got:\n%s", output) + } +} + +func TestRenderHelp_EmptySections(t *testing.T) { + sections := Sections{} + meta := Metadata{} + + var buf bytes.Buffer + RenderHelp(&buf, sections, meta) + output := buf.String() + + expected := "No documentation available. Pass fn.WithDocs to fn.AsMain to enable --help.\n" + if output != expected { + t.Errorf("expected minimal message %q, got %q", expected, output) + } +} + +func TestRenderDoc_ValidJSON_AllFields(t *testing.T) { + sections := Sections{ + Short: "Set labels on all resources", + Long: "The set-labels function adds or updates labels.", + Examples: " kpt fn eval --image ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1", + } + meta := Metadata{ + Image: "ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1", + Description: "Set labels on all resources", + Tags: []string{"mutator", "labels"}, + SourceURL: "https://github.com/kptdev/krm-functions/tree/main/functions/go/set-labels", + ExamplePackageURLs: []string{"https://github.com/kptdev/krm-functions/tree/main/examples/set-labels-simple"}, + License: "Apache-2.0", + Hidden: false, + } + + var buf bytes.Buffer + err := RenderDoc(&buf, sections, meta) + if err != nil { + t.Fatalf("RenderDoc returned error: %v", err) + } + + // Verify output is valid JSON. + var output DocOutput + if err := json.Unmarshal(buf.Bytes(), &output); err != nil { + t.Fatalf("output is not valid JSON: %v\nRaw: %s", err, buf.String()) + } + + // Verify all fields are correctly populated. + if output.Short != sections.Short { + t.Errorf("Short: got %q, want %q", output.Short, sections.Short) + } + if output.Long != sections.Long { + t.Errorf("Long: got %q, want %q", output.Long, sections.Long) + } + if output.Examples != sections.Examples { + t.Errorf("Examples: got %q, want %q", output.Examples, sections.Examples) + } + if output.Image != meta.Image { + t.Errorf("Image: got %q, want %q", output.Image, meta.Image) + } + if output.Description != meta.Description { + t.Errorf("Description: got %q, want %q", output.Description, meta.Description) + } + if len(output.Tags) != len(meta.Tags) { + t.Errorf("Tags length: got %d, want %d", len(output.Tags), len(meta.Tags)) + } else { + for i, tag := range meta.Tags { + if output.Tags[i] != tag { + t.Errorf("Tags[%d]: got %q, want %q", i, output.Tags[i], tag) + } + } + } + if output.SourceURL != meta.SourceURL { + t.Errorf("SourceURL: got %q, want %q", output.SourceURL, meta.SourceURL) + } + if len(output.ExamplePackageURLs) != len(meta.ExamplePackageURLs) { + t.Errorf("ExamplePackageURLs length: got %d, want %d", len(output.ExamplePackageURLs), len(meta.ExamplePackageURLs)) + } else { + for i, url := range meta.ExamplePackageURLs { + if output.ExamplePackageURLs[i] != url { + t.Errorf("ExamplePackageURLs[%d]: got %q, want %q", i, output.ExamplePackageURLs[i], url) + } + } + } + if output.License != meta.License { + t.Errorf("License: got %q, want %q", output.License, meta.License) + } + if output.Hidden != meta.Hidden { + t.Errorf("Hidden: got %v, want %v", output.Hidden, meta.Hidden) + } +} + +func TestRenderDoc_EmptyInputs(t *testing.T) { + sections := Sections{} + meta := Metadata{} + + var buf bytes.Buffer + err := RenderDoc(&buf, sections, meta) + if err != nil { + t.Fatalf("RenderDoc returned error: %v", err) + } + + // Verify output is valid JSON. + var raw map[string]any + if err := json.Unmarshal(buf.Bytes(), &raw); err != nil { + t.Fatalf("output is not valid JSON: %v\nRaw: %s", err, buf.String()) + } + + // Verify it decodes to a DocOutput with zero-value fields. + var output DocOutput + if err := json.Unmarshal(buf.Bytes(), &output); err != nil { + t.Fatalf("failed to decode into DocOutput: %v", err) + } + + if output.Short != "" { + t.Errorf("Short: got %q, want empty", output.Short) + } + if output.Long != "" { + t.Errorf("Long: got %q, want empty", output.Long) + } + if output.Examples != "" { + t.Errorf("Examples: got %q, want empty", output.Examples) + } + if output.Image != "" { + t.Errorf("Image: got %q, want empty", output.Image) + } + if output.Hidden != false { + t.Errorf("Hidden: got %v, want false", output.Hidden) + } +} + +func TestRenderDoc_HiddenFieldSerialization(t *testing.T) { + sections := Sections{ + Short: "A hidden function", + } + meta := Metadata{ + Hidden: true, + } + + var buf bytes.Buffer + err := RenderDoc(&buf, sections, meta) + if err != nil { + t.Fatalf("RenderDoc returned error: %v", err) + } + + // Verify the raw JSON contains "hidden": true. + var raw map[string]any + if err := json.Unmarshal(buf.Bytes(), &raw); err != nil { + t.Fatalf("output is not valid JSON: %v\nRaw: %s", err, buf.String()) + } + + hiddenVal, ok := raw["hidden"] + if !ok { + t.Fatal("JSON output does not contain 'hidden' field") + } + if hiddenVal != true { + t.Errorf("hidden field: got %v, want true", hiddenVal) + } + + // Also verify via struct decoding. + var output DocOutput + if err := json.Unmarshal(buf.Bytes(), &output); err != nil { + t.Fatalf("failed to decode into DocOutput: %v", err) + } + if !output.Hidden { + t.Errorf("DocOutput.Hidden: got false, want true") + } +} diff --git a/go/fn/internal/maphelpers.go b/go/fn/internal/maphelpers.go index fdfcec5d7..3c09356e7 100644 --- a/go/fn/internal/maphelpers.go +++ b/go/fn/internal/maphelpers.go @@ -1,4 +1,4 @@ -// Copyright 2022,2025 The kpt Authors +// Copyright 2022, 2025, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -33,7 +33,7 @@ func (o *MapVariant) GetRNode(fields ...string) (*yaml.RNode, bool, error) { func (o *MapVariant) GetNestedValue(fields ...string) (variant, bool, error) { current := o n := len(fields) - for i := 0; i < n; i++ { + for i := range n { entry, found := current.getVariant(fields[i]) if !found { return nil, found, nil @@ -55,7 +55,7 @@ func (o *MapVariant) SetNestedValue(val variant, fields ...string) error { current := o n := len(fields) var err error - for i := 0; i < n; i++ { + for i := range n { if i == n-1 { current.setField(fields[i], val) } else { @@ -199,7 +199,7 @@ func (o *MapVariant) SetNestedSlice(s *SliceVariant, fields ...string) error { func (o *MapVariant) RemoveNestedField(fields ...string) (bool, error) { current := o n := len(fields) - for i := 0; i < n; i++ { + for i := range n { entry, found := current.getVariant(fields[i]) if !found { return false, nil diff --git a/go/fn/internal/test/variant_test.go b/go/fn/internal/test/variant_test.go index 6755421f2..cffd22742 100644 --- a/go/fn/internal/test/variant_test.go +++ b/go/fn/internal/test/variant_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt Authors +// Copyright 2022, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -30,8 +30,8 @@ func TestMapVariantToTypedObject(t *testing.T) { testcases := []struct { name string src string - dst interface{} - expected interface{} + dst any + expected any }{ { name: "k8s built-in types", @@ -116,7 +116,7 @@ desiredReplicas: 1 func TestNewFromTypedObject(t *testing.T) { testcases := []struct { name string - input interface{} + input any expected string }{ { @@ -264,7 +264,7 @@ func TestBadNewFromTypedObject(t *testing.T) { type Foo struct { metav1.TypeMeta `json:",inline" yaml:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"` + metav1.ObjectMeta `json:"metadata" yaml:"metadata,omitempty"` DesiredReplicas int `json:"desiredReplicas,omitempty" yaml:"desiredReplicas,omitempty"` } diff --git a/go/fn/internal/variant.go b/go/fn/internal/variant.go index 5236266de..f666f7ae6 100644 --- a/go/fn/internal/variant.go +++ b/go/fn/internal/variant.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt Authors +// Copyright 2022, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -117,7 +117,7 @@ func ExtractObjects(nodes ...*yaml.Node) ([]*MapVariant, error) { return objects, nil } -func TypedObjectToMapVariant(v interface{}) (*MapVariant, error) { +func TypedObjectToMapVariant(v any) (*MapVariant, error) { // The built-in types only have json tags. We can't simply do ynode.Encode(v), // since it use the lowercased field name by default if no yaml tag is specified. // This affects both k8s built-in types (e.g. appsv1.Deployment) and any types @@ -129,7 +129,7 @@ func TypedObjectToMapVariant(v interface{}) (*MapVariant, error) { if err != nil { return nil, err } - var m map[string]interface{} + var m map[string]any if err = json.Unmarshal(j, &m); err != nil { return nil, err } @@ -150,7 +150,7 @@ func TypedObjectToMapVariant(v interface{}) (*MapVariant, error) { return mv, err } -func TypedObjectToSliceVariant(v interface{}) (*SliceVariant, error) { +func TypedObjectToSliceVariant(v any) (*SliceVariant, error) { // The built-in types only have json tags. We can't simply do ynode.Encode(v), // since it use the lowercased field name by default if no yaml tag is specified. // This affects both k8s built-in types (e.g. appsv1.Deployment) and any types @@ -162,7 +162,7 @@ func TypedObjectToSliceVariant(v interface{}) (*SliceVariant, error) { if err != nil { return nil, err } - var l []interface{} + var l []any if err = json.Unmarshal(j, &l); err != nil { return nil, err } @@ -180,8 +180,8 @@ func TypedObjectToSliceVariant(v interface{}) (*SliceVariant, error) { return &SliceVariant{node: node}, nil } -func MapVariantToTypedObject(mv *MapVariant, ptr interface{}) error { - if ptr == nil || reflect.ValueOf(ptr).Kind() != reflect.Ptr { +func MapVariantToTypedObject(mv *MapVariant, ptr any) error { + if ptr == nil || reflect.ValueOf(ptr).Kind() != reflect.Pointer { return fmt.Errorf("ptr must be a pointer to an object") } diff --git a/go/fn/kptfileko/conditions.go b/go/fn/kptfileko/conditions.go index 4e4e6ff55..c0c11774f 100644 --- a/go/fn/kptfileko/conditions.go +++ b/go/fn/kptfileko/conditions.go @@ -1,4 +1,4 @@ -// Copyright 2025 The kpt Authors +// Copyright 2025, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,8 +18,8 @@ import ( "fmt" "slices" - kptfileapi "github.com/kptdev/krm-functions-sdk/go/fn/kptfileapi" "github.com/kptdev/krm-functions-sdk/go/fn" + kptfileapi "github.com/kptdev/krm-functions-sdk/go/fn/kptfileapi" ) type SubObjectMatcher func(obj *fn.SubObject) bool diff --git a/go/fn/kptfileko/conditions_test.go b/go/fn/kptfileko/conditions_test.go index 3df08699d..bb49ef820 100644 --- a/go/fn/kptfileko/conditions_test.go +++ b/go/fn/kptfileko/conditions_test.go @@ -20,8 +20,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - kptfileapi "github.com/kptdev/krm-functions-sdk/go/fn/kptfileapi" "github.com/kptdev/krm-functions-sdk/go/fn" + kptfileapi "github.com/kptdev/krm-functions-sdk/go/fn/kptfileapi" ) const ( @@ -66,7 +66,7 @@ func TestIsTypeAndStatus(t *testing.T) { } func TestIsConditionType(t *testing.T) { - ko, err := fn.NewFromTypedObject(map[string]interface{}{"conditionType": ConditionTypeReady}) + ko, err := fn.NewFromTypedObject(map[string]any{"conditionType": ConditionTypeReady}) require.NoError(t, err) assert.True(t, IsConditionType(ConditionTypeReady)(&ko.SubObject)) diff --git a/go/fn/kptfileko/kptfile.go b/go/fn/kptfileko/kptfile.go index b733b1f1c..6715f824c 100644 --- a/go/fn/kptfileko/kptfile.go +++ b/go/fn/kptfileko/kptfile.go @@ -18,8 +18,8 @@ import ( "fmt" "strings" - kptfileapi "github.com/kptdev/krm-functions-sdk/go/fn/kptfileapi" "github.com/kptdev/krm-functions-sdk/go/fn" + kptfileapi "github.com/kptdev/krm-functions-sdk/go/fn/kptfileapi" "sigs.k8s.io/kustomize/kyaml/yaml" ) diff --git a/go/fn/kptfileko/pipeline.go b/go/fn/kptfileko/pipeline.go index d17938334..986de6ce2 100644 --- a/go/fn/kptfileko/pipeline.go +++ b/go/fn/kptfileko/pipeline.go @@ -1,4 +1,4 @@ -// Copyright 2024 The kpt Authors +// Copyright 2024, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,8 +19,8 @@ import ( "reflect" "slices" - kptfileapi "github.com/kptdev/krm-functions-sdk/go/fn/kptfileapi" "github.com/kptdev/krm-functions-sdk/go/fn" + kptfileapi "github.com/kptdev/krm-functions-sdk/go/fn/kptfileapi" ) // UpsertMutatorFunctions ensures that the given KRM functions are added to or updated in the Kptfile's mutators list. diff --git a/go/fn/log.go b/go/fn/log.go index 64c7160b5..6d2fc40eb 100644 --- a/go/fn/log.go +++ b/go/fn/log.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt Authors +// Copyright 2022, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,10 +19,10 @@ import ( "os" ) -func Log(in ...interface{}) { +func Log(in ...any) { fmt.Fprintln(os.Stderr, in...) } -func Logf(format string, in ...interface{}) { +func Logf(format string, in ...any) { fmt.Fprintf(os.Stderr, format, in...) } diff --git a/go/fn/object.go b/go/fn/object.go index 071f82f80..68f938fcd 100644 --- a/go/fn/object.go +++ b/go/fn/object.go @@ -138,8 +138,8 @@ func (o *SubObject) NestedSubObject(fields ...string) (SubObject, bool, error) { } // NestedResource returns a map[string]string value of a nested field, false if not found and an error if not a map[string]string type. -func (o *SubObject) NestedResource(ptr interface{}, fields ...string) (bool, error) { - if ptr == nil || reflect.ValueOf(ptr).Kind() != reflect.Ptr { +func (o *SubObject) NestedResource(ptr any, fields ...string) (bool, error) { + if ptr == nil || reflect.ValueOf(ptr).Kind() != reflect.Pointer { return false, fmt.Errorf("ptr must be a pointer to an object") } k := reflect.TypeOf(ptr).Elem().Kind() @@ -202,7 +202,7 @@ func (o *SubObject) RemoveNestedField(fields ...string) (bool, error) { } // onLockedFields locks the SubObject fields which are expected for kpt internal use only. -func (o *SubObject) onLockedFields(val interface{}, fields ...string) error { +func (o *SubObject) onLockedFields(val any, fields ...string) error { if o.hasUpstreamIdentifier(val, fields...) { return ErrAttemptToTouchUpstreamIdentifier{} } @@ -212,7 +212,7 @@ func (o *SubObject) onLockedFields(val interface{}, fields ...string) error { // SetNestedField sets a nested field located by fields to the value provided as val. val // should not be a yaml.RNode. If you want to deal with yaml.RNode, you should // use Get method and modify the underlying yaml.Node. -func (o *SubObject) SetNestedField(val interface{}, fields ...string) error { +func (o *SubObject) SetNestedField(val any, fields ...string) error { if err := o.onLockedFields(val, fields...); err != nil { return err } @@ -227,7 +227,7 @@ func (o *SubObject) SetNestedField(val interface{}, fields ...string) error { o.obj = internal.NewMap(nil) } kind := reflect.ValueOf(val).Kind() - if kind == reflect.Ptr { + if kind == reflect.Pointer { kind = reflect.TypeOf(val).Elem().Kind() } @@ -377,12 +377,12 @@ func (o *KubeObject) SetHeadComment(comment string, fields ...string) error { // As converts a KubeObject to the desired typed object. ptr must be // a pointer to a typed object. -func (o *SubObject) As(ptr interface{}) error { +func (o *SubObject) As(ptr any) error { err := func() error { if o == nil { return fmt.Errorf("the object doesn't exist") } - if ptr == nil || reflect.ValueOf(ptr).Kind() != reflect.Ptr { + if ptr == nil || reflect.ValueOf(ptr).Kind() != reflect.Pointer { return fmt.Errorf("ptr must be a pointer to an object") } return internal.MapVariantToTypedObject(o.obj, ptr) @@ -394,9 +394,9 @@ func (o *SubObject) As(ptr interface{}) error { } // NewFromTypedObject construct a KubeObject from a typed object (e.g. corev1.Pod) -func NewFromTypedObject(v interface{}) (*KubeObject, error) { +func NewFromTypedObject(v any) (*KubeObject, error) { kind := reflect.ValueOf(v).Kind() - if kind == reflect.Ptr { + if kind == reflect.Pointer { kind = reflect.TypeOf(v).Elem().Kind() } var err error @@ -755,7 +755,7 @@ func rnodeToKubeObject(rn *yaml.RNode) *KubeObject { return asKubeObject(mapVariant) } -func NewKubeObjectFromMap(m map[string]interface{}) (*KubeObject, error) { +func NewKubeObjectFromMap(m map[string]any) (*KubeObject, error) { rn, err := yaml.FromMap(m) if err != nil { return nil, fmt.Errorf("couldn't convert unstructured/JSON map to KubeObject: %w", err) @@ -834,7 +834,7 @@ func (o *SubObject) Set(newValue *SubObject) error { // `newValue` must be of type struct or map[string]... func (o *SubObject) SetFromTypedObject(newValue any) error { kind := reflect.ValueOf(newValue).Kind() - if kind == reflect.Ptr { + if kind == reflect.Pointer { kind = reflect.TypeOf(newValue).Elem().Kind() } if kind != reflect.Struct && kind != reflect.Map { diff --git a/go/fn/object_io.go b/go/fn/object_io.go index d583484b4..303daa52b 100644 --- a/go/fn/object_io.go +++ b/go/fn/object_io.go @@ -1,4 +1,4 @@ -// Copyright 2024 The kpt and Nephio Authors +// Copyright 2024, 2026 The kpt and Nephio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,8 +23,8 @@ import ( "path/filepath" "strings" - kptfilev1 "github.com/kptdev/krm-functions-sdk/go/fn/kptfileapi" "github.com/kptdev/krm-functions-sdk/go/fn/internal" + kptfilev1 "github.com/kptdev/krm-functions-sdk/go/fn/kptfileapi" "sigs.k8s.io/kustomize/kyaml/kio" "sigs.k8s.io/kustomize/kyaml/kio/kioutil" "sigs.k8s.io/kustomize/kyaml/yaml" diff --git a/go/fn/object_test.go b/go/fn/object_test.go index 7c84228b5..804114f3c 100644 --- a/go/fn/object_test.go +++ b/go/fn/object_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt Authors +// Copyright 2022, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -186,7 +186,6 @@ spec: } for testName, data := range testCases { - data := data t.Run(testName+"/resource", func(t *testing.T) { assert.Equal(t, resource.IsGVK(data.group, data.version, data.kind), data.expect) }) diff --git a/go/fn/origin.go b/go/fn/origin.go index f9464cc47..ba0c82551 100644 --- a/go/fn/origin.go +++ b/go/fn/origin.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt Authors +// Copyright 2022, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -48,9 +48,9 @@ func (r *ResourceIdentifier) String() string { } // hasUpstreamIdentifier determines whether the args are touching the kpt only annotation "internal.kpt.dev/upstream-identifier" -func (o *SubObject) hasUpstreamIdentifier(val interface{}, fields ...string) bool { +func (o *SubObject) hasUpstreamIdentifier(val any, fields ...string) bool { kind := reflect.ValueOf(val).Kind() - if kind == reflect.Ptr { + if kind == reflect.Pointer { kind = reflect.TypeOf(val).Elem().Kind() } switch kind { @@ -132,8 +132,8 @@ func (o *KubeObject) HasUpstreamOrigin() bool { // ParseGroupVersion parses a "apiVersion" to get the "group" and "version" values. func ParseGroupVersion(apiVersion string) (group, version string) { - if i := strings.Index(apiVersion, "/"); i > -1 { - return apiVersion[:i], apiVersion[i+1:] + if before, after, ok := strings.Cut(apiVersion, "/"); ok { + return before, after } return "", apiVersion } diff --git a/go/fn/resourcelist.go b/go/fn/resourcelist.go index b89f3f817..016d98929 100644 --- a/go/fn/resourcelist.go +++ b/go/fn/resourcelist.go @@ -202,7 +202,7 @@ func (rl *ResourceList) Sort() { // UpsertObjectToItems adds an object to ResourceList.items. The input object can // be a KubeObject or any typed object (e.g. corev1.Pod). -func (rl *ResourceList) UpsertObjectToItems(obj interface{}, checkExistence func(obj, another *KubeObject) bool, replaceIfAlreadyExist bool) error { +func (rl *ResourceList) UpsertObjectToItems(obj any, checkExistence func(obj, another *KubeObject) bool, replaceIfAlreadyExist bool) error { if checkExistence == nil { checkExistence = func(obj, another *KubeObject) bool { ri1 := obj.resourceIdentifier() diff --git a/go/fn/result.go b/go/fn/result.go index cb9dd3909..e04d87eb4 100644 --- a/go/fn/result.go +++ b/go/fn/result.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt Authors +// Copyright 2022, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -91,7 +91,7 @@ func (i Result) String() string { if i.Severity == "" { severity = Info } - list := []interface{}{severity} + list := []any{severity} if len(idStringList) > 0 { formatString += " %s" list = append(list, strings.Join(idStringList, "/")) @@ -122,10 +122,10 @@ type Field struct { Path string `yaml:"path,omitempty" json:"path,omitempty"` // CurrentValue is the current field value - CurrentValue interface{} `yaml:"currentValue,omitempty" json:"currentValue,omitempty"` + CurrentValue any `yaml:"currentValue,omitempty" json:"currentValue,omitempty"` // ProposedValue is the proposed value of the field to fix an issue. - ProposedValue interface{} `yaml:"proposedValue,omitempty" json:"proposedValue,omitempty"` + ProposedValue any `yaml:"proposedValue,omitempty" json:"proposedValue,omitempty"` } type Results []*Result diff --git a/go/fn/run.go b/go/fn/run.go index f23d11487..d404114d1 100644 --- a/go/fn/run.go +++ b/go/fn/run.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt Authors +// Copyright 2022, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,16 +18,72 @@ import ( "fmt" "io" "os" + "slices" + "strings" + "github.com/kptdev/krm-functions-sdk/go/fn/internal/docs" "sigs.k8s.io/kustomize/kyaml/errors" "sigs.k8s.io/kustomize/kyaml/kio" ) -// AsMain evaluates the ResourceList from STDIN to STDOUT. +// Option configures fn.AsMain behavior. +type Option func(*mainConfig) + +// mainConfig holds configuration gathered from Options. +type mainConfig struct { + readme []byte // raw embedded README.md content + metadata []byte // raw embedded metadata.yaml content +} + +// WithDocs registers embedded README and metadata content for --help and --doc. +func WithDocs(readme []byte, meta []byte) Option { + return func(c *mainConfig) { + c.readme = readme + c.metadata = meta + } +} + +// AsMain evaluates a KRM function. By default it reads a ResourceList from +// STDIN, processes it, and writes the result to STDOUT. +// // `input` can be // - a `ResourceListProcessor` which implements `Process` method // - a function `Runner` which implements `Run` method -func AsMain(input interface{}) error { +// +// Invocation modes (checked in this order): +// - --help: prints human-readable documentation to STDOUT and returns nil. +// - --doc: prints machine-readable JSON documentation to STDOUT and returns nil. +// - positional file args: reads KRM resources from files instead of STDIN. +// - no args: reads ResourceList from STDIN (default behavior). +// +// Options configure additional behavior such as documentation support +// via WithDocs. Existing callers with no options continue to work unchanged. +func AsMain(input any, opts ...Option) error { + // Apply options to build configuration. + var cfg mainConfig + for _, opt := range opts { + opt(&cfg) + } + + // Check for --help and --doc flags before reading STDIN. + // --help always takes precedence over --doc regardless of argument order. + if slices.Contains(os.Args[1:], "--help") { + return handleHelp(&cfg) + } + if slices.Contains(os.Args[1:], "--doc") { + return handleDoc(&cfg) + } + + // Collect non-flag positional arguments (file paths). + // Skip any argument that looks like a flag (starts with "-" or "--"). + var filePaths []string + for _, arg := range os.Args[1:] { + if strings.HasPrefix(arg, "-") { + continue + } + filePaths = append(filePaths, arg) + } + err := func() error { var p ResourceListProcessor switch input := input.(type) { @@ -38,6 +94,31 @@ func AsMain(input interface{}) error { default: return fmt.Errorf("unknown input type %T", input) } + + // If file paths are provided, use file mode instead of STDIN. + if len(filePaths) > 0 { + rl, err := readFilesAsResourceList(filePaths) + if err != nil { + return err + } + success, fnErr := p.Process(rl) + out, yamlErr := rl.ToYAML() + if yamlErr != nil { + return yamlErr + } + _, outErr := os.Stdout.Write(out) + if outErr != nil { + return outErr + } + if fnErr != nil { + return fnErr + } + if !success { + return fmt.Errorf("error: function failure") + } + return nil + } + in, err := io.ReadAll(os.Stdin) if err != nil { return fmt.Errorf("unable to read from stdin: %v", err) @@ -57,6 +138,73 @@ func AsMain(input interface{}) error { return err } +// handleHelp renders help text to STDOUT based on registered docs. +func handleHelp(cfg *mainConfig) error { + if cfg.readme == nil && cfg.metadata == nil { + fmt.Fprint(os.Stdout, "No documentation available. Pass fn.WithDocs to fn.AsMain to enable --help.\n") + return nil + } + + sections := docs.ParseMarkers(cfg.readme) + meta, err := docs.ParseMetadata(cfg.metadata) + if err != nil { + Logf("warning: invalid metadata YAML: %v", err) + meta = docs.Metadata{} + } + + docs.RenderHelp(os.Stdout, sections, meta) + return nil +} + +// handleDoc renders JSON documentation to STDOUT based on registered docs. +func handleDoc(cfg *mainConfig) error { + if cfg.readme == nil && cfg.metadata == nil { + fmt.Fprint(os.Stdout, "{}") + return nil + } + + sections := docs.ParseMarkers(cfg.readme) + meta, err := docs.ParseMetadata(cfg.metadata) + if err != nil { + Logf("warning: invalid metadata YAML: %v", err) + meta = docs.Metadata{} + } + + return docs.RenderDoc(os.Stdout, sections, meta) +} + +// readFilesAsResourceList reads KRM YAML from the given file paths, +// assembles them into a ResourceList with an empty FunctionConfig. +// Each file is parsed as one or more KRM YAML documents (separated by ---). +// Empty files are valid (no items added). Returns a descriptive error if a +// file does not exist or contains invalid YAML. +func readFilesAsResourceList(paths []string) (*ResourceList, error) { + rl := &ResourceList{ + FunctionConfig: NewEmptyKubeObject(), + } + for _, path := range paths { + data, err := os.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + return nil, fmt.Errorf("file not found: %s", path) + } + return nil, fmt.Errorf("failed to read file %s: %v", path, err) + } + // Empty files are valid — proceed with no items from this file. + if len(strings.TrimSpace(string(data))) == 0 { + continue + } + objects, err := ParseKubeObjects(data) + if err != nil { + return nil, fmt.Errorf("failed to parse KRM resources from %s: %v", path, err) + } + for _, obj := range objects { + rl.Items = append(rl.Items, obj) + } + } + return rl, nil +} + // Run evaluates the function. input must be a resourceList in yaml format. An // updated resourceList will be returned. func Run(p ResourceListProcessor, input []byte) ([]byte, error) { diff --git a/go/fn/run_filemode_property_test.go b/go/fn/run_filemode_property_test.go new file mode 100644 index 000000000..ff1386b83 --- /dev/null +++ b/go/fn/run_filemode_property_test.go @@ -0,0 +1,173 @@ +// Copyright 2026 The kpt Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fn + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "pgregory.net/rapid" +) + +// Feature: sdk-alignment, Property 6: File mode equivalence +// +// For any valid set of KRM YAML resources, processing them via standalone file +// mode SHALL produce identical output to processing the same resources assembled +// into a ResourceList via STDIN mode. +// +// Validates: Requirements 4.4 + +// genKRMResource generates a valid KRM YAML resource (a ConfigMap) with random +// name and namespace. ConfigMaps are used because they are simple, always valid +// KRM resources that don't require complex schemas. +func genKRMResource() *rapid.Generator[string] { + return rapid.Custom(func(t *rapid.T) string { + name := rapid.StringMatching(`[a-z][a-z0-9]{2,12}`).Draw(t, "name") + namespace := rapid.StringMatching(`[a-z][a-z0-9]{2,8}`).Draw(t, "namespace") + // Generate 1-3 data entries with YAML-safe values (alphanumeric only, + // no special characters that could be misinterpreted by the YAML parser). + numEntries := rapid.IntRange(1, 3).Draw(t, "numEntries") + var dataLines strings.Builder + for i := range numEntries { + key := rapid.StringMatching(`[a-z][a-z0-9]{1,8}`).Draw(t, fmt.Sprintf("key%d", i)) + value := rapid.StringMatching(`[a-zA-Z0-9]{1,15}`).Draw(t, fmt.Sprintf("value%d", i)) + dataLines.WriteString(fmt.Sprintf(" %s: %s\n", key, value)) + } + return fmt.Sprintf(`apiVersion: v1 +kind: ConfigMap +metadata: + name: %s + namespace: %s +data: +%s`, name, namespace, dataLines.String()) + }) +} + +// genKRMResourceList generates a slice of 1-5 valid KRM YAML resource strings. +func genKRMResourceList() *rapid.Generator[[]string] { + return rapid.SliceOfN(genKRMResource(), 1, 5) +} + +func TestProperty6_FileModeEquivalence(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + resources := genKRMResourceList().Draw(t, "resources") + + // --- File mode path --- + // Write each resource to a temp file. + tmpDir, err := os.MkdirTemp("", "property6-*") + if err != nil { + t.Fatalf("failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + var filePaths []string + for i, res := range resources { + path := filepath.Join(tmpDir, fmt.Sprintf("resource-%d.yaml", i)) + if err := os.WriteFile(path, []byte(res), 0600); err != nil { + t.Fatalf("failed to write temp file: %v", err) + } + filePaths = append(filePaths, path) + } + + // Process via file mode: readFilesAsResourceList → Process → ToYAML. + // Use a no-op processor that passes items through unchanged. + noopProc := ResourceListProcessorFunc(func(rl *ResourceList) (bool, error) { + return true, nil + }) + + fileRL, err := readFilesAsResourceList(filePaths) + if err != nil { + t.Fatalf("readFilesAsResourceList failed: %v", err) + } + _, fnErr := noopProc.Process(fileRL) + if fnErr != nil { + t.Fatalf("file mode Process failed: %v", fnErr) + } + fileOutput, err := fileRL.ToYAML() + if err != nil { + t.Fatalf("file mode ToYAML failed: %v", err) + } + + // --- STDIN mode path --- + // Assemble the same resources into a ResourceList YAML (as STDIN would provide). + var stdinInput strings.Builder + stdinInput.WriteString("apiVersion: config.kubernetes.io/v1\nkind: ResourceList\nitems:\n") + for _, res := range resources { + // Indent each resource line under items as a YAML list element. + stdinInput.WriteString("- ") + first := true + for _, line := range splitLines(res) { + if first { + stdinInput.WriteString(line + "\n") + first = false + } else { + stdinInput.WriteString(" " + line + "\n") + } + } + } + + stdinOutput, err := Run(noopProc, []byte(stdinInput.String())) + if err != nil { + t.Fatalf("STDIN mode Run failed: %v\n Input:\n%s", err, stdinInput.String()) + } + + // --- Compare outputs --- + // Parse both outputs as ResourceLists and compare items. + fileResultRL, err := ParseResourceList(fileOutput) + if err != nil { + t.Fatalf("failed to parse file mode output: %v\n Output:\n%s", err, string(fileOutput)) + } + stdinResultRL, err := ParseResourceList(stdinOutput) + if err != nil { + t.Fatalf("failed to parse STDIN mode output: %v\n Output:\n%s", err, string(stdinOutput)) + } + + // Compare item counts. + if len(fileResultRL.Items) != len(stdinResultRL.Items) { + t.Fatalf("item count mismatch: file mode has %d items, STDIN mode has %d items", + len(fileResultRL.Items), len(stdinResultRL.Items)) + } + + // Compare each item by its string representation (after sorting, which + // ToYAML does automatically). + for i := range fileResultRL.Items { + fileItem := fileResultRL.Items[i].String() + stdinItem := stdinResultRL.Items[i].String() + if fileItem != stdinItem { + t.Fatalf("item %d mismatch:\n File mode:\n%s\n STDIN mode:\n%s", + i, fileItem, stdinItem) + } + } + }) +} + +// splitLines splits a string into lines, preserving empty lines. +func splitLines(s string) []string { + var lines []string + start := 0 + for i := 0; i < len(s); i++ { + if s[i] == '\n' { + lines = append(lines, s[start:i]) + start = i + 1 + } + } + if start < len(s) { + lines = append(lines, s[start:]) + } + return lines +} diff --git a/go/fn/run_filemode_test.go b/go/fn/run_filemode_test.go new file mode 100644 index 000000000..cf41384df --- /dev/null +++ b/go/fn/run_filemode_test.go @@ -0,0 +1,427 @@ +// Copyright 2026 The kpt Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fn + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestFileMode_ValidInput verifies that valid input files produce correct output +// on STDOUT when processed via file mode. +// Requirements: 4.1, 4.2 +func TestFileMode_ValidInput(t *testing.T) { + tmpDir := t.TempDir() + + // Write a valid ConfigMap resource to a temp file. + configMap := `apiVersion: v1 +kind: ConfigMap +metadata: + name: my-config + namespace: default +data: + key1: value1 +` + filePath := filepath.Join(tmpDir, "configmap.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(configMap), 0600)) + + // Set os.Args to simulate file mode invocation. + setArgs(t, []string{"cmd", filePath}) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor) + assert.NoError(t, err, "file mode with valid input should succeed") + }) + + // Verify output is a valid ResourceList containing the ConfigMap. + assert.NotEmpty(t, output, "file mode should produce output on STDOUT") + assert.Contains(t, output, "kind: ResourceList") + assert.Contains(t, output, "my-config") + assert.Contains(t, output, "key1: value1") +} + +// TestFileMode_MultipleFiles verifies that multiple valid input files are +// combined into a single ResourceList output. +// Requirements: 4.1, 4.2 +func TestFileMode_MultipleFiles(t *testing.T) { + tmpDir := t.TempDir() + + cm1 := `apiVersion: v1 +kind: ConfigMap +metadata: + name: config-one + namespace: default +data: + foo: bar +` + cm2 := `apiVersion: v1 +kind: ConfigMap +metadata: + name: config-two + namespace: default +data: + baz: qux +` + file1 := filepath.Join(tmpDir, "cm1.yaml") + file2 := filepath.Join(tmpDir, "cm2.yaml") + require.NoError(t, os.WriteFile(file1, []byte(cm1), 0600)) + require.NoError(t, os.WriteFile(file2, []byte(cm2), 0600)) + + setArgs(t, []string{"cmd", file1, file2}) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor) + assert.NoError(t, err, "file mode with multiple files should succeed") + }) + + // Both resources should appear in the output. + assert.Contains(t, output, "config-one") + assert.Contains(t, output, "config-two") + assert.Contains(t, output, "foo: bar") + assert.Contains(t, output, "baz: qux") +} + +// TestFileMode_NonExistentFile verifies that a non-existent file returns a +// descriptive error message including the file path. +// Requirements: 4.3 +func TestFileMode_NonExistentFile(t *testing.T) { + nonExistentPath := filepath.Join(t.TempDir(), "does-not-exist.yaml") + + setArgs(t, []string{"cmd", nonExistentPath}) + + err := AsMain(noopProcessor) + require.Error(t, err, "non-existent file should return an error") + assert.Contains(t, err.Error(), "file not found") + assert.Contains(t, err.Error(), nonExistentPath, "error should include the file path") +} + +// TestFileMode_InvalidYAML verifies that a file with invalid YAML returns a +// parse error. +// Requirements: 4.3 +func TestFileMode_InvalidYAML(t *testing.T) { + tmpDir := t.TempDir() + + invalidYAML := `{{{this is not valid YAML at all!!!` + filePath := filepath.Join(tmpDir, "invalid.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(invalidYAML), 0600)) + + setArgs(t, []string{"cmd", filePath}) + + err := AsMain(noopProcessor) + require.Error(t, err, "invalid YAML file should return an error") + assert.Contains(t, err.Error(), filePath, "error should include the file path") + assert.Contains(t, err.Error(), "failed to parse KRM resources from") +} + +// TestFileMode_EmptyFile verifies that an empty file proceeds without error +// (valid for generators that don't require input items). +// Requirements: 4.1 +func TestFileMode_EmptyFile(t *testing.T) { + tmpDir := t.TempDir() + + // Write an empty file. + filePath := filepath.Join(tmpDir, "empty.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(""), 0600)) + + setArgs(t, []string{"cmd", filePath}) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor) + assert.NoError(t, err, "empty file should proceed without error") + }) + + // Output should be a valid ResourceList (possibly with no items). + assert.NotEmpty(t, output, "file mode should still produce output") + assert.Contains(t, output, "kind: ResourceList") +} + +// TestFileMode_WhitespaceOnlyFile verifies that a file containing only +// whitespace is treated as empty and proceeds without error. +// Requirements: 4.1 +func TestFileMode_WhitespaceOnlyFile(t *testing.T) { + tmpDir := t.TempDir() + + filePath := filepath.Join(tmpDir, "whitespace.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(" \n\n \t \n"), 0600)) + + setArgs(t, []string{"cmd", filePath}) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor) + assert.NoError(t, err, "whitespace-only file should proceed without error") + }) + + assert.Contains(t, output, "kind: ResourceList") +} + +// TestFileMode_OutputToStdout verifies that file mode output goes to STDOUT +// (not STDERR or elsewhere). +// Requirements: 4.2 +func TestFileMode_OutputToStdout(t *testing.T) { + tmpDir := t.TempDir() + + configMap := `apiVersion: v1 +kind: ConfigMap +metadata: + name: stdout-test + namespace: test +data: + hello: world +` + filePath := filepath.Join(tmpDir, "resource.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(configMap), 0600)) + + setArgs(t, []string{"cmd", filePath}) + + // Capture both stdout and stderr to verify output goes to stdout only. + var stdoutOutput string + stderrOutput := captureStderr(t, func() { + stdoutOutput = captureStdout(t, func() { + err := AsMain(noopProcessor) + assert.NoError(t, err) + }) + }) + + // STDOUT should have the ResourceList output. + assert.Contains(t, stdoutOutput, "stdout-test") + assert.Contains(t, stdoutOutput, "kind: ResourceList") + + // STDERR should not contain the resource output. + assert.NotContains(t, stderrOutput, "stdout-test") +} + +// TestReadFilesAsResourceList_ValidFile tests the readFilesAsResourceList helper +// directly with a valid file. +func TestReadFilesAsResourceList_ValidFile(t *testing.T) { + tmpDir := t.TempDir() + + configMap := `apiVersion: v1 +kind: ConfigMap +metadata: + name: direct-test + namespace: default +data: + key: value +` + filePath := filepath.Join(tmpDir, "cm.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(configMap), 0600)) + + rl, err := readFilesAsResourceList([]string{filePath}) + require.NoError(t, err) + require.NotNil(t, rl) + + // Should have one item. + assert.Len(t, rl.Items, 1) + assert.Equal(t, "direct-test", rl.Items[0].GetName()) + assert.Equal(t, "ConfigMap", rl.Items[0].GetKind()) + + // FunctionConfig should be set (empty KubeObject). + assert.NotNil(t, rl.FunctionConfig) +} + +// TestReadFilesAsResourceList_NonExistentFile tests the readFilesAsResourceList +// helper directly with a non-existent file. +func TestReadFilesAsResourceList_NonExistentFile(t *testing.T) { + nonExistentPath := "/tmp/definitely-does-not-exist-12345.yaml" + + rl, err := readFilesAsResourceList([]string{nonExistentPath}) + require.Error(t, err) + assert.Nil(t, rl) + assert.Contains(t, err.Error(), "file not found") + assert.Contains(t, err.Error(), nonExistentPath) +} + +// TestReadFilesAsResourceList_InvalidYAML tests the readFilesAsResourceList +// helper directly with invalid YAML content. +func TestReadFilesAsResourceList_InvalidYAML(t *testing.T) { + tmpDir := t.TempDir() + + filePath := filepath.Join(tmpDir, "bad.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(`{{{not yaml`), 0600)) + + rl, err := readFilesAsResourceList([]string{filePath}) + require.Error(t, err) + assert.Nil(t, rl) + assert.Contains(t, err.Error(), "failed to parse KRM resources from") + assert.Contains(t, err.Error(), filePath) +} + +// TestReadFilesAsResourceList_EmptyFile tests the readFilesAsResourceList +// helper directly with an empty file. +func TestReadFilesAsResourceList_EmptyFile(t *testing.T) { + tmpDir := t.TempDir() + + filePath := filepath.Join(tmpDir, "empty.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(""), 0600)) + + rl, err := readFilesAsResourceList([]string{filePath}) + require.NoError(t, err) + require.NotNil(t, rl) + + // Empty file should result in no items. + assert.Empty(t, rl.Items) + // FunctionConfig should still be set. + assert.NotNil(t, rl.FunctionConfig) +} + +// TestReadFilesAsResourceList_MultiDocument tests that a file with multiple +// YAML documents (separated by ---) produces multiple items. +func TestReadFilesAsResourceList_MultiDocument(t *testing.T) { + tmpDir := t.TempDir() + + multiDoc := `apiVersion: v1 +kind: ConfigMap +metadata: + name: first + namespace: default +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: second + namespace: default +` + filePath := filepath.Join(tmpDir, "multi.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(multiDoc), 0600)) + + rl, err := readFilesAsResourceList([]string{filePath}) + require.NoError(t, err) + require.NotNil(t, rl) + + assert.Len(t, rl.Items, 2) + + // Verify both items are present (order may vary). + names := []string{rl.Items[0].GetName(), rl.Items[1].GetName()} + assert.Contains(t, names, "first") + assert.Contains(t, names, "second") +} + +// TestFileMode_ProcessorReceivesItems verifies that the processor actually +// receives the items from the file and can modify them. +// Requirements: 4.1, 4.2 +func TestFileMode_ProcessorReceivesItems(t *testing.T) { + tmpDir := t.TempDir() + + configMap := `apiVersion: v1 +kind: ConfigMap +metadata: + name: to-be-labeled + namespace: default +` + filePath := filepath.Join(tmpDir, "cm.yaml") + require.NoError(t, os.WriteFile(filePath, []byte(configMap), 0600)) + + // Use a processor that adds a label to all items. + labelProc := ResourceListProcessorFunc(func(rl *ResourceList) (bool, error) { + for _, item := range rl.Items { + if err := item.SetLabel("added-by", "test"); err != nil { + return false, err + } + } + return true, nil + }) + + setArgs(t, []string{"cmd", filePath}) + + output := captureStdout(t, func() { + err := AsMain(labelProc) + assert.NoError(t, err) + }) + + // Verify the label was added in the output. + assert.Contains(t, output, "added-by") + assert.Contains(t, output, "test") +} + +// TestFileMode_HelpTakesPrecedence verifies that --help takes precedence over +// file paths when both are present. +func TestFileMode_HelpTakesPrecedence(t *testing.T) { + tmpDir := t.TempDir() + + filePath := filepath.Join(tmpDir, "cm.yaml") + require.NoError(t, os.WriteFile(filePath, []byte("apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: test\n"), 0600)) + + setArgs(t, []string{"cmd", "--help", filePath}) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor) + assert.NoError(t, err) + }) + + // Should show help, not process the file. + assert.Contains(t, output, "No documentation available") + assert.NotContains(t, output, "kind: ResourceList") +} + +// TestFileMode_MixedValidAndEmpty verifies that a mix of valid and empty files +// works correctly — only the valid file contributes items. +func TestFileMode_MixedValidAndEmpty(t *testing.T) { + tmpDir := t.TempDir() + + configMap := `apiVersion: v1 +kind: ConfigMap +metadata: + name: only-item + namespace: default +` + validFile := filepath.Join(tmpDir, "valid.yaml") + emptyFile := filepath.Join(tmpDir, "empty.yaml") + require.NoError(t, os.WriteFile(validFile, []byte(configMap), 0600)) + require.NoError(t, os.WriteFile(emptyFile, []byte(""), 0600)) + + setArgs(t, []string{"cmd", emptyFile, validFile}) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor) + assert.NoError(t, err) + }) + + // Should contain the item from the valid file. + assert.Contains(t, output, "only-item") + // Should still be a valid ResourceList. + assert.Contains(t, output, "kind: ResourceList") + + // Verify we can parse the output. + rl, err := ParseResourceList([]byte(output)) + require.NoError(t, err) + assert.Len(t, rl.Items, 1) + assert.Equal(t, "only-item", rl.Items[0].GetName()) +} + +// TestFileMode_NonExistentAmongValid verifies that if one file in a list +// doesn't exist, the error is returned even if other files are valid. +// Requirements: 4.3 +func TestFileMode_NonExistentAmongValid(t *testing.T) { + tmpDir := t.TempDir() + + validFile := filepath.Join(tmpDir, "valid.yaml") + require.NoError(t, os.WriteFile(validFile, []byte("apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: test\n"), 0600)) + + nonExistent := filepath.Join(tmpDir, "missing.yaml") + + setArgs(t, []string{"cmd", validFile, nonExistent}) + + // Capture stderr to suppress the error log from AsMain. + captureStderr(t, func() { + err := AsMain(noopProcessor) + require.Error(t, err) + assert.Contains(t, err.Error(), "file not found") + assert.Contains(t, err.Error(), nonExistent) + }) +} diff --git a/go/fn/run_flags_test.go b/go/fn/run_flags_test.go new file mode 100644 index 000000000..7339d30b9 --- /dev/null +++ b/go/fn/run_flags_test.go @@ -0,0 +1,312 @@ +// Copyright 2026 The kpt Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fn + +import ( + "bytes" + "encoding/json" + "io" + "os" + "strings" + "testing" + + "github.com/kptdev/krm-functions-sdk/go/fn/internal/docs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// noopProcessor is a minimal ResourceListProcessor that does nothing. +// Used to satisfy AsMain's input requirement without triggering STDIN reads. +var noopProcessor = ResourceListProcessorFunc(func(rl *ResourceList) (bool, error) { + return true, nil +}) + +// captureStdout redirects os.Stdout to a pipe, runs fn, and returns what was written. +func captureStdout(t *testing.T, fn func()) string { + t.Helper() + + origStdout := os.Stdout + r, w, err := os.Pipe() + require.NoError(t, err) + os.Stdout = w + + fn() + + w.Close() + os.Stdout = origStdout + + var buf bytes.Buffer + _, err = io.Copy(&buf, r) + require.NoError(t, err) + r.Close() + + return buf.String() +} + +// captureStderr redirects os.Stderr to a pipe, runs fn, and returns what was written. +func captureStderr(t *testing.T, fn func()) string { + t.Helper() + + origStderr := os.Stderr + r, w, err := os.Pipe() + require.NoError(t, err) + os.Stderr = w + + fn() + + w.Close() + os.Stderr = origStderr + + var buf bytes.Buffer + _, err = io.Copy(&buf, r) + require.NoError(t, err) + r.Close() + + return buf.String() +} + +// setArgs temporarily sets os.Args for the duration of a test. +func setArgs(t *testing.T, args []string) { + t.Helper() + origArgs := os.Args + os.Args = args + t.Cleanup(func() { os.Args = origArgs }) +} + +// TestAsMain_HelpFlag_ExitsZero verifies that --help returns nil (exit 0) +// without reading STDIN. +// Requirements: 2.1 +func TestAsMain_HelpFlag_ExitsZero(t *testing.T) { + setArgs(t, []string{"cmd", "--help"}) + + // Close stdin to prove it's not read — if AsMain tries to read STDIN, + // it would get an error or EOF immediately. + origStdin := os.Stdin + r, w, err := os.Pipe() + require.NoError(t, err) + w.Close() // Close write end immediately — reading would get EOF + os.Stdin = r + t.Cleanup(func() { + os.Stdin = origStdin + r.Close() + }) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor, WithDocs([]byte("some readme"), []byte("image: test"))) + assert.NoError(t, err, "--help should return nil (exit 0)") + }) + + // Should have produced some output + assert.NotEmpty(t, output, "--help should produce output") +} + +// TestAsMain_HelpFlag_NoDocs verifies that --help with no WithDocs prints +// a minimal message. +// Requirements: 2.3 +func TestAsMain_HelpFlag_NoDocs(t *testing.T) { + setArgs(t, []string{"cmd", "--help"}) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor) + assert.NoError(t, err, "--help with no docs should return nil") + }) + + assert.Contains(t, output, "No documentation available") + assert.Contains(t, output, "fn.WithDocs") +} + +// TestAsMain_HelpFlag_WithDocs verifies that --help with WithDocs renders +// the README sections. +// Requirements: 2.2 +func TestAsMain_HelpFlag_WithDocs(t *testing.T) { + setArgs(t, []string{"cmd", "--help"}) + + readme := []byte(` +Set labels on resources + + + +The set-labels function adds labels to all resources. + + + + kpt fn eval --image set-labels:v0.1 + +`) + meta := []byte(`image: ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1 +description: Set labels on all resources +`) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor, WithDocs(readme, meta)) + assert.NoError(t, err) + }) + + assert.Contains(t, output, "Set labels on resources") + assert.Contains(t, output, "set-labels function adds labels") + assert.Contains(t, output, "kpt fn eval") +} + +// TestAsMain_DocFlag_OutputsValidJSON verifies that --doc outputs valid JSON +// and returns nil (exit 0). +// Requirements: 3.1 +func TestAsMain_DocFlag_OutputsValidJSON(t *testing.T) { + setArgs(t, []string{"cmd", "--doc"}) + + readme := []byte(` +Set labels + + + +Long description here. + +`) + meta := []byte(`image: ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1 +description: Set labels on all resources +tags: + - mutator +license: Apache-2.0 +`) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor, WithDocs(readme, meta)) + assert.NoError(t, err, "--doc should return nil (exit 0)") + }) + + // Verify it's valid JSON + var docOutput docs.DocOutput + err := json.Unmarshal([]byte(output), &docOutput) + require.NoError(t, err, "--doc output should be valid JSON") + + // Verify fields are populated + assert.Equal(t, "Set labels", docOutput.Short) + assert.Equal(t, "Long description here.", docOutput.Long) + assert.Equal(t, "ghcr.io/kptdev/krm-functions-catalog/set-labels:v0.1", docOutput.Image) + assert.Equal(t, "Set labels on all resources", docOutput.Description) + assert.Equal(t, []string{"mutator"}, docOutput.Tags) + assert.Equal(t, "Apache-2.0", docOutput.License) +} + +// TestAsMain_DocFlag_NoDocs verifies that --doc with no WithDocs outputs `{}`. +// Requirements: 3.3 +func TestAsMain_DocFlag_NoDocs(t *testing.T) { + setArgs(t, []string{"cmd", "--doc"}) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor) + assert.NoError(t, err, "--doc with no docs should return nil") + }) + + assert.Equal(t, "{}", strings.TrimSpace(output)) +} + +// TestAsMain_DocFlag_HiddenField verifies that hidden:true in metadata +// propagates to the --doc JSON output. +// Requirements: 3.6 +func TestAsMain_DocFlag_HiddenField(t *testing.T) { + setArgs(t, []string{"cmd", "--doc"}) + + readme := []byte(` +Hidden function + +`) + meta := []byte(`image: ghcr.io/kptdev/krm-functions-catalog/hidden-fn:v0.1 +description: A hidden function +hidden: true +`) + + output := captureStdout(t, func() { + err := AsMain(noopProcessor, WithDocs(readme, meta)) + assert.NoError(t, err) + }) + + var docOutput docs.DocOutput + err := json.Unmarshal([]byte(output), &docOutput) + require.NoError(t, err, "--doc output should be valid JSON") + + assert.True(t, docOutput.Hidden, "hidden:true should propagate to JSON output") + assert.Equal(t, "ghcr.io/kptdev/krm-functions-catalog/hidden-fn:v0.1", docOutput.Image) +} + +// TestAsMain_DocFlag_InvalidMetadataYAML verifies that invalid metadata YAML +// logs a warning and continues with zero-value metadata (only README fields in output). +// Requirements: 5.5, 3.6 +func TestAsMain_DocFlag_InvalidMetadataYAML(t *testing.T) { + setArgs(t, []string{"cmd", "--doc"}) + + readme := []byte(` +My function + +`) + invalidMeta := []byte(`{{{not valid yaml at all!!!`) + + var stdoutOutput string + stderrOutput := captureStderr(t, func() { + stdoutOutput = captureStdout(t, func() { + err := AsMain(noopProcessor, WithDocs(readme, invalidMeta)) + assert.NoError(t, err, "invalid metadata should not cause AsMain to fail") + }) + }) + + // Verify warning was logged to stderr + assert.Contains(t, stderrOutput, "warning") + assert.Contains(t, stderrOutput, "invalid metadata YAML") + + // Verify JSON output still contains README fields + var docOutput docs.DocOutput + err := json.Unmarshal([]byte(stdoutOutput), &docOutput) + require.NoError(t, err, "--doc output should still be valid JSON") + + assert.Equal(t, "My function", docOutput.Short) + // Metadata fields should be zero-value + assert.Empty(t, docOutput.Image) + assert.Empty(t, docOutput.Description) + assert.Empty(t, docOutput.Tags) + assert.False(t, docOutput.Hidden) +} + +// TestAsMain_HelpFlag_InvalidMetadataYAML verifies that --help with invalid +// metadata YAML logs a warning and continues rendering help from README only. +// Requirements: 5.5 +func TestAsMain_HelpFlag_InvalidMetadataYAML(t *testing.T) { + setArgs(t, []string{"cmd", "--help"}) + + readme := []byte(` +My function short desc + + + +Detailed description of the function. + +`) + // Use YAML that actually fails to parse (unclosed flow mapping) + invalidMeta := []byte(`{{{not valid yaml at all!!!`) + + var stdoutOutput string + stderrOutput := captureStderr(t, func() { + stdoutOutput = captureStdout(t, func() { + err := AsMain(noopProcessor, WithDocs(readme, invalidMeta)) + assert.NoError(t, err, "invalid metadata should not cause --help to fail") + }) + }) + + // Verify warning was logged + assert.Contains(t, stderrOutput, "warning") + assert.Contains(t, stderrOutput, "invalid metadata YAML") + + // Verify help output still contains README sections + assert.Contains(t, stdoutOutput, "My function short desc") + assert.Contains(t, stdoutOutput, "Detailed description of the function") +} diff --git a/go/fn/runnerProcessor.go b/go/fn/runnerProcessor.go index 3c4c7fd1f..b28eb69f8 100644 --- a/go/fn/runnerProcessor.go +++ b/go/fn/runnerProcessor.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt Authors +// Copyright 2022, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -93,7 +93,7 @@ func (r *runnerProcessor) config(o *KubeObject) error { func asFnName(runner Runner) string { // Validate the fnRunner type to avoid panic. kind := reflect.ValueOf(runner).Kind() - if kind != reflect.Interface && kind != reflect.Ptr { + if kind != reflect.Interface && kind != reflect.Pointer { return "" } return reflect.ValueOf(runner).Elem().Type().Name() @@ -104,11 +104,11 @@ func assignCMDataToFn(runner Runner, data map[string]string) error { if obj.Kind() != reflect.Struct { return fmt.Errorf("the ConfigMap is not of a struct, got %v", obj.Kind().String()) } - stringMap := reflect.MapOf(reflect.TypeOf("string"), reflect.TypeOf("string")) - for i := 0; i < obj.NumField(); i++ { - if obj.Field(i).Kind() == reflect.Map && obj.Field(i).Type() == stringMap { - if obj.Field(i).CanSet() { - obj.Field(i).Set(reflect.ValueOf(data)) + stringMap := reflect.MapOf(reflect.TypeFor[string](), reflect.TypeFor[string]()) + for _, field := range obj.Fields() { + if field.Kind() == reflect.Map && field.Type() == stringMap { + if field.CanSet() { + field.Set(reflect.ValueOf(data)) } return nil } diff --git a/go/get-started/go.mod b/go/get-started/go.mod index 8ff23feb1..3c0eee65e 100644 --- a/go/get-started/go.mod +++ b/go/get-started/go.mod @@ -2,6 +2,10 @@ module github.com/kptdev/krm-functions-sdk/go/get-started go 1.26.2 +// NOTE: replace directive is for in-repo development only. +// External consumers should use: require github.com/kptdev/krm-functions-sdk/go/fn v1.x.x +replace github.com/kptdev/krm-functions-sdk/go/fn => ../fn + require github.com/kptdev/krm-functions-sdk/go/fn v1.0.0 require ( @@ -25,7 +29,6 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/kptdev/kpt v1.0.0-beta.61 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/xlab/treeprint v1.2.0 // indirect @@ -36,7 +39,6 @@ require ( k8s.io/apimachinery v0.34.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect - sigs.k8s.io/kustomize/api v0.20.1 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect - sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go/get-started/go.sum b/go/get-started/go.sum index e85d71aab..78135fd53 100644 --- a/go/get-started/go.sum +++ b/go/get-started/go.sum @@ -43,10 +43,6 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kptdev/kpt v1.0.0-beta.61 h1:uTMcRVmLnVTUUXS1PYoTbc0QEda8sL9J3ymU96RhMDk= -github.com/kptdev/kpt v1.0.0-beta.61/go.mod h1:EvyF48ocsuwB1XO+2KAwjqMX+qYVYbZ8h/HKufXRmcE= -github.com/kptdev/krm-functions-sdk/go/fn v1.0.0 h1:2xTAEw0/mWNnPNvBR7K3rvrnjmBMxVbtTyu2ZHJjQxo= -github.com/kptdev/krm-functions-sdk/go/fn v1.0.0/go.mod h1:GxUbq9hEUYUtl2rGyQfzxz++xV+dSRrHpRxsx5l0PvA= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -109,18 +105,16 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +pgregory.net/rapid v1.3.0 h1:vBvO0VSqti75J1jjYqpgPNBLKMd1+gxa9fYo7vk/Exc= +pgregory.net/rapid v1.3.0/go.mod h1:dPlE4OBBxgXPqkP79flB6sJL1dx5azpI7HQ9MY9Z7uk= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= -sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= diff --git a/go/get-started/main.go b/go/get-started/main.go index b772ec875..9efdc9f58 100644 --- a/go/get-started/main.go +++ b/go/get-started/main.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt Authors +// Copyright 2022, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,11 +16,18 @@ package main import ( "context" + _ "embed" "os" "github.com/kptdev/krm-functions-sdk/go/fn" ) +//go:embed README.md +var readme []byte + +//go:embed metadata.yaml +var metadata []byte + var _ fn.Runner = &YourFunction{} // TODO: Change to your functionConfig "Kind" name. @@ -41,7 +48,7 @@ func (r *YourFunction) Run(ctx *fn.Context, functionConfig *fn.KubeObject, items func main() { runner := fn.WithContext(context.Background(), &YourFunction{}) - if err := fn.AsMain(runner); err != nil { + if err := fn.AsMain(runner, fn.WithDocs(readme, metadata)); err != nil { os.Exit(1) } } diff --git a/go/get-started/metadata.yaml b/go/get-started/metadata.yaml new file mode 100644 index 000000000..ce0f22645 --- /dev/null +++ b/go/get-started/metadata.yaml @@ -0,0 +1,8 @@ +image: ghcr.io/kptdev/krm-functions-catalog/some-function-name:v0.1 +description: Explain what this function does in one or two sentences. +tags: + - mutator +sourceURL: https://github.com/kptdev/krm-functions-sdk/tree/main/go/get-started +examplePackageURLs: [] +license: Apache-2.0 +hidden: false diff --git a/go/get-started/testdata/test1/_expected.yaml b/go/get-started/testdata/noop-passthrough/_expected.yaml similarity index 100% rename from go/get-started/testdata/test1/_expected.yaml rename to go/get-started/testdata/noop-passthrough/_expected.yaml diff --git a/go/get-started/testdata/test1/_fnconfig.yaml b/go/get-started/testdata/noop-passthrough/_fnconfig.yaml similarity index 100% rename from go/get-started/testdata/test1/_fnconfig.yaml rename to go/get-started/testdata/noop-passthrough/_fnconfig.yaml diff --git a/go/get-started/testdata/test1/resources.yaml b/go/get-started/testdata/noop-passthrough/resources.yaml similarity index 100% rename from go/get-started/testdata/test1/resources.yaml rename to go/get-started/testdata/noop-passthrough/resources.yaml diff --git a/go/kfn/commands/build_test.go b/go/kfn/commands/build_test.go index 68f5d242a..2413895e0 100644 --- a/go/kfn/commands/build_test.go +++ b/go/kfn/commands/build_test.go @@ -1,4 +1,4 @@ -// Copyright 2022 The kpt Authors +// Copyright 2022, 2026 The kpt Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import ( "context" "fmt" "os" + "slices" "strings" "testing" @@ -150,10 +151,8 @@ func fakeExecCmd(t *testing.T, expectedArgsAndReturns []string, envs []string, n c = append(c, name) c = append(c, args...) command := strings.Join(c, " ") - for _, expected := range expectedArgsAndReturns { - if expected == command { - return - } + if slices.Contains(expectedArgsAndReturns, command) { + return } t.Fatalf("unexpected command run %v", command) }