From de8156b9090b923768894fb49a15e233ac1c9fab Mon Sep 17 00:00:00 2001 From: Michael Greaves Date: Thu, 12 Mar 2026 11:09:57 +0100 Subject: [PATCH 01/29] First batch of chapter 2 proofreading. Signed-off-by: Michael Greaves --- .../content/en/book/02-concepts/_index.md | 59 ++++++++----------- 1 file changed, 26 insertions(+), 33 deletions(-) diff --git a/documentation/content/en/book/02-concepts/_index.md b/documentation/content/en/book/02-concepts/_index.md index c102d2db17..0e2527a87c 100644 --- a/documentation/content/en/book/02-concepts/_index.md +++ b/documentation/content/en/book/02-concepts/_index.md @@ -1,7 +1,7 @@ --- title: "Chapter 2: Concepts" linkTitle: "Chapter 2: Concepts" -description: This describes what is kpt and what are the main concepts behind it +description: This chapter describes what kpt is and what the main concepts are behind kpt. toc: true menu: main: @@ -11,45 +11,38 @@ menu: ## What is kpt? -kpt (Kubernetes Package Transformation) supports management of Configuration as Data (CaD). +kpt stands for Kubernetes Package Transformation. It supports the management of configuration as data (CaD). -*Configuration as Data* is an approach to management of configuration (incl. configuration of infrastructure, policy, -services, applications, etc.) which: +*Configuration as data* is an approach to the management of configurations, including the configuration of infrastructure, policy, services, applications, and so on, which comprises the following actions: -* makes configuration data the source of truth, stored separately from the live - state -* uses a uniform, serializable data model to represent configuration -* separates code that acts on the configuration from the data and from packages / bundles of the data -* abstracts configuration file structure and storage from operations that act upon the configuration data; clients - manipulating configuration data don’t need to directly interact with storage (git, container images) +* Making configuration data the source of truth, stored separately from the live state. +* Using a uniform, serializable data model to represent the configuration. +* Separating code that acts on the configuration from the data and from the packages/bundles of the data. +* Abstracting the configuration file structure and storage from the operations that act upon the configuration data. Clients manipulating configuration data do not need to directly interact with the storage (git, container images). -This enables machine manipulation of configuration for Kubernetes and any infrastructure represented in the +This enables the machine manipulation of the configuration for Kubernetes and any infrastructure represented in the [Kubernetes Resource Model (KRM)](https://github.com/kubernetes/design-proposals-archive/blob/main/architecture/resource-management.md). ![img](/images/cad-overview.svg) -### Configuration as data key principles - -* secrets should be stored separately, in a secret-focused storage system -* stores a versioned history of configuration changes by change sets to bundles of related configuration data -* relies on uniformity and consistency of the configuration format, including type metadata, to enable pattern-based - operations on the configuration data, along the lines of duck typing -* separates schemas for the configuration data from the data, and relies on schema information for strongly typed - operations and to disambiguate data structures and other variations within the model -* decouples abstractions of configuration from collections of configuration data -* represents abstractions of configuration generators as data with schemas, like other configuration data -* finds, filters / queries / selects, and/or validates configuration data that can be operated on by given code - (functions) -* finds and/or filters / queries / selects code (functions) that can operate on resource types contained within a body - of configuration data -* actuation (reconciliation of configuration data with live state) is separate from transformation of configuration - data, and is driven by the declarative data model -* transformations, particularly value propagation, are preferable to wholesale configuration generation except when the - expansion is dramatic (say, >10x) -* transformation input generation should usually be decoupled from propagation -* deployment context inputs should be taken from well defined “provider context” objects -* identifiers and references should be declarative -* live state should be linked back to sources of truth (configuration) +### Key principles of configuration as data + +There are a number of key principles to be borne in mind, with regard to configuration as data (CaD). These principles are as follows: + +* Secrets should be stored separately, in a secret-focused storage system. +* Storage of a versioned history of the configuration changes by change sets to bundles of related configuration data. +* Reliance on uniformity and consistency of the configuration format, including type metadata, to enable pattern-based operations on the configuration data, along the lines of duck typing. +* Separation of schemas for the configuration data from the data, and reliance on schema information for strongly typed operations and to disambiguate data structures and other variations within the model. +* Decoupling of abstractions of configuration from collections of configuration data. +* Representation of abstractions of configuration generators as data with schemas, like other configuration data. +* Finding, filtering/querying/selecting, and/or validating configuration data that can be operated on by given code (functions). +* Finding and/or filtering/querying/selecting code (functions) that can operate on the resource types contained within a body of configuration data. +* Actuation (reconciliation of configuration data with live state) is separate from the transformation of configuration data, and is driven by the declarative data model. +* Transformations, particularly value propagation, are preferable to wholesale configuration generation, except when the expansion is dramatic (for example, >10x). +* Transformation input generation should usually be decoupled from propagation. +* Deployment context inputs should be taken from well defined “provider context” objects. +* Identifiers and references should be declarative. +* Live state should be linked back to sources of truth (configuration). ### Components of the kpt toolchain From c7987b3bacaa195f7d910748b546193819f87b79 Mon Sep 17 00:00:00 2001 From: Michael Greaves Date: Thu, 9 Apr 2026 11:06:11 +0200 Subject: [PATCH 02/29] Proofreading up to kptfile annotations. Signed-off-by: Michael Greaves --- .../content/en/book/02-concepts/_index.md | 179 ++++++++---------- 1 file changed, 75 insertions(+), 104 deletions(-) diff --git a/documentation/content/en/book/02-concepts/_index.md b/documentation/content/en/book/02-concepts/_index.md index 0e2527a87c..59667264b4 100644 --- a/documentation/content/en/book/02-concepts/_index.md +++ b/documentation/content/en/book/02-concepts/_index.md @@ -29,18 +29,18 @@ This enables the machine manipulation of the configuration for Kubernetes and an There are a number of key principles to be borne in mind, with regard to configuration as data (CaD). These principles are as follows: -* Secrets should be stored separately, in a secret-focused storage system. +* Storage of secrets separately, in a secret-focused storage system. * Storage of a versioned history of the configuration changes by change sets to bundles of related configuration data. * Reliance on uniformity and consistency of the configuration format, including type metadata, to enable pattern-based operations on the configuration data, along the lines of duck typing. * Separation of schemas for the configuration data from the data, and reliance on schema information for strongly typed operations and to disambiguate data structures and other variations within the model. * Decoupling of abstractions of configuration from collections of configuration data. * Representation of abstractions of configuration generators as data with schemas, like other configuration data. -* Finding, filtering/querying/selecting, and/or validating configuration data that can be operated on by given code (functions). +* Finding, filtering/querying/selecting, and/or validating configuration data that can be operated on by the given code (functions). * Finding and/or filtering/querying/selecting code (functions) that can operate on the resource types contained within a body of configuration data. -* Actuation (reconciliation of configuration data with live state) is separate from the transformation of configuration data, and is driven by the declarative data model. +* Actuation (reconciliation of configuration data with live state) that is separate from the transformation of configuration data, and is driven by the declarative data model. * Transformations, particularly value propagation, are preferable to wholesale configuration generation, except when the expansion is dramatic (for example, >10x). * Transformation input generation should usually be decoupled from propagation. -* Deployment context inputs should be taken from well defined “provider context” objects. +* Deployment context inputs should be taken from well-defined “provider context” objects. * Identifiers and references should be declarative. * Live state should be linked back to sources of truth (configuration). @@ -48,58 +48,46 @@ There are a number of key principles to be borne in mind, with regard to configu The kpt toolchain includes the following components: -- [**kpt CLI**](../../reference/cli/): The kpt CLI supports package and function operations, and also deployment, via - either direct apply or GitOps. By keeping an inventory of deployed resources, kpt enables resource pruning, aggregated - status and observability, and an improved preview experience. +- [**kpt CLI**](../../reference/cli/): The kpt CLI supports package and function operations, as well as deployment, either through direct apply or through GitOps. By keeping an inventory of deployed resources, kpt enables resource pruning, aggregated status and observability, and an improved preview experience. -- [**Function SDK**](https://github.com/kptdev/krm-functions-sdk): Any general-purpose or domain-specific language can be used to create functions to transform - and/or validate the YAML KRM input/output format, but we provide SDKs to simplify the function authoring process, in - [Go](../05-developing-functions/#developing-in-Go). +- [**Function SDK**](https://github.com/kptdev/krm-functions-sdk): Any general-purpose or domain-specific language can be used to create functions to transform and/or validate the YAML KRM input/output format. However, we provide SDKs to simplify the function authoring process, in +[Go](../05-developing-functions/#developing-in-Go). -- [**Function catalog**](https://catalog.kpt.dev/function-catalog): A catalog of off-the-shelf, tested functions. kpt makes - configuration easy to create and transform, via reusable functions. Because they are expected to be used for in-place - transformation, the functions need to be idempotent. +- [**Function catalog**](https://catalog.kpt.dev/function-catalog): This is a catalog of off-the-shelf, tested functions. kpt makes configurations easy to create and transform, via reusable functions. Because the functions are expected to be used for in-place transformation, they need to be idempotent. ## Packages - -kpt manages KRM resources in bundles called **packages**. -Off-the-shelf packages are rarely deployed without any customization. Like [kustomize](https://kustomize.io), kpt -applies transformation **functions**, using the same -[KRM function specification](https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md), -but optimizes for in-place configuration transformation rather than out-of-place transformation. - -Validation goes hand-in-hand with customization and KRM functions can be used to automate both mutation and validation -of resources, similar to -[Kubernetes admission control](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/). +kpt manages the KRM resources in bundles called **packages**. +Off-the-shelf packages are rarely deployed without any customization. Like [kustomize](https://kustomize.io), kpt applies transformation **functions**, using the same +[KRM function specification](https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md). +However, kpt optimizes for in-place configuration transformation rather than out-of-place transformation. +Validation goes hand-in-hand with customization. KRM functions can be used to automate both mutation and validation of resources, similarly to +[Kubernetes admission control](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/). -A kpt package is a bundle of configuration _data_. It is represented as a directory tree containing KRM resources using -YAML as the file format. +A kpt package is a bundle of configuration _data_. It is represented as a directory tree containing the KRM resources using YAML as the file format. -A package is explicitly declared using a file named `Kptfile` containing a KRM resource of kind `Kptfile`. The Kptfile -contains metadata about the package and is just a regular resource in the YAML format. +A package is explicitly declared using a file named `Kptfile`. This file contains a KRM resource of type `Kptfile`. The Kptfile contains metadata about the package and is simply a regular resource in the YAML format. -### Kptfile Annotations +### Kptfile annotations -The Kptfile supports annotations that control package-level behaviour: +The Kptfile supports annotations that control package-level behavior: -- **`kpt.dev/bfs-rendering`**: When set to `"true"`, renders the package hierarchy in breadth-first order instead of -the default depth-first post-order. -- **`kpt.dev/save-on-render-failure`**: When set to `"true"`, saves partially rendered resources to disk even when -rendering fails, instead of reverting all changes. This is particularly useful for debugging render failures and is -essential for programmatic package rendering scenarios where preserving partial progress is valuable. +- **`kpt.dev/bfs-rendering`**: When set to `"true"`, this annotation renders the package hierarchy in breadth-first order, instead of the default depth-first +post-order. +- **`kpt.dev/save-on-render-failure`**: When set to `"true"`, this annotation saves partially rendered resources to disk, even when rendering fails, instead +of reverting all changes. This is particularly useful for debugging render failures and is essential for programmatic package rendering scenarios, where preserving partial progress is valuable. -Just as directories can be nested, a package can contain another package, called a _subpackage_. +Just as directories can be nested, a package can contain another package. This is called a _subpackage_. -Let's take a look at the wordpress package as an example: +Let us now have a look at the wordpress package as an example: ```shell kpt pkg get https://github.com/kptdev/kpt/package-examples/wordpress@v1.0.0-beta.59 ``` -View the package hierarchy using the `tree` command: +You can view the package hierarchy using the `tree` command: ```shell kpt pkg tree wordpress/ @@ -118,122 +106,105 @@ Package "wordpress" This _package hierarchy_ contains two packages: -1. `wordpress` is the top-level package in the hierarchy declared using `wordpress/Kptfile`. This package contains 2 - subdirectories. `wordpress/deployment` is a regular directory used for organizing resources that belong to the - `wordpress` package itself. The `wordpress` package contains 3 direct resources in 3 files: `service.yaml`, - `deployment/deployment.yaml`, and `deployment/volume.yaml`. -2. `wordpress/mysql` is a subpackage of `wordpress` package since it contains a `Kptfile`. This package contains 3 - resources in `wordpress/mysql/deployment.yaml` file. +1. `wordpress`: This is the top-level package in the hierarchy. It is declared using `wordpress/Kptfile`. This package contains two subdirectories. +`wordpress/deployment` is a regular directory used for organizing resources that belong to the `wordpress` package itself. The `wordpress` package contains +three direct resources in three files: + - `service.yaml` + - `deployment/deployment.yaml` + - `deployment/volume.yaml` +2. `wordpress/mysql`: This is a subpackage of the `wordpress` package, since it contains a `Kptfile`. This package contains three resources in the +`wordpress/mysql/deployment.yaml` file. -kpt uses Git as the underlying version control system. A typical workflow starts by fetching an _upstream_ package from -a Git repository to the local filesystem using `kpt pkg` commands. All other functionality -(i.e. `kpt fn` and `kpt live`) use the package from the local filesystem, not the remote Git repository. You may think -of this as the _vendoring_ used by tooling for some programming languages. The main difference is that kpt is designed -to enable you to modify the vendored package on the local filesystem and then later update the package by merging the -local and upstream changes. +kpt uses Git as the underlying version control system. A typical workflow starts by fetching an _upstream_ package from a Git repository to the localfilesystem using `kpt pkg` commands. All other functionalities (namely, +`kpt fn` and `kpt live`) use the package from the local filesystem, rather than the remote Git repository. It can be thought of as the _vendoring_ used by tooling for some programming languages. The main difference is that kpt is designed to enable you to modify the vendored package on the local filesystem, and then update the package by merging the local and upstream changes. -There is one scenario where a Kptfile is implicit: You can use kpt to fetch any Git directory containing KRM resources, -even if it does not contain a `Kptfile`. Effectively, you are telling kpt to treat that Git directory as a package. kpt -automatically creates the `Kptfile` on the local filesystem to keep track of the upstream repo. This means that kpt is -compatible with large corpus of existing Kubernetes configuration stored on Git today! +There is one scenario where a Kptfile is implicit: you can use kpt to fetch any Git directory containing KRM resources, even if the directory does not contain a `Kptfile`. Effectively, you are telling kpt to treat the Git directory as a package. kpt automatically creates the `Kptfile` on the local filesystem to keep track of the upstream repository. This means that kpt is compatible with a +large corpus of existing Kubernetes configurations currently stored on Git. -For example, `spark` is just a vanilla directory of KRM: +For example, `spark` is essentially a vanilla directory of KRM: ```shell kpt pkg get https://github.com/kubernetes/examples/tree/master/_archived/spark ``` -We will go into details of how to work with packages in [Chapter 3](../03-packages). +Details of how to work with packages are set out in [Chapter 3](../03-packages). ## Workflows -In this section, we'll describe the typical workflows in kpt. We say "typical", because there is no single right way of -using kpt. A user may choose to use some command but not another. This modularity is a key design principle. However, we -still want to provide guidance on how the functionality could be used in real-world scenarios. +In this section, we will describe the typical workflows in kpt. The word _typical_ is used here because there is no single correct way of using kpt. A +user may choose to use a specific command but not another. This modularity is a key design principle. However, we would still like to provide guidance on how the functionality can be used in real-world scenarios. -A workflow in kpt can be best modelled as performing some verbs on the noun _package_. For example, when consuming an -upstream package, the initial workflow can look like this: +A workflow in kpt can be best modeled as performing some verbs on the noun _package_. For example, when consuming an upstream package, the initial workflow may look like this: ![img](/images/lifecycle/flow1.svg) -- **Get**: Using `kpt pkg get` -- **Explore**: Using an editor or running commands such as `kpt pkg tree` -- **Edit**: Customize the package either manually or automatically using `kpt fn eval`. This may involve editing the - functions pipeline in the `Kptfile` which is executed in the next stage. -- **Render**: Using `kpt fn render` +- **Get**: Use the `kpt pkg get` command. +- **Explore**: Use an editor or run commands, such as `kpt pkg tree`. +- **Edit**: Customize the package manually or automatically using the `kpt fn eval` command. This may involve editing the functions pipeline in the `Kptfile` which is executed in the next stage. +- **Render**: Use the `kpt fn render` command. -First, you get a package from upstream. Then, you explore the content of the package to understand it better. Then you -typically want to customize the package for you specific needs. Finally, you render the package which produces the final -resources that can be directly applied to the cluster. Render is a required step as it ensures certain preconditions and -postconditions hold true about the state of the package. +1. Get a package from upstream. +2. Explore the content of the package to understand it better. +3. Customize the package to suit your needs. +4. Render the package. This produces the final resources that can be directly applied to the cluster. +Render is a required step, as it ensures that certain preconditions and +postconditions about the state of the package hold true. -This workflow is an iterative process. There is usually a tight Edit/Render loop in order to produce the desired -outcome. +This workflow is an iterative process. There is usually a tight Edit/Render loop, in order to produce the desired outcome. -Some time later, you may want to update to a newer version of the upstream package: +You may later wish to update to a newer version of the upstream package: ![img](/images/lifecycle/flow2.svg) -- **Update**: Using `kpt pkg update` +- **Update**: Use the `kpt pkg update` command. -Updating the package involves merging your local changes with the changes made by the upstream package authors between -the two specified versions. This is a resource-based merge strategy, and not a line-based merge strategy used by +Updating the package involves merging your local changes with the changes made by the upstream package authors between the two specified versions. This is a resource-based merge strategy, and not a line-based merge strategy used by `git merge`. Instead of consuming an existing package, you can also create a package from scratch: ![img](/images/lifecycle/flow5.svg) -- **Create**: Initialize a directory using `kpt pkg init`. +- **Create**: Initialize a directory using the `kpt pkg init` command. -Now, let's say you have rendered the package, and want to deploy it to a cluster. The workflow -may look like this: +Let us suppose that you have rendered the package, and would like to deploy it to a cluster. The workflow may look like this: ![img](/images/lifecycle/flow3.svg) -- **Initialize**: One-time process using `kpt live init` -- **Preview**: Using `kpt live apply --dry-run` -- **Apply**: Using `kpt live apply` -- **Observe**: Using `kpt live status` +- **Initialize**: This is a one-time process using the `kpt live init` command. +- **Preview**: Use the `kpt live apply --dry-run` command. +- **Apply**: Use the `kpt live apply` command. +- **Observe**: Use the `kpt live status` command. -First, you use dry-run to validate the resources in your package and verify that the expected -resources will be applied and pruned. Then if that looks good, you apply the package. Afterwards, -you may observe the status of the package on the cluster. +First, use the kpt `live apply --dry-run` command to validate the resources in your package and verify that the expected resources will be applied and pruned. If the preview looks good, then apply the package, using the `kpt live apply` command. Afterwards, you may observe the status of the package on the cluster. -You typically want to store the package on Git: +Typically, it is best to store the package in Git: ![img](/images/lifecycle/flow4.svg) -- **Publish**: Using `git commit` +- **Publish**: Use the `git commit` command. -The publishing flow is orthogonal to deployment flow. This allows you to act as a publisher of an -upstream package even though you may not deploy the package personally. +The publishing flow is orthogonal to the deployment flow. This allows you to act as a publisher of an upstream package, even though you may not deploy the package personally. ## Functions -A KRM function (formerly called a _kpt_ function_) is a containerized program that -can perform CRUD operations on KRM resources stored on the local filesystem. kpt -functions are the extensible mechanism to automate mutation and validation of -KRM resources. Some example use cases: +A Kubernetes Resource Model (KRM) function (formerly called a _kpt_ function_) is a containerized program that can perform create, read, update, and delete (CRUD) operations on KRM resources stored on the local filesystem. kpt functions are the extensible mechanism to automate the mutation and validation of KRM resources. The following are some example use cases: - Enforce all `Namespace` resources to have a `cost-center` label. -- Add a label to resources based on some filtering criteria +- Add a label to resources based on certain filtering criteria. - Use a `Team` custom resource to generate a `Namespace` and associated - organization-mandated defaults (e.g. `RBAC`, `ResourceQuota`, etc.) when - bootstrapping a new team + organization-mandated defaults (for example, `RBAC`, `ResourceQuota`, and so on) when bootstrapping a new team. - Bulk transformation of all `PodSecurityPolicy` resources to improve the security posture. - Inject a sidecar container (service mesh, mysql proxy, logging) in a workload - resource (e.g. `Deployment`) + resource (for example, `Deployment`). -Since functions are containerized, they can encapsulate different toolchains, -languages, and runtimes. For example, the function container image can -encapsulate: +Since the functions are containerized, they can encapsulate different toolchains, languages, and runtimes. For example, the function container image can encapsulate the following: -- A binary built using kpt's official Go SDK -- Wrap an existing KRM tool such as `kubeconform` -- Invoke a bash script performing low-level operations -- The interpreter for "executable configuration" such as `Starlark` or `Rego` +- A binary built using kpt's official Go software development kit (SDK). +- Wrap an existing KRM tool, such as `kubeconform`. +- Invoke a bash script performing low-level operations. +- The interpreter for "executable configuration", such as `Starlark` or `Rego`. To astute readers, this model will sound familiar: functions are the client-side analog to Kubernetes controllers: From 5bd7d50b6000e7dd3263dbc6c6d3f031edde55c4 Mon Sep 17 00:00:00 2001 From: Michael Greaves Date: Thu, 16 Apr 2026 10:56:42 +0200 Subject: [PATCH 03/29] Chapter 2 proofreading. Signed-off-by: Michael Greaves --- .../content/en/book/02-concepts/_index.md | 99 +++++++------------ 1 file changed, 34 insertions(+), 65 deletions(-) diff --git a/documentation/content/en/book/02-concepts/_index.md b/documentation/content/en/book/02-concepts/_index.md index b8ee438c9c..03ec3ea164 100644 --- a/documentation/content/en/book/02-concepts/_index.md +++ b/documentation/content/en/book/02-concepts/_index.md @@ -18,7 +18,7 @@ kpt stands for Kubernetes Package Transformation. It supports the management of * Making configuration data the source of truth, stored separately from the live state. * Using a uniform, serializable data model to represent the configuration. * Separating code that acts on the configuration from the data and from the packages/bundles of the data. -* Abstracting the configuration file structure and storage from the operations that act upon the configuration data. Clients manipulating configuration data do not need to directly interact with the storage (git, container images). +* Abstracting the configuration file structure and storage from the operations that act upon the configuration data. Clients manipulating the configuration data do not need to directly interact with the storage (Git, container images). This enables the machine manipulation of the configuration for Kubernetes and any infrastructure represented in the [Kubernetes Resource Model (KRM)](https://github.com/kubernetes/design-proposals-archive/blob/main/architecture/resource-management.md). @@ -33,16 +33,16 @@ There are a number of key principles to be borne in mind, with regard to configu * Storage of a versioned history of the configuration changes by change sets to bundles of related configuration data. * Reliance on uniformity and consistency of the configuration format, including type metadata, to enable pattern-based operations on the configuration data, along the lines of duck typing. * Separation of schemas for the configuration data from the data, and reliance on schema information for strongly typed operations and to disambiguate data structures and other variations within the model. -* Decoupling of abstractions of configuration from collections of configuration data. +* Decoupling of abstractions of configurations from collections of configuration data. * Representation of abstractions of configuration generators as data with schemas, like other configuration data. * Finding, filtering/querying/selecting, and/or validating configuration data that can be operated on by the given code (functions). * Finding and/or filtering/querying/selecting code (functions) that can operate on the resource types contained within a body of configuration data. -* Actuation (reconciliation of configuration data with live state) that is separate from the transformation of configuration data, and is driven by the declarative data model. +* Actuation (reconciliation of the configuration data with the live state) that is separate from the transformation of the configuration data, and is driven by the declarative data model. * Transformations, particularly value propagation, are preferable to wholesale configuration generation, except when the expansion is dramatic (for example, >10x). * Transformation input generation should usually be decoupled from propagation. * Deployment context inputs should be taken from well-defined “provider context” objects. * Identifiers and references should be declarative. -* Live state should be linked back to sources of truth (configuration). +* The live state should be linked back to the sources of truth (configuration). ### Components of the kpt toolchain @@ -50,7 +50,7 @@ The kpt toolchain includes the following components: - [**kpt CLI**](../../reference/cli/): The kpt CLI supports package and function operations, as well as deployment, either through direct apply or through GitOps. By keeping an inventory of deployed resources, kpt enables resource pruning, aggregated status and observability, and an improved preview experience. -- [**Function SDK**](https://github.com/kptdev/krm-functions-sdk): Any general-purpose or domain-specific language can be used to create functions to transform and/or validate the YAML KRM input/output format. However, we provide SDKs to simplify the function authoring process, in +- [**Function SDK**](https://github.com/kptdev/krm-functions-sdk): Any general-purpose or domain-specific language can be used to create functions to transform and/or validate the YAML KRM input/output format. However, we provide software development kits (SDKs) to simplify the function authoring process, in [Go](../05-developing-functions/#developing-in-Go). - [**Function catalog**](https://catalog.kpt.dev/function-catalog): This is a catalog of off-the-shelf, tested functions. kpt makes configurations easy to create and transform, via reusable functions. Because the functions are expected to be used for in-place transformation, they need to be idempotent. @@ -70,34 +70,23 @@ A kpt package is a bundle of configuration _data_. It is represented as a direct A package is explicitly declared using a file named `Kptfile`. This file contains a KRM resource of type `Kptfile`. The Kptfile contains metadata about the package and is simply a regular resource in the YAML format. +Just as directories can be nested, a package can contain another package. This is called a _subpackage_. + ### Kptfile annotations The Kptfile supports annotations that control package-level behavior: - **`kpt.dev/bfs-rendering`**: When set to `"true"`, this annotation renders the package hierarchy in breadth-first order, instead of the default depth-first post-order. -- **`kpt.dev/save-on-render-failure`**: When set to `"true"`, this annotation saves partially rendered resources to disk, even when rendering fails, instead -of reverting all changes. This is particularly useful for debugging render failures and is essential for programmatic package rendering scenarios, where preserving partial progress is valuable. - -Just as directories can be nested, a package can contain another package. This is called a _subpackage_. - -- **`kpt.dev/bfs-rendering`**: When set to `"true"`, renders the package hierarchy in breadth-first order instead of -the default depth-first post-order. -- **`kpt.dev/save-on-render-failure`**: When set to `"true"`, saves partially rendered resources to disk even when -rendering fails, instead of reverting all changes. This is particularly useful for debugging render failures and is -essential for programmatic package rendering scenarios where preserving partial progress is valuable. +- **`kpt.dev/save-on-render-failure`**: When set to `"true"`, this annotation saves partially rendered resources to disk, even when rendering fails, instead of reverting all changes. This is particularly useful for debugging render failures and is essential for programmatic package rendering scenarios, where preserving partial progress is valuable. ### Status Conditions -The Kptfile includes a `status.conditions` field that provides a declarative way to track the execution status of kpt -operations. This makes package management operations observable and traceable. -When `kpt fn render` executes, a `Rendered` status condition is automatically added to the root Kptfile to indicate -whether the rendering operation succeeded or failed. -This status is recorded only for in-place renders (the default behavior). -It is not written for out-of-place modes such as stdout (`-o stdout`), unwrap (`-o unwrap`), or -directory output (`-o `). +The Kptfile includes a `status.conditions` field that provides a declarative way to track the execution status of kpt operations. This makes package management operations observable and traceable. + +When the `kpt fn render` command is executed, a `Rendered` status condition is automatically added to the root Kptfile to indicate whether the rendering operation succeeded or failed. This status is recorded only for in-place renders (the default behavior). It is not written for out-of-place modes, such as stdout (`-o stdout`), unwrap (`-o unwrap`), or directory output (`-o `). -**On successful render:** +**On a successful render:** ```yaml status: conditions: @@ -106,7 +95,7 @@ status: reason: RenderSuccess ``` -**On failed render:** +**On a failed render:** ```yaml status: conditions: @@ -118,18 +107,15 @@ status: pipeline.run: must run with `--allow-exec` option to allow running function binaries ``` -The status condition is recorded only in the root Kptfile, not in subpackages. The error message in failure cases -provides details about what went wrong during the render operation. - -Just as directories can be nested, a package can contain another package, called a _subpackage_. +The status condition is recorded only in the root Kptfile, not in subpackages. The error message, in failure cases, provides details about what went wrong during the render operation. -Let's take a look at the wordpress package as an example: +Let us have a look at the wordpress package as an example: ```shell kpt pkg get https://github.com/kptdev/kpt/package-examples/wordpress@v1.0.0-beta.59 ``` -You can view the package hierarchy using the `tree` command: +You can view the package hierarchy, using the `tree` command: ```shell kpt pkg tree wordpress/ @@ -157,7 +143,7 @@ three direct resources in three files: 2. `wordpress/mysql`: This is a subpackage of the `wordpress` package, since it contains a `Kptfile`. This package contains three resources in the `wordpress/mysql/deployment.yaml` file. -kpt uses Git as the underlying version control system. A typical workflow starts by fetching an _upstream_ package from a Git repository to the localfilesystem using `kpt pkg` commands. All other functionalities (namely, +kpt uses Git as the underlying version control system. A typical workflow starts by fetching an _upstream_ package from a Git repository to the localfilesystem using `kpt pkg` commands. All the other functionalities (namely, `kpt fn` and `kpt live`) use the package from the local filesystem, rather than the remote Git repository. It can be thought of as the _vendoring_ used by tooling for some programming languages. The main difference is that kpt is designed to enable you to modify the vendored package on the local filesystem, and then update the package by merging the local and upstream changes. There is one scenario where a Kptfile is implicit: you can use kpt to fetch any Git directory containing KRM resources, even if the directory does not contain a `Kptfile`. Effectively, you are telling kpt to treat the Git directory as a package. kpt automatically creates the `Kptfile` on the local filesystem to keep track of the upstream repository. This means that kpt is compatible with a @@ -207,7 +193,7 @@ Instead of consuming an existing package, you can also create a package from scr ![img](/images/lifecycle/flow5.svg) -- **Create**: Initialize a package using `kpt pkg init`. The command creates the directory if it doesn't exist. +- **Create**: Initialize a package using the `kpt pkg init` command. This command creates the directory if it does not exist. Let us suppose that you have rendered the package, and would like to deploy it to a cluster. The workflow may look like this: @@ -248,8 +234,7 @@ Since the functions are containerized, they can encapsulate different toolchains - Invoke a bash script performing low-level operations. - The interpreter for "executable configuration", such as `Starlark` or `Rego`. -To astute readers, this model will sound familiar: functions are the client-side -analog to Kubernetes controllers: +To astute readers, this model will sound familiar: the functions are the client-side analog to the Kubernetes controllers: | | Client-side | Server-side | | ---------------- | ------------------------ | ----------------- | @@ -257,49 +242,33 @@ analog to Kubernetes controllers: | **Data** | YAML files on filesystem | resources on etcd | | **Programs** | functions | controllers | -Just as Kubernetes system orchestrates server-side containers, kpt CLI -orchestrates client-side containers operating on configuration. By standardizing -the input and output of the function containers, and how the containers are -executed, kpt can provide the following guarantees: +Just as the Kubernetes system orchestrates the server-side containers, the kpt CLI orchestrates the client-side containers operating on the configuration. By standardizing the input and output of the function containers, and how the containers are executed, kpt can provide the following guarantees: -- Functions are interoperable -- Functions can be chained together -- Functions are hermetic. For correctness, security and speed, it's desirable to - be able to run functions hermetically without any privileges; preventing - out-of-band access to the host filesystem and networking. +- The functions are interoperable. +- The functions can be chained together. +- The functions are hermetic. For correctness, security and speed, it is desirable to be able to run functions hermetically without any privileges, thereby preventing out-of-band access to the host filesystem and networking. We will discuss the KRM Functions Specification Standard in detail in -[Chapter 5](../05-developing-functions). -At a high level, a function execution looks like this: +[Chapter 5](../05-developing-functions). At a high level, a function execution looks like this: ![img](/images/func.svg) -where: +Where: -- `input items`: The input list of KRM resources to operate on. -- `output items`: The output list obtained from adding, removing, or modifying - items in the input. -- `functionConfig`: An optional meta resource containing the arguments to this - invocation of the function. -- `results`: An optional meta resource emitted by the function for observability - and debugging purposes. +- `input items`: This is the input list of the KRM resources on which to operate. +- `output items`: This is the output list obtained from adding, removing, or modifying items in the input. +- `functionConfig`: This is an optional meta resource containing the arguments to this invocation of the function. +- `results`: This is an optional meta resource emitted by the function for observability and debugging purposes. -Naturally, functions can be chained together in a pipeline: +Functions can be chained together in a pipeline, as illustrated here: ![img](/images/pipeline.svg) -There are two different CLI commands that execute functions corresponding to two -fundamentally different approaches: +There are two different CLI commands that execute the functions corresponding to two fundamentally different approaches: -- `kpt fn render`: Executes the pipeline of functions declared in the package - and its subpackages. This is a declarative way to run functions. -- `kpt fn eval`: Executes a given function on the package. The image to run and - the `functionConfig` is specified as CLI argument. This is an imperative way - to run functions. Since the function is provided explicitly by the user, an - imperative invocation can be more privileged and low-level than an declarative - invocation. For example, it can have access to the host system. +- `kpt fn render`: This command executes the pipeline of functions declared in the package and its subpackages. This is a declarative way to run the functions. +- `kpt fn eval`: This command executes a given function on the package. The image to run and the `functionConfig` are specified as a CLI argument. This is an imperative way to run functions. Since the function is provided explicitly by the user, an imperative invocation can be more privileged and low-level than a declarative invocation. For example, it can have access to the host system. -We will discuss how to run functions in [Chapter 4](../04-using-functions) and how to develop functions -in [Chapter 5](../05-developing-functions). +We will discuss how to run functions in [Chapter 4](../04-using-functions), and how to develop functions in [Chapter 5](../05-developing-functions). From 001e60b70b645610297777b4ff31091104acf5a0 Mon Sep 17 00:00:00 2001 From: mpgreaves <103421482+mpgreaves@users.noreply.github.com> Date: Thu, 23 Apr 2026 10:59:54 +0200 Subject: [PATCH 04/29] Apply suggestions from code review Signed-off-by: Michael Greaves Assisted-by: Copilot Code Review: not-known Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- documentation/content/en/book/02-concepts/_index.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/documentation/content/en/book/02-concepts/_index.md b/documentation/content/en/book/02-concepts/_index.md index 03ec3ea164..3a3f7d73c4 100644 --- a/documentation/content/en/book/02-concepts/_index.md +++ b/documentation/content/en/book/02-concepts/_index.md @@ -50,8 +50,8 @@ The kpt toolchain includes the following components: - [**kpt CLI**](../../reference/cli/): The kpt CLI supports package and function operations, as well as deployment, either through direct apply or through GitOps. By keeping an inventory of deployed resources, kpt enables resource pruning, aggregated status and observability, and an improved preview experience. -- [**Function SDK**](https://github.com/kptdev/krm-functions-sdk): Any general-purpose or domain-specific language can be used to create functions to transform and/or validate the YAML KRM input/output format. However, we provide software development kits (SDKs) to simplify the function authoring process, in -[Go](../05-developing-functions/#developing-in-Go). +- [**Function SDK**](https://github.com/kptdev/krm-functions-sdk): Any general-purpose or domain-specific language can be used to create functions to transform and/or validate the YAML KRM input/output format. However, we provide software development kits (SDKs) to simplify the function authoring process, in + [Go](../05-developing-functions/#developing-in-go). - [**Function catalog**](https://catalog.kpt.dev/function-catalog): This is a catalog of off-the-shelf, tested functions. kpt makes configurations easy to create and transform, via reusable functions. Because the functions are expected to be used for in-place transformation, they need to be idempotent. @@ -68,7 +68,7 @@ Validation goes hand-in-hand with customization. KRM functions can be used to au A kpt package is a bundle of configuration _data_. It is represented as a directory tree containing the KRM resources using YAML as the file format. -A package is explicitly declared using a file named `Kptfile`. This file contains a KRM resource of type `Kptfile`. The Kptfile contains metadata about the package and is simply a regular resource in the YAML format. +A package is explicitly declared using a file named `Kptfile`. This file contains a KRM resource of type `Kptfile`. The Kptfile contains metadata about the package and is simply a regular resource in the YAML format. Just as directories can be nested, a package can contain another package. This is called a _subpackage_. @@ -77,7 +77,7 @@ Just as directories can be nested, a package can contain another package. This i The Kptfile supports annotations that control package-level behavior: - **`kpt.dev/bfs-rendering`**: When set to `"true"`, this annotation renders the package hierarchy in breadth-first order, instead of the default depth-first -post-order. + post-order. - **`kpt.dev/save-on-render-failure`**: When set to `"true"`, this annotation saves partially rendered resources to disk, even when rendering fails, instead of reverting all changes. This is particularly useful for debugging render failures and is essential for programmatic package rendering scenarios, where preserving partial progress is valuable. ### Status Conditions @@ -143,7 +143,7 @@ three direct resources in three files: 2. `wordpress/mysql`: This is a subpackage of the `wordpress` package, since it contains a `Kptfile`. This package contains three resources in the `wordpress/mysql/deployment.yaml` file. -kpt uses Git as the underlying version control system. A typical workflow starts by fetching an _upstream_ package from a Git repository to the localfilesystem using `kpt pkg` commands. All the other functionalities (namely, +kpt uses Git as the underlying version control system. A typical workflow starts by fetching an _upstream_ package from a Git repository to the local filesystem using `kpt pkg` commands. All the other functionalities (namely, `kpt fn` and `kpt live`) use the package from the local filesystem, rather than the remote Git repository. It can be thought of as the _vendoring_ used by tooling for some programming languages. The main difference is that kpt is designed to enable you to modify the vendored package on the local filesystem, and then update the package by merging the local and upstream changes. There is one scenario where a Kptfile is implicit: you can use kpt to fetch any Git directory containing KRM resources, even if the directory does not contain a `Kptfile`. Effectively, you are telling kpt to treat the Git directory as a package. kpt automatically creates the `Kptfile` on the local filesystem to keep track of the upstream repository. This means that kpt is compatible with a @@ -204,7 +204,7 @@ Let us suppose that you have rendered the package, and would like to deploy it t - **Apply**: Use the `kpt live apply` command. - **Observe**: Use the `kpt live status` command. -First, use the kpt `live apply --dry-run` command to validate the resources in your package and verify that the expected resources will be applied and pruned. If the preview looks good, then apply the package, using the `kpt live apply` command. Afterwards, you may observe the status of the package on the cluster. +First, use the `kpt live apply --dry-run` command to validate the resources in your package and verify that the expected resources will be applied and pruned. If the preview looks good, then apply the package, using the `kpt live apply` command. Afterwards, you may observe the status of the package on the cluster. Typically, it is best to store the package in Git: From cec97b1827b4f5d6e0a22bd96dc7f608cefe3214 Mon Sep 17 00:00:00 2001 From: Michael Greaves Date: Thu, 23 Apr 2026 11:06:33 +0200 Subject: [PATCH 05/29] Fixed a minor issue. Signed-off-by: Michael Greaves --- documentation/content/en/book/02-concepts/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/content/en/book/02-concepts/_index.md b/documentation/content/en/book/02-concepts/_index.md index 3a3f7d73c4..a51bf79fca 100644 --- a/documentation/content/en/book/02-concepts/_index.md +++ b/documentation/content/en/book/02-concepts/_index.md @@ -216,7 +216,7 @@ The publishing flow is orthogonal to the deployment flow. This allows you to act ## Functions -A Kubernetes Resource Model (KRM) function (formerly called a _kpt_ function_) is a containerized program that can perform create, read, update, and delete (CRUD) operations on KRM resources stored on the local filesystem. kpt functions are the extensible mechanism to automate the mutation and validation of KRM resources. The following are some example use cases: +A Kubernetes Resource Model (KRM) function (formerly called a _kpt function_) is a containerized program that can perform create, read, update, and delete (CRUD) operations on KRM resources stored on the local filesystem. KRM functions are the extensible mechanism to automate the mutation and validation of KRM resources. The following are some example use cases: - Enforce all `Namespace` resources to have a `cost-center` label. - Add a label to resources based on certain filtering criteria. From 68383cf97545531fbaa15f4b2346b6f71bc95f81 Mon Sep 17 00:00:00 2001 From: mpgreaves <103421482+mpgreaves@users.noreply.github.com> Date: Thu, 23 Apr 2026 11:15:26 +0200 Subject: [PATCH 06/29] Apply suggestions from code review Signed-off-by: Michael Greaves Assisted-by: Copilot Code Review: not-known Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- documentation/content/en/book/02-concepts/_index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/content/en/book/02-concepts/_index.md b/documentation/content/en/book/02-concepts/_index.md index a51bf79fca..e08d609170 100644 --- a/documentation/content/en/book/02-concepts/_index.md +++ b/documentation/content/en/book/02-concepts/_index.md @@ -40,7 +40,7 @@ There are a number of key principles to be borne in mind, with regard to configu * Actuation (reconciliation of the configuration data with the live state) that is separate from the transformation of the configuration data, and is driven by the declarative data model. * Transformations, particularly value propagation, are preferable to wholesale configuration generation, except when the expansion is dramatic (for example, >10x). * Transformation input generation should usually be decoupled from propagation. -* Deployment context inputs should be taken from well-defined “provider context” objects. +* Deployment context inputs should be taken from well-defined "provider context" objects. * Identifiers and references should be declarative. * The live state should be linked back to the sources of truth (configuration). @@ -143,7 +143,7 @@ three direct resources in three files: 2. `wordpress/mysql`: This is a subpackage of the `wordpress` package, since it contains a `Kptfile`. This package contains three resources in the `wordpress/mysql/deployment.yaml` file. -kpt uses Git as the underlying version control system. A typical workflow starts by fetching an _upstream_ package from a Git repository to the local filesystem using `kpt pkg` commands. All the other functionalities (namely, +kpt uses Git as the underlying version control system. A typical workflow starts by fetching an _upstream_ package from a Git repository to the local filesystem using `kpt pkg` commands. All the other commands (namely, `kpt fn` and `kpt live`) use the package from the local filesystem, rather than the remote Git repository. It can be thought of as the _vendoring_ used by tooling for some programming languages. The main difference is that kpt is designed to enable you to modify the vendored package on the local filesystem, and then update the package by merging the local and upstream changes. There is one scenario where a Kptfile is implicit: you can use kpt to fetch any Git directory containing KRM resources, even if the directory does not contain a `Kptfile`. Effectively, you are telling kpt to treat the Git directory as a package. kpt automatically creates the `Kptfile` on the local filesystem to keep track of the upstream repository. This means that kpt is compatible with a From 9d9e8d0592552fee64438136d8b8892694903beb Mon Sep 17 00:00:00 2001 From: mpgreaves <103421482+mpgreaves@users.noreply.github.com> Date: Thu, 21 May 2026 10:57:49 +0200 Subject: [PATCH 07/29] Apply suggestions from code review Signed-off-by: Michael Greaves Assisted-by: Copilot Code Review:not-known Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- documentation/content/en/book/02-concepts/_index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/content/en/book/02-concepts/_index.md b/documentation/content/en/book/02-concepts/_index.md index e08d609170..7f70220f2a 100644 --- a/documentation/content/en/book/02-concepts/_index.md +++ b/documentation/content/en/book/02-concepts/_index.md @@ -68,7 +68,7 @@ Validation goes hand-in-hand with customization. KRM functions can be used to au A kpt package is a bundle of configuration _data_. It is represented as a directory tree containing the KRM resources using YAML as the file format. -A package is explicitly declared using a file named `Kptfile`. This file contains a KRM resource of type `Kptfile`. The Kptfile contains metadata about the package and is simply a regular resource in the YAML format. +A package is explicitly declared using a file named `Kptfile`. This file contains a KRM resource of kind `Kptfile`. The Kptfile contains metadata about the package and is simply a regular resource in the YAML format. Just as directories can be nested, a package can contain another package. This is called a _subpackage_. @@ -104,7 +104,7 @@ status: reason: RenderFailed message: |- pkg.render: pkg .: - pipeline.run: must run with `--allow-exec` option to allow running function binaries + pipeline.run: must run with `--allow-exec` option to allow running function binaries ``` The status condition is recorded only in the root Kptfile, not in subpackages. The error message, in failure cases, provides details about what went wrong during the render operation. From 38fb89fde283e62968e8c9f7b3fa992b527c51b7 Mon Sep 17 00:00:00 2001 From: Gergely Csatari Date: Thu, 2 Jul 2026 12:01:40 +0300 Subject: [PATCH 08/29] Apply suggestions from code review Signed-off-by: Gergely Csatari Co-authored-by: Catalin Stratulat <159934629+Catalin-Stratulat-Ericsson@users.noreply.github.com> --- documentation/content/en/book/02-concepts/_index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/content/en/book/02-concepts/_index.md b/documentation/content/en/book/02-concepts/_index.md index 7f70220f2a..6b90603a99 100644 --- a/documentation/content/en/book/02-concepts/_index.md +++ b/documentation/content/en/book/02-concepts/_index.md @@ -1,7 +1,7 @@ --- title: "Chapter 2: Concepts" linkTitle: "Chapter 2: Concepts" -description: This chapter describes what kpt is and what the main concepts are behind kpt. +description: This chapter describes what kpt is and what the main concepts behind it are. toc: true menu: main: @@ -13,7 +13,7 @@ menu: kpt stands for Kubernetes Package Transformation. It supports the management of configuration as data (CaD). -*Configuration as data* is an approach to the management of configurations, including the configuration of infrastructure, policy, services, applications, and so on, which comprises the following actions: +*Configuration as data* is an approach to the management of configurations, including the configuration of infrastructure, policy, services, applications, among others, which comprises the following actions: * Making configuration data the source of truth, stored separately from the live state. * Using a uniform, serializable data model to represent the configuration. From a1021204757564fa6d2de6b8ab61012b229dafc2 Mon Sep 17 00:00:00 2001 From: Gergely Csatari Date: Thu, 2 Jul 2026 12:06:40 +0300 Subject: [PATCH 09/29] Fixing title Signed-off-by: Gergely Csatari --- documentation/content/en/book/02-concepts/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/content/en/book/02-concepts/_index.md b/documentation/content/en/book/02-concepts/_index.md index 6b90603a99..b3d676475c 100644 --- a/documentation/content/en/book/02-concepts/_index.md +++ b/documentation/content/en/book/02-concepts/_index.md @@ -1,7 +1,7 @@ --- title: "Chapter 2: Concepts" linkTitle: "Chapter 2: Concepts" -description: This chapter describes what kpt is and what the main concepts behind it are. +description: This chapter describes what kpt is and what are the main concepts behind it. toc: true menu: main: From 342a52da3050484c918ac1d57ac017cd9028c985 Mon Sep 17 00:00:00 2001 From: Gergely Csatari Date: Thu, 23 Jul 2026 10:24:55 +0300 Subject: [PATCH 10/29] Clarifications on the scoping of Kptfile annotations Signed-off-by: Gergely Csatari --- documentation/content/en/book/02-concepts/_index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/documentation/content/en/book/02-concepts/_index.md b/documentation/content/en/book/02-concepts/_index.md index b3d676475c..955d843280 100644 --- a/documentation/content/en/book/02-concepts/_index.md +++ b/documentation/content/en/book/02-concepts/_index.md @@ -80,6 +80,10 @@ The Kptfile supports annotations that control package-level behavior: post-order. - **`kpt.dev/save-on-render-failure`**: When set to `"true"`, this annotation saves partially rendered resources to disk, even when rendering fails, instead of reverting all changes. This is particularly useful for debugging render failures and is essential for programmatic package rendering scenarios, where preserving partial progress is valuable. +These annotations are read from the Kptfile of the invoked package and applied to all subpackages recursively. In case +if these annotations are defined in the Kptfile of the subpackages, they are ignored. If an subpackage is rendered +directly the renreded subpackages annotations will be used. + ### Status Conditions The Kptfile includes a `status.conditions` field that provides a declarative way to track the execution status of kpt operations. This makes package management operations observable and traceable. From 7f6da0630c13bca79612cbf5339f075bbe912095 Mon Sep 17 00:00:00 2001 From: Aravindhan Ayyanathan Date: Fri, 28 Aug 2026 14:31:27 +0100 Subject: [PATCH 11/29] Docs: use Docsy alert shortcode for notes and warnings (#4703) Signed-off-by: Aravindhan Ayyanathan --- documentation/assets/scss/_nav.scss | 6 +++--- .../assets/scss/_variables_project.scss | 2 +- .../en/book/04-using-functions/_index.md | 12 ++++++++---- .../en/book/06-deploying-packages/_index.md | 12 ++++++------ documentation/content/en/guides/3-way-merge.md | 4 +++- .../en/guides/namespace-provisioning-cli.md | 4 +++- .../content/en/guides/tenant-onboarding.md | 8 +++++--- .../content/en/installation/migration.md | 4 +++- .../en/reference/cli/alpha/live/plan/_index.md | 4 ++-- documentation/layouts/shortcodes/warning.html | 17 ----------------- 10 files changed, 34 insertions(+), 39 deletions(-) delete mode 100644 documentation/layouts/shortcodes/warning.html diff --git a/documentation/assets/scss/_nav.scss b/documentation/assets/scss/_nav.scss index 8fd9a32833..c00973739e 100644 --- a/documentation/assets/scss/_nav.scss +++ b/documentation/assets/scss/_nav.scss @@ -3,7 +3,7 @@ // .td-navbar-cover { - background: $primary; + background: #fff; @include media-breakpoint-up(md) { background: transparent !important; @@ -20,12 +20,12 @@ } .navbar-bg-onscroll { - background: $primary !important; + background: #fff !important; opacity: inherit; } .td-navbar { - background: $primary; + background: #fff; min-height: 4rem; margin: 0; z-index: 32; diff --git a/documentation/assets/scss/_variables_project.scss b/documentation/assets/scss/_variables_project.scss index b22462768f..c82aaea80f 100644 --- a/documentation/assets/scss/_variables_project.scss +++ b/documentation/assets/scss/_variables_project.scss @@ -16,7 +16,7 @@ $kpt-blue: #00bbf9; $kpt-dark-pink: #a43e7b; $kpt-dark-blue: #0081ac; -$primary: #fff; +$primary: $kpt-dark-blue; $black: #000; $primary-300: #c53490; diff --git a/documentation/content/en/book/04-using-functions/_index.md b/documentation/content/en/book/04-using-functions/_index.md index 91bded2916..06ab30c2a0 100644 --- a/documentation/content/en/book/04-using-functions/_index.md +++ b/documentation/content/en/book/04-using-functions/_index.md @@ -253,8 +253,9 @@ pipeline: - exec: "sed -e 's/foo/bar/'" ``` -Note: +{{% alert title="Note" color="primary" %}} You must render the package by allowing the executables. To do this, specify the `--allow-exec` command line flag, as shown below: +{{% /alert %}} ```shell kpt fn render [PKG_DIR] --allow-exec @@ -572,9 +573,10 @@ Package "wordpress": Successfully executed 5 function(s) in 2 package(s). ``` -Note: +{{% alert title="Note" color="primary" %}} The `ensure-name-substring` function is applied only to the resources matching the selection criteria. +{{% /alert %}} If you have resources with particular labels or annotations that you want to use to select your resources, then you can use them. Here, for example, is a function that is only applied to the resources matching the label `foo: bar`: @@ -735,8 +737,9 @@ Many functions take a `functionConfig` of the kind `ConfigMap`, since they only kpt fn eval wordpress -i set-namespace:latest -- namespace=mywordpress ``` -Note: +{{% alert title="Note" color="primary" %}} The arguments must come after the separator `--`. +{{% /alert %}} ### Specifying `selectors` @@ -810,8 +813,9 @@ The `kubeconform` function can, for example, consume a JSON schema file, as foll kpt fn eval -i kubeconform:latest --mount type=bind,src="/path/to/schema-dir",dst=/schema-dir --as-current-user wordpress -- schema_location=file:///schema-dir ``` -Note: +{{% alert title="Note" color="primary" %}} The `--as-current-user` flag may be required to run the function as your uid, instead of the default `nobody`, to access the host filesystem. +{{% /alert %}} All the volumes are mounted as _readonly_ by default. To mount volumes in _read-write_ mode, specify `rw=true`, as follows: diff --git a/documentation/content/en/book/06-deploying-packages/_index.md b/documentation/content/en/book/06-deploying-packages/_index.md index d5d25006f6..da144387c5 100644 --- a/documentation/content/en/book/06-deploying-packages/_index.md +++ b/documentation/content/en/book/06-deploying-packages/_index.md @@ -121,9 +121,9 @@ resources in `wordpress` package are in the `default` namespace, so it chooses and namespace of the `ResourceGroup` resource. Refer to the [init command reference]({{% relref "/reference/cli/live/init" %}}) for usage. -{{< warning type=warning >}} +{{% alert title="Warning" color="warning" %}} Once a package is applied to the cluster, do not change the `ResourceGroup` CR. Doing so corrupts the association between the package and the inventory in the cluster, possibly leading to unpredictable and destructive operations. -{{< /warning >}} +{{% /alert %}} ## Applying a Package @@ -135,9 +135,9 @@ Let's create that first: ```shell kubectl create secret generic mysql-pass --from-literal=password=YOUR_PASSWORD ``` -{{< warning type=info >}} +{{% alert title="Note" color="primary" %}} You can also declare the `Secret` resource, but make sure it is not committed to Git as part of the package. -{{< /warning >}} +{{% /alert %}} Then deploy the package and wait for the resources to be reconciled: @@ -179,10 +179,10 @@ running `kpt live apply`: kpt live install-resource-group ``` -{{< warning type=info >}} +{{% alert title="Note" color="primary" %}} Installing this CRD requires sufficient ClusterRole permission, so you may need to ask your cluster admin to install it for you. -{{< /warning >}} +{{% /alert %}} ### Server-side vs Client-side apply diff --git a/documentation/content/en/guides/3-way-merge.md b/documentation/content/en/guides/3-way-merge.md index 30d3d66f48..364816d94e 100644 --- a/documentation/content/en/guides/3-way-merge.md +++ b/documentation/content/en/guides/3-way-merge.md @@ -103,7 +103,9 @@ Replaces your entire local package with upstream, discarding all local changes. **When to use**: Only when you intentionally want to discard all customizations -**Warning**: This strategy will **lose all your local modifications**. Use with caution. +{{% alert title="Warning" color="warning" %}} +This strategy will **lose all your local modifications**. Use with caution. +{{% /alert %}} For complete strategy details and examples, see the [update command reference]({{% relref "/reference/cli/pkg/update" %}}). diff --git a/documentation/content/en/guides/namespace-provisioning-cli.md b/documentation/content/en/guides/namespace-provisioning-cli.md index 1512813658..2ca869a1ae 100644 --- a/documentation/content/en/guides/namespace-provisioning-cli.md +++ b/documentation/content/en/guides/namespace-provisioning-cli.md @@ -211,10 +211,12 @@ Package "tenant": Successfully executed 1 function(s) in 1 package(s). ``` -Note: if you are curious about how KRM functions are implemented. Take a look +{{% alert title="Note" color="primary" %}} +If you are curious about how KRM functions are implemented, take a look at [set-namespace code](https://github.com/kptdev/krm-functions-catalog/blob/main/functions/go/set-namespace/transformer/namespace.go) to get a feel for the implementation. You can also check out [Chapter 5: Developing Functions](/book/05-developing-functions/) of the kpt book. +{{% /alert %}} ### Permissions diff --git a/documentation/content/en/guides/tenant-onboarding.md b/documentation/content/en/guides/tenant-onboarding.md index 57c40a6408..104640da6a 100644 --- a/documentation/content/en/guides/tenant-onboarding.md +++ b/documentation/content/en/guides/tenant-onboarding.md @@ -1,9 +1,9 @@ # Tenant onboarding -{{< warning type=warning >}} +{{% alert title="Warning" color="warning" %}} This guide is a work in progress and may not fully reflect the current state of kpt. It is due for a rewrite. -{{< /warning >}} +{{% /alert %}} We have seen that in large organizations using kubernetes, there is a platform team (or infrastructure team) that is responsible for managing the kubernetes @@ -14,8 +14,10 @@ learn - how you can use kpt to address the tenant use-case. Though this guide focuses on the tenant use-case, the pattern for package workflow discussed here can be applied to other use cases as well. -**Note:** This guide is inspired by the [kube-common-setup](https://github.com/nghnam/kube-common-setup) +{{% alert title="Note" color="primary" %}} +This guide is inspired by the [kube-common-setup](https://github.com/nghnam/kube-common-setup) helm chart. +{{% /alert %}} ## Terminology diff --git a/documentation/content/en/installation/migration.md b/documentation/content/en/installation/migration.md index b81dbb60cb..79c10c0169 100644 --- a/documentation/content/en/installation/migration.md +++ b/documentation/content/en/installation/migration.md @@ -256,8 +256,10 @@ git diff function definitions will be declared in `pipeline` section of Kptfile. Reference to function config is added via [configPath] option. -Note: This function modifies only the local package files and doesn’t make any +{{% alert title="Note" color="primary" %}} +This function modifies only the local package files and doesn’t make any changes to the resources in the live cluster. +{{% /alert %}} #### Manual portion of migration diff --git a/documentation/content/en/reference/cli/alpha/live/plan/_index.md b/documentation/content/en/reference/cli/alpha/live/plan/_index.md index a7f346e74c..1e45bd7dad 100644 --- a/documentation/content/en/reference/cli/alpha/live/plan/_index.md +++ b/documentation/content/en/reference/cli/alpha/live/plan/_index.md @@ -10,9 +10,9 @@ description: > Output a plan for the changes that will happen when applying a package. --> -{{< warning type=warning >}} +{{% alert title="Warning" color="warning" %}} This feature is still in alpha, so the output format is subject to change. -{{< /warning >}} +{{% /alert %}} `plan` does a dry-run of applying a package to the cluster. It outputs the results in combination with a diff for every resource that will be updated, which gives an diff --git a/documentation/layouts/shortcodes/warning.html b/documentation/layouts/shortcodes/warning.html deleted file mode 100644 index a9552de980..0000000000 --- a/documentation/layouts/shortcodes/warning.html +++ /dev/null @@ -1,17 +0,0 @@ -{{ $type := .Get "type" | default "info" }} -{{ $title := .Get "title" }} - -
- {{ if $title }} - {{ $title }} - {{ else }} - {{ if eq $type "info" }}ℹ Info:{{ end }} - {{ if eq $type "tip" }}💡 Tip:{{ end }} - {{ if eq $type "warning" }}⚠ Warning:{{ end }} - {{ if eq $type "danger" }}❌ Danger:{{ end }} - {{ end }} - - {{/* Render inner content as Markdown and replace inline backticks with */}} - {{ $content := .Inner | markdownify }} - {{ $content | replaceRE "`([^`]+)`" "$1" | safeHTML }} -
From d2fe7db42c2edb2e19c99794a4dfacf1c6f15211 Mon Sep 17 00:00:00 2001 From: Aravindhan Ayyanathan Date: Mon, 31 Aug 2026 10:36:16 +0100 Subject: [PATCH 12/29] Refactor: Move setup-go-kpt composite action from porch to kpt (#4709) Signed-off-by: Aravindhan Ayyanathan --- .github/actions/setup-go-kpt/action.yml | 81 +++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 .github/actions/setup-go-kpt/action.yml diff --git a/.github/actions/setup-go-kpt/action.yml b/.github/actions/setup-go-kpt/action.yml new file mode 100644 index 0000000000..ff428d8a93 --- /dev/null +++ b/.github/actions/setup-go-kpt/action.yml @@ -0,0 +1,81 @@ +# 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. + +name: "Setup Go and kpt" +description: "Sets up Go from go.mod and installs kpt version matching go.mod" + +inputs: + install-kpt: + description: "Whether to install kpt" + required: false + default: "true" + kpt-fallback-version: + description: "Fallback kpt version if extraction from go.mod fails" + required: false + default: "v1.0.0-beta.65" + go-version: + description: "Exact version for Go version setup, go-version-file is ignored if set" + required: false + go-version-file: + description: "Path to go.mod file for Go version setup" + required: false + default: "go.mod" + go-cache: + description: "Enable Go module caching" + required: false + default: "true" + cache-dependency-path: + description: "Relative path to go.sum file for Go module caching" + required: false + default: "go.sum" + +outputs: + kpt-version: + description: "The kpt version installed" + value: ${{ steps.kpt-version.outputs.version }} + +runs: + using: "composite" + steps: + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: ${{ inputs.go-version }} + go-version-file: ${{ inputs.go-version-file }} + cache: ${{ inputs.go-cache == 'true' }} + cache-dependency-path: ${{ inputs.cache-dependency-path }} + + - name: Get kpt version from go.mod + if: inputs.install-kpt == 'true' + id: kpt-version + shell: bash + env: + GO_VERSION_FILE: ${{ inputs.go-version-file }} + KPT_FALLBACK: ${{ inputs.kpt-fallback-version }} + run: | + GO_MOD_DIR=$(dirname -- "$GO_VERSION_FILE") + KPT_VERSION=$(cd "$GO_MOD_DIR" && go list -m -f '{{.Version}}' github.com/kptdev/kpt 2>/dev/null) + if [ -z "$KPT_VERSION" ]; then + echo "Warning: Could not resolve kpt version from ${GO_VERSION_FILE}, using fallback." + KPT_VERSION="$KPT_FALLBACK" + fi + echo "version=${KPT_VERSION}" >> "$GITHUB_OUTPUT" + + - name: Install kpt + if: inputs.install-kpt == 'true' + uses: jaxxstorm/action-install-gh-release@v2.1.0 + with: + repo: kptdev/kpt + tag: ${{ steps.kpt-version.outputs.version }} + chmod: 0755 From adc24507c1b86daa8dbb57183979de5d3f945642 Mon Sep 17 00:00:00 2001 From: Aravindhan Ayyanathan Date: Mon, 31 Aug 2026 10:36:37 +0100 Subject: [PATCH 13/29] fix: Improve CEL conditional execution consistency and test coverage (#4713) * fix: Improve CEL conditional execution consistency and test coverage Signed-off-by: Aravindhan Ayyanathan * Address review comments Signed-off-by: Aravindhan Ayyanathan --------- Signed-off-by: Aravindhan Ayyanathan --- .../en/book/04-using-functions/_index.md | 2 +- .../en/reference/cli/fn/eval/_index.md | 7 +++ .../.expected/config.yaml | 19 +++++++ .../fn-eval/condition-invalid-cel/.krmignore | 1 + .../condition-invalid-cel/resources.yaml | 19 +++++++ .../condition-met/.expected/config.yaml | 17 ++++++ e2e/testdata/fn-eval/condition-met/.krmignore | 1 + .../fn-eval/condition-met/resources.yaml | 26 +++++++++ .../condition-not-met/.expected/config.yaml | 18 +++++++ .../fn-eval/condition-not-met/.krmignore | 1 + .../fn-eval/condition-not-met/resources.yaml | 19 +++++++ .../condition-mixed/.expected/config.yaml | 7 +++ .../condition-mixed/.expected/diff.patch | 22 ++++++++ .../condition/condition-mixed/.krmignore | 1 + .../condition/condition-mixed/Kptfile | 10 ++++ .../condition/condition-mixed/resources.yaml | 6 +++ .../.expected/diff.patch | 17 ++++++ .../validator-excludes-own-fnconfig/Kptfile | 8 +++ .../deployment.yaml | 6 +++ .../starlark-val.yaml | 10 ++++ internal/docs/generated/fndocs/docs.go | 9 +++- pkg/fn/runtime/condition_test.go | 12 ++--- pkg/lib/runneroptions/celenv.go | 20 +++---- pkg/lib/runneroptions/celenv_test.go | 54 +++++++++++++++++++ pkg/live/load.go | 4 +- pkg/live/rgstream.go | 6 +-- pkg/test/runner/config.go | 2 + pkg/test/runner/runner.go | 3 ++ .../cmdconfig/commands/cmdeval/cmdeval.go | 2 +- 29 files changed, 303 insertions(+), 26 deletions(-) create mode 100644 e2e/testdata/fn-eval/condition-invalid-cel/.expected/config.yaml create mode 100644 e2e/testdata/fn-eval/condition-invalid-cel/.krmignore create mode 100644 e2e/testdata/fn-eval/condition-invalid-cel/resources.yaml create mode 100644 e2e/testdata/fn-eval/condition-met/.expected/config.yaml create mode 100644 e2e/testdata/fn-eval/condition-met/.krmignore create mode 100644 e2e/testdata/fn-eval/condition-met/resources.yaml create mode 100644 e2e/testdata/fn-eval/condition-not-met/.expected/config.yaml create mode 100644 e2e/testdata/fn-eval/condition-not-met/.krmignore create mode 100644 e2e/testdata/fn-eval/condition-not-met/resources.yaml create mode 100644 e2e/testdata/fn-render/condition/condition-mixed/.expected/config.yaml create mode 100644 e2e/testdata/fn-render/condition/condition-mixed/.expected/diff.patch create mode 100644 e2e/testdata/fn-render/condition/condition-mixed/.krmignore create mode 100644 e2e/testdata/fn-render/condition/condition-mixed/Kptfile create mode 100644 e2e/testdata/fn-render/condition/condition-mixed/resources.yaml create mode 100644 e2e/testdata/fn-render/validator-excludes-own-fnconfig/.expected/diff.patch create mode 100644 e2e/testdata/fn-render/validator-excludes-own-fnconfig/Kptfile create mode 100644 e2e/testdata/fn-render/validator-excludes-own-fnconfig/deployment.yaml create mode 100644 e2e/testdata/fn-render/validator-excludes-own-fnconfig/starlark-val.yaml create mode 100644 pkg/lib/runneroptions/celenv_test.go diff --git a/documentation/content/en/book/04-using-functions/_index.md b/documentation/content/en/book/04-using-functions/_index.md index 06ab30c2a0..d2ff864d88 100644 --- a/documentation/content/en/book/04-using-functions/_index.md +++ b/documentation/content/en/book/04-using-functions/_index.md @@ -435,7 +435,7 @@ If the condition is not met: $ kpt fn render wordpress Package "wordpress": -[SKIPPED] "ghcr.io/kptdev/krm-functions-catalog/set-labels:latest" (condition not met) +[SKIPPED] "ghcr.io/kptdev/krm-functions-catalog/set-labels:latest" (celCondition not met) Successfully executed 0 function(s) in 1 package(s). ``` diff --git a/documentation/content/en/reference/cli/fn/eval/_index.md b/documentation/content/en/reference/cli/fn/eval/_index.md index ee069e5613..3432935448 100644 --- a/documentation/content/en/reference/cli/fn/eval/_index.md +++ b/documentation/content/en/reference/cli/fn/eval/_index.md @@ -195,6 +195,13 @@ fn-args: --save, s: Save the function image and fn-config to Kptfile. Require ` + "`" + `--image` + "`" + `. +--when: + A CEL expression that determines whether the function should be run. + The expression is evaluated against the input resources and must return a boolean. + If it returns true (or is not specified), the function executes normally. + If it returns false, the function is skipped. + Example: --when "resources.exists(r, r.kind == 'ConfigMap')" + ``` #### Environment Variables diff --git a/e2e/testdata/fn-eval/condition-invalid-cel/.expected/config.yaml b/e2e/testdata/fn-eval/condition-invalid-cel/.expected/config.yaml new file mode 100644 index 0000000000..2764cca6e3 --- /dev/null +++ b/e2e/testdata/fn-eval/condition-invalid-cel/.expected/config.yaml @@ -0,0 +1,19 @@ +# 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. + +testType: eval +image: ghcr.io/kptdev/krm-functions-catalog/no-op +when: "this is not valid CEL" +exitCode: 1 +stdErr: "failed to compile CEL expression" diff --git a/e2e/testdata/fn-eval/condition-invalid-cel/.krmignore b/e2e/testdata/fn-eval/condition-invalid-cel/.krmignore new file mode 100644 index 0000000000..9d7a4007d6 --- /dev/null +++ b/e2e/testdata/fn-eval/condition-invalid-cel/.krmignore @@ -0,0 +1 @@ +.expected diff --git a/e2e/testdata/fn-eval/condition-invalid-cel/resources.yaml b/e2e/testdata/fn-eval/condition-invalid-cel/resources.yaml new file mode 100644 index 0000000000..28807636fa --- /dev/null +++ b/e2e/testdata/fn-eval/condition-invalid-cel/resources.yaml @@ -0,0 +1,19 @@ +# 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. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-app +spec: + replicas: 1 diff --git a/e2e/testdata/fn-eval/condition-met/.expected/config.yaml b/e2e/testdata/fn-eval/condition-met/.expected/config.yaml new file mode 100644 index 0000000000..e026ce08f5 --- /dev/null +++ b/e2e/testdata/fn-eval/condition-met/.expected/config.yaml @@ -0,0 +1,17 @@ +# 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. + +testType: eval +image: ghcr.io/kptdev/krm-functions-catalog/no-op +when: "resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'app-config')" diff --git a/e2e/testdata/fn-eval/condition-met/.krmignore b/e2e/testdata/fn-eval/condition-met/.krmignore new file mode 100644 index 0000000000..9d7a4007d6 --- /dev/null +++ b/e2e/testdata/fn-eval/condition-met/.krmignore @@ -0,0 +1 @@ +.expected diff --git a/e2e/testdata/fn-eval/condition-met/resources.yaml b/e2e/testdata/fn-eval/condition-met/resources.yaml new file mode 100644 index 0000000000..bbb709d0f7 --- /dev/null +++ b/e2e/testdata/fn-eval/condition-met/resources.yaml @@ -0,0 +1,26 @@ +# 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. +apiVersion: v1 +kind: ConfigMap +metadata: + name: app-config +data: + env: production +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-app +spec: + replicas: 1 diff --git a/e2e/testdata/fn-eval/condition-not-met/.expected/config.yaml b/e2e/testdata/fn-eval/condition-not-met/.expected/config.yaml new file mode 100644 index 0000000000..5943443c03 --- /dev/null +++ b/e2e/testdata/fn-eval/condition-not-met/.expected/config.yaml @@ -0,0 +1,18 @@ +# 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. + +testType: eval +image: ghcr.io/kptdev/krm-functions-catalog/no-op +when: "resources.exists(r, r.kind == 'ConfigMap' && r.metadata.name == 'app-config')" +stdErr: "[SKIPPED]" diff --git a/e2e/testdata/fn-eval/condition-not-met/.krmignore b/e2e/testdata/fn-eval/condition-not-met/.krmignore new file mode 100644 index 0000000000..9d7a4007d6 --- /dev/null +++ b/e2e/testdata/fn-eval/condition-not-met/.krmignore @@ -0,0 +1 @@ +.expected diff --git a/e2e/testdata/fn-eval/condition-not-met/resources.yaml b/e2e/testdata/fn-eval/condition-not-met/resources.yaml new file mode 100644 index 0000000000..28807636fa --- /dev/null +++ b/e2e/testdata/fn-eval/condition-not-met/resources.yaml @@ -0,0 +1,19 @@ +# 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. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-app +spec: + replicas: 1 diff --git a/e2e/testdata/fn-render/condition/condition-mixed/.expected/config.yaml b/e2e/testdata/fn-render/condition/condition-mixed/.expected/config.yaml new file mode 100644 index 0000000000..292ec739a0 --- /dev/null +++ b/e2e/testdata/fn-render/condition/condition-mixed/.expected/config.yaml @@ -0,0 +1,7 @@ +stdErr: | + Package "condition-mixed": + [RUNNING] "ghcr.io/kptdev/krm-functions-catalog/no-op:latest" on package "condition-mixed" + [PASS] "ghcr.io/kptdev/krm-functions-catalog/no-op:latest" in 0s + [SKIPPED] "ghcr.io/kptdev/krm-functions-catalog/no-op:latest" (celCondition not met) + + Successfully executed 1 function(s) in 1 package(s). diff --git a/e2e/testdata/fn-render/condition/condition-mixed/.expected/diff.patch b/e2e/testdata/fn-render/condition/condition-mixed/.expected/diff.patch new file mode 100644 index 0000000000..8fdee0ab68 --- /dev/null +++ b/e2e/testdata/fn-render/condition/condition-mixed/.expected/diff.patch @@ -0,0 +1,22 @@ +diff --git a/Kptfile b/Kptfile +index ea7d5a8..4da1d54 100644 +--- a/Kptfile ++++ b/Kptfile +@@ -8,3 +8,17 @@ pipeline: + when: resources.exists(r, r.kind == 'ConfigMap') + - image: ghcr.io/kptdev/krm-functions-catalog/no-op + when: resources.exists(r, r.kind == 'Secret') ++status: ++ conditions: ++ - type: Rendered ++ status: "True" ++ reason: RenderSuccess ++ renderStatus: ++ mutationSteps: ++ - image: ghcr.io/kptdev/krm-functions-catalog/no-op:latest ++ exitCode: 0 ++ when: resources.exists(r, r.kind == 'ConfigMap') ++ - image: ghcr.io/kptdev/krm-functions-catalog/no-op:latest ++ exitCode: 0 ++ when: resources.exists(r, r.kind == 'Secret') ++ skipped: true diff --git a/e2e/testdata/fn-render/condition/condition-mixed/.krmignore b/e2e/testdata/fn-render/condition/condition-mixed/.krmignore new file mode 100644 index 0000000000..9d7a4007d6 --- /dev/null +++ b/e2e/testdata/fn-render/condition/condition-mixed/.krmignore @@ -0,0 +1 @@ +.expected diff --git a/e2e/testdata/fn-render/condition/condition-mixed/Kptfile b/e2e/testdata/fn-render/condition/condition-mixed/Kptfile new file mode 100644 index 0000000000..ea7d5a8974 --- /dev/null +++ b/e2e/testdata/fn-render/condition/condition-mixed/Kptfile @@ -0,0 +1,10 @@ +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: condition-mixed +pipeline: + mutators: + - image: ghcr.io/kptdev/krm-functions-catalog/no-op + when: resources.exists(r, r.kind == 'ConfigMap') + - image: ghcr.io/kptdev/krm-functions-catalog/no-op + when: resources.exists(r, r.kind == 'Secret') diff --git a/e2e/testdata/fn-render/condition/condition-mixed/resources.yaml b/e2e/testdata/fn-render/condition/condition-mixed/resources.yaml new file mode 100644 index 0000000000..292a53a80e --- /dev/null +++ b/e2e/testdata/fn-render/condition/condition-mixed/resources.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: app-config +data: + env: prod diff --git a/e2e/testdata/fn-render/validator-excludes-own-fnconfig/.expected/diff.patch b/e2e/testdata/fn-render/validator-excludes-own-fnconfig/.expected/diff.patch new file mode 100644 index 0000000000..feeff49443 --- /dev/null +++ b/e2e/testdata/fn-render/validator-excludes-own-fnconfig/.expected/diff.patch @@ -0,0 +1,17 @@ +diff --git a/Kptfile b/Kptfile +index 06ea70e..7b4e0f5 100644 +--- a/Kptfile ++++ b/Kptfile +@@ -6,3 +6,12 @@ pipeline: + validators: + - image: ghcr.io/kptdev/krm-functions-catalog/starlark:latest + configPath: starlark-val.yaml ++status: ++ conditions: ++ - type: Rendered ++ status: "True" ++ reason: RenderSuccess ++ renderStatus: ++ validationSteps: ++ - image: ghcr.io/kptdev/krm-functions-catalog/starlark:latest ++ exitCode: 0 diff --git a/e2e/testdata/fn-render/validator-excludes-own-fnconfig/Kptfile b/e2e/testdata/fn-render/validator-excludes-own-fnconfig/Kptfile new file mode 100644 index 0000000000..06ea70e3f3 --- /dev/null +++ b/e2e/testdata/fn-render/validator-excludes-own-fnconfig/Kptfile @@ -0,0 +1,8 @@ +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: validator-excludes-fnconfig +pipeline: + validators: + - image: ghcr.io/kptdev/krm-functions-catalog/starlark:latest + configPath: starlark-val.yaml diff --git a/e2e/testdata/fn-render/validator-excludes-own-fnconfig/deployment.yaml b/e2e/testdata/fn-render/validator-excludes-own-fnconfig/deployment.yaml new file mode 100644 index 0000000000..073986e91d --- /dev/null +++ b/e2e/testdata/fn-render/validator-excludes-own-fnconfig/deployment.yaml @@ -0,0 +1,6 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-deployment +spec: + replicas: 1 diff --git a/e2e/testdata/fn-render/validator-excludes-own-fnconfig/starlark-val.yaml b/e2e/testdata/fn-render/validator-excludes-own-fnconfig/starlark-val.yaml new file mode 100644 index 0000000000..7d6d5736f4 --- /dev/null +++ b/e2e/testdata/fn-render/validator-excludes-own-fnconfig/starlark-val.yaml @@ -0,0 +1,10 @@ +apiVersion: fn.kpt.dev/v1alpha1 +kind: StarlarkRun +metadata: + name: starlark-val +source: |- + def validate(resource_list): + for r in resource_list["items"]: + if r["metadata"]["name"] == "starlark-val" and r["kind"] == "StarlarkRun": + fail("validator should not see its own fn-config in items") + validate(ctx.resource_list) diff --git a/internal/docs/generated/fndocs/docs.go b/internal/docs/generated/fndocs/docs.go index b040132753..0b2b24a16e 100644 --- a/internal/docs/generated/fndocs/docs.go +++ b/internal/docs/generated/fndocs/docs.go @@ -160,7 +160,7 @@ Flags: labels. --mount: - List of storage options to enable reading from the local filesytem. By default, + List of storage options to enable reading from the local filesystem. By default, container functions can not access the local filesystem. It accepts the same options as specified on the [Docker Volumes] for ` + "`" + `docker run` + "`" + `. All volumes are mounted readonly by default. Specify ` + "`" + `rw=true` + "`" + ` to mount volumes in read-write mode. @@ -200,6 +200,13 @@ Flags: --save, s: Save the function image and fn-config to Kptfile. Require ` + "`" + ` + "` + "`" + `" + ` + "`" + `--image` + "`" + ` + "` + "`" + `" + ` + "`" + `. + --when: + A CEL expression that determines whether the function should be run. + The expression is evaluated against the input resources and must return a boolean. + If it returns true (or is not specified), the function executes normally. + If it returns false, the function is skipped. + Example: --when "resources.exists(r, r.kind == 'ConfigMap')" + Environment Variables: diff --git a/pkg/fn/runtime/condition_test.go b/pkg/fn/runtime/condition_test.go index 8232ef448d..7fd9d90a2b 100644 --- a/pkg/fn/runtime/condition_test.go +++ b/pkg/fn/runtime/condition_test.go @@ -43,7 +43,7 @@ func TestFunctionRunner_Conditions(t *testing.T) { testCases := []struct { name string fn *kptfile.Function - condition string + celCond string expectRun bool }{ { @@ -51,7 +51,7 @@ func TestFunctionRunner_Conditions(t *testing.T) { fn: &kptfile.Function{ Image: runneroptions.FuncGenPkgContext, }, - condition: "resources.exists(r, r.kind == 'ConfigMap')", + celCond: "resources.exists(r, r.kind == 'ConfigMap')", expectRun: true, }, { @@ -59,7 +59,7 @@ func TestFunctionRunner_Conditions(t *testing.T) { fn: &kptfile.Function{ Image: runneroptions.FuncGenPkgContext, }, - condition: "resources.exists(r, r.kind == 'Deployment')", + celCond: "resources.exists(r, r.kind == 'Deployment')", expectRun: false, }, { @@ -67,7 +67,7 @@ func TestFunctionRunner_Conditions(t *testing.T) { fn: &kptfile.Function{ Exec: "my-exec", }, - condition: "resources.size() > 0", + celCond: "resources.size() > 0", expectRun: true, }, { @@ -75,14 +75,14 @@ func TestFunctionRunner_Conditions(t *testing.T) { fn: &kptfile.Function{ Exec: "my-exec", }, - condition: "resources.size() == 0", + celCond: "resources.size() == 0", expectRun: false, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - tc.fn.CelCondition = tc.condition + tc.fn.CelCondition = tc.celCond results := fnresult.NewResultList() // Mock runner options diff --git a/pkg/lib/runneroptions/celenv.go b/pkg/lib/runneroptions/celenv.go index 644a6e35f6..8c7ab955ad 100644 --- a/pkg/lib/runneroptions/celenv.go +++ b/pkg/lib/runneroptions/celenv.go @@ -31,8 +31,8 @@ type CELEnvironment struct { } // NewCELEnvironment creates a new CELEnvironment with the standard KRM variable bindings. -// Includes cel-go built-in extensions and k8s-specific validators (IP, CIDR, Quantity, SemVer) -// from k8s.io/apiserver/pkg/cel/library for full Kubernetes CEL compatibility. +// Includes cel-go built-in extensions (strings, sets, lists, comprehensions) for +// evaluating CEL expressions against KRM resources. func NewCELEnvironment() (*CELEnvironment, error) { env, err := cel.NewEnv( cel.Variable("resources", cel.ListType(cel.DynType)), @@ -54,12 +54,12 @@ func NewCELEnvironment() (*CELEnvironment, error) { // EvaluateCondition compiles and evaluates a CEL condition against a list of KRM resources. // Returns true if the condition is met, false otherwise. // An empty condition always returns true (function executes unconditionally). -func (e *CELEnvironment) EvaluateCondition(ctx context.Context, condition string, resources []*yaml.RNode, checkFrequency uint, costLimit uint64) (bool, error) { - if condition == "" { +func (e *CELEnvironment) EvaluateCondition(ctx context.Context, celCond string, resources []*yaml.RNode, checkFrequency uint, costLimit uint64) (bool, error) { + if celCond == "" { return true, nil } - ast, issues := e.env.Compile(condition) + ast, issues := e.env.Compile(celCond) if issues != nil && issues.Err() != nil { return false, fmt.Errorf("failed to compile CEL expression: %w", issues.Err()) } @@ -109,13 +109,9 @@ func resourcesToList(resources []*yaml.RNode) ([]any, error) { } func resourceToMap(resource *yaml.RNode) (map[string]any, error) { - node := resource.YNode() - if node == nil { - return nil, fmt.Errorf("resource has nil yaml.Node") - } - var result map[string]any - if err := node.Decode(&result); err != nil { - return nil, fmt.Errorf("failed to decode resource: %w", err) + result, err := resource.Map() + if err != nil { + return nil, err } ensureMetadata(result) return result, nil diff --git a/pkg/lib/runneroptions/celenv_test.go b/pkg/lib/runneroptions/celenv_test.go new file mode 100644 index 0000000000..3ffc96bfbb --- /dev/null +++ b/pkg/lib/runneroptions/celenv_test.go @@ -0,0 +1,54 @@ +// 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 runneroptions + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +func TestEvaluateCondition_ResourceToMap(t *testing.T) { + env, err := NewCELEnvironment() + require.NoError(t, err) + + resource, err := yaml.Parse("apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: test\ndata:\n key: val\n") + require.NoError(t, err) + + // Exercises the refactored resourceToMap (RNode.Map() + ensureMetadata) path + result, err := env.EvaluateCondition(context.Background(), + `resources.exists(r, r.kind == "ConfigMap" && r.metadata.name == "test")`, + []*yaml.RNode{resource}, 100, 1000000) + require.NoError(t, err) + assert.True(t, result) +} + +func TestEvaluateCondition_EnsureMetadataDefaults(t *testing.T) { + env, err := NewCELEnvironment() + require.NoError(t, err) + + // Resource missing metadata — exercises ensureMetadata defaulting + resource, err := yaml.Parse("apiVersion: v1\nkind: ConfigMap\ndata:\n key: val\n") + require.NoError(t, err) + + result, err := env.EvaluateCondition(context.Background(), + `resources.exists(r, r.metadata.name == "")`, + []*yaml.RNode{resource}, 100, 1000000) + require.NoError(t, err) + assert.True(t, result) +} diff --git a/pkg/live/load.go b/pkg/live/load.go index be39fa417e..7d39bcf7ea 100644 --- a/pkg/live/load.go +++ b/pkg/live/load.go @@ -206,7 +206,7 @@ type InventoryFilter struct { } func (i *InventoryFilter) Filter(object *yaml.RNode) (*yaml.RNode, error) { - if GroupVersionKindForObject(object).String() != kptfilev1.KptFileGVK().String() { + if GroupVersionKindForObject(object) != kptfilev1.KptFileGVK() { return object, nil } @@ -241,7 +241,7 @@ func GroupVersionKindForObject(object *yaml.RNode) schema.GroupVersionKind { } func (r *RGFilter) Filter(object *yaml.RNode) (*yaml.RNode, error) { - if GroupVersionKindForObject(object).String() != rgfilev1alpha1.ResourceGroupGVK().String() { + if GroupVersionKindForObject(object) != rgfilev1alpha1.ResourceGroupGVK() { return object, nil } diff --git a/pkg/live/rgstream.go b/pkg/live/rgstream.go index 9460718120..dd5da263e0 100644 --- a/pkg/live/rgstream.go +++ b/pkg/live/rgstream.go @@ -1,4 +1,4 @@ -// Copyright 2020 The kpt Authors +// Copyright 2020,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. @@ -31,8 +31,8 @@ import ( var ( excludedGKs = []schema.GroupKind{ - {Group: kptfilev1.KptFileGVK().Group, Kind: kptfilev1.KptFileGVK().Kind}, - {Group: rgfilev1alpha1.ResourceGroupGVK().Group, Kind: rgfilev1alpha1.ResourceGroupGVK().Kind}, + kptfilev1.KptFileGVK().GroupKind(), + rgfilev1alpha1.ResourceGroupGVK().GroupKind(), } ) diff --git a/pkg/test/runner/config.go b/pkg/test/runner/config.go index c5dfb6a4eb..68ee3ece0f 100644 --- a/pkg/test/runner/config.go +++ b/pkg/test/runner/config.go @@ -48,6 +48,8 @@ type EvalTestCaseConfig struct { FnConfig string `json:"fnConfig,omitempty" yaml:"fnConfig,omitempty"` // fnConfigUniquePath is an absolute, OS-specific path to function config file. fnConfigUniquePath kptfilev1.UniquePath + // When is a CEL expression that determines if the function should be run. + When string `json:"when,omitempty" yaml:"when,omitempty"` } // TestCaseConfig contains the config information for the test case diff --git a/pkg/test/runner/runner.go b/pkg/test/runner/runner.go index 2163c41a8e..a97a304425 100644 --- a/pkg/test/runner/runner.go +++ b/pkg/test/runner/runner.go @@ -216,6 +216,9 @@ func (r *Runner) runFnEval() error { if r.testCase.Config.EvalConfig.IncludeMetaResources { kptArgs = append(kptArgs, "--include-meta-resources") } + if r.testCase.Config.EvalConfig.When != "" { + kptArgs = append(kptArgs, "--when", r.testCase.Config.EvalConfig.When) + } // args must be appended last if len(r.testCase.Config.EvalConfig.Args) > 0 { kptArgs = append(kptArgs, "--") diff --git a/thirdparty/cmdconfig/commands/cmdeval/cmdeval.go b/thirdparty/cmdconfig/commands/cmdeval/cmdeval.go index 064c99bd49..8888368186 100644 --- a/thirdparty/cmdconfig/commands/cmdeval/cmdeval.go +++ b/thirdparty/cmdconfig/commands/cmdeval/cmdeval.go @@ -119,7 +119,7 @@ func GetEvalFnRunner(ctx context.Context, parent string) *EvalFnRunner { r.Command.Flags().StringArrayVar( &r.excludeLabels, "exclude-labels", []string{}, "exclude resources matching the given labels") r.Command.Flags().StringVar( - &r.CelCondition, "condition", "", "conditional expression to determine if function should be run") + &r.CelCondition, "when", "", "CEL expression to determine if the function should be run") if err := r.Command.Flags().MarkHidden("include-meta-resources"); err != nil { panic(err) From 5b19062ad3e8d23b3fc5b36c40af943e5f7e7bbb Mon Sep 17 00:00:00 2001 From: Aravindhan Ayyanathan Date: Mon, 31 Aug 2026 10:36:54 +0100 Subject: [PATCH 14/29] Docs: Rationalize and document local-config annotation in the book (#4679) Signed-off-by: Aravindhan Ayyanathan --- .../content/en/book/02-concepts/_index.md | 23 ++++++++++ .../en/book/04-using-functions/_index.md | 42 +++++++++++++++++++ .../en/book/06-deploying-packages/_index.md | 6 +++ 3 files changed, 71 insertions(+) diff --git a/documentation/content/en/book/02-concepts/_index.md b/documentation/content/en/book/02-concepts/_index.md index e740ad97f2..763c4c12e9 100644 --- a/documentation/content/en/book/02-concepts/_index.md +++ b/documentation/content/en/book/02-concepts/_index.md @@ -172,6 +172,29 @@ Details of how to work with packages are set out in [Chapter 3](../03-packages). We will go into details of how to work with packages in [Chapter 3]({{% relref "/book/03-packages" %}}). >>>>>>> main +### Local Configuration + +A package can contain resources that are used only during rendering and should not +be applied to the cluster. These are called local configuration resources. +Common examples include function configs (referenced via `configPath`) and helper +resources used as input to pipeline functions. + +You mark a resource as local by adding the `config.kubernetes.io/local-config` +annotation: + +```yaml +metadata: + annotations: + config.kubernetes.io/local-config: "true" +``` + +When you deploy the package with `kpt live apply`, local configuration resources +are automatically filtered out. They exist in the package solely to support the +pipeline. See [Chapter 4]({{% relref "/book/04-using-functions" %}}) for how function configs use +this annotation, and the +[`local-config` annotation reference]({{% relref "/reference/annotations/local-config" %}}) for +full details. + ## Workflows In this section, we will describe the typical workflows in kpt. The word _typical_ is used here because there is no single correct way of using kpt. A diff --git a/documentation/content/en/book/04-using-functions/_index.md b/documentation/content/en/book/04-using-functions/_index.md index d2ff864d88..6e2bf08c0a 100644 --- a/documentation/content/en/book/04-using-functions/_index.md +++ b/documentation/content/en/book/04-using-functions/_index.md @@ -305,10 +305,16 @@ apiVersion: v1 kind: ConfigMap metadata: name: labels + annotations: + config.kubernetes.io/local-config: "true" data: tier: mysql ``` +Note the `local-config` annotation: it ensures this resource is not applied to +the cluster when you run `kpt live apply`. See +[Marking function configs as local](#marking-function-configs-as-local) below. + #### `configMap` Many functions take a `functionConfig` of kind `ConfigMap`, since they only need simple key/value pairs as an argument. For convenience, there is a way to inline the key/value pairs in the `Kptfile`. @@ -368,6 +374,42 @@ The `configRef` fields are as follows: The reference must match exactly one resource in the package. An error is raised if zero or multiple resources match. +### Marking function configs as local + +When you use `configPath` to reference a function config file, that resource is +automatically excluded from the pipeline input (it is not passed to functions as +a regular resource). However, the resource still exists in the package and will +be sent to the cluster when you run `kpt live apply`, unless you mark it as +local configuration. + +To prevent a function config from being applied to the cluster, add the +`config.kubernetes.io/local-config` annotation: + +```yaml +# wordpress/mysql/labels.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: labels + annotations: + config.kubernetes.io/local-config: "true" +data: + tier: mysql +``` + +Resources with this annotation set to any value other than `"false"` are filtered +out during `kpt live apply`. They remain in the package for use by the pipeline +but are never sent to the cluster. + +This annotation is useful for: + +- Function configs referenced by `configPath` +- Template or helper resources used only during rendering +- Any resource that should exist in the package but not on the cluster + +For full details on the annotation schema and behavior, see the +[`local-config` annotation reference]({{% relref "/reference/annotations/local-config" %}}). + ### Specifying function `name` The functions can optionally be named using the `pipeline.mutators.name` field or the `pipeline.validators.name` field to identify a function. diff --git a/documentation/content/en/book/06-deploying-packages/_index.md b/documentation/content/en/book/06-deploying-packages/_index.md index da144387c5..84d6b9b173 100644 --- a/documentation/content/en/book/06-deploying-packages/_index.md +++ b/documentation/content/en/book/06-deploying-packages/_index.md @@ -129,6 +129,12 @@ Once a package is applied to the cluster, do not change the `ResourceGroup` CR. Once you have initialized the package, you can deploy it using `kpt live apply`. +Note that not all resources in the package are applied to the cluster. Resources +annotated with `config.kubernetes.io/local-config: "true"` are automatically +filtered out. These are typically function configs or helper resources used only +during rendering. The `Kptfile` itself is also excluded. For more details, see +the [`local-config` annotation reference]({{% relref "/reference/annotations/local-config" %}}). + The `wordpress` package requires a `Secret` containing the mysql password. Let's create that first: From 39803ff5d6dbc9c420dbfcb4931b23c449b8cee5 Mon Sep 17 00:00:00 2001 From: mozesl-nokia Date: Mon, 31 Aug 2026 13:52:29 +0200 Subject: [PATCH 15/29] Bump API dependency for v1.0.0- #4718 (#4718) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mózes László Máté --- go.mod | 4 +- go.sum | 4 +- release/README.md | 14 ++ scripts/generate-folder-release-notes.sh | 303 +++++++++++++++++++++++ 4 files changed, 321 insertions(+), 4 deletions(-) create mode 100755 scripts/generate-folder-release-notes.sh diff --git a/go.mod b/go.mod index 7619e95383..decb51d47c 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/kptdev/kpt go 1.26.5 -replace github.com/kptdev/kpt/api => ./api +// replace github.com/kptdev/kpt/api => ./api require ( github.com/Masterminds/semver/v3 v3.5.0 @@ -14,7 +14,7 @@ require ( github.com/google/go-cmp v0.7.0 github.com/google/go-containerregistry v0.21.6 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 - github.com/kptdev/kpt/api v0.0.4 + github.com/kptdev/kpt/api v1.0.0 github.com/kptdev/krm-functions-sdk/go/fn v1.1.1 github.com/otiai10/copy v1.14.1 github.com/philopon/go-toposort v0.0.0-20170620085441-9be86dbd762f diff --git a/go.sum b/go.sum index fb7881e0fb..f12f6775f9 100644 --- a/go.sum +++ b/go.sum @@ -116,8 +116,8 @@ 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/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= -github.com/kptdev/kpt/api v0.0.4 h1:kp2Jm1FUv6A316nO+ddZMXR4nRgcN9MBlY++06Q3PWo= -github.com/kptdev/kpt/api v0.0.4/go.mod h1:D/WM1LJ/HvHt3cHxDs2mF6mXkA37BbD1nrcsAVWgbfE= +github.com/kptdev/kpt/api v1.0.0 h1:D5gdcqEQ9p3ADb6G+ugjXJ5vlBZ07KP89D3pNxyFpIY= +github.com/kptdev/kpt/api v1.0.0/go.mod h1:D/WM1LJ/HvHt3cHxDs2mF6mXkA37BbD1nrcsAVWgbfE= github.com/kptdev/krm-functions-sdk/go/fn v1.1.1 h1:F/tdu0FSWSnLaAV+AC8CVS0YGpUwkVnAs+O5NHPRmQU= github.com/kptdev/krm-functions-sdk/go/fn v1.1.1/go.mod h1:rPrLdh02mfqq5PjZRrMr5mhRA6dkjKm7O4bm5VZJWOg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= diff --git a/release/README.md b/release/README.md index 6e6b4118d5..42962238d2 100644 --- a/release/README.md +++ b/release/README.md @@ -32,6 +32,20 @@ That `@v1.0.0` form is the **module version**; the corresponding **git tag** in Tags must use the **`api/`** prefix so the version matches the module subdirectory (see [Go modules: VCS version](https://go.dev/ref/mod#vcs-version)). +### Release notes + +GitHub's built-in "Generate release notes" includes PRs for the whole repository. For an API release, generate notes scoped to `api/` instead: + +```shell +scripts/generate-folder-release-notes.sh --folder api --new-tag v1.2.3 +``` + +That calls the GitHub Release Notes API, then keeps only bullets whose PRs also appear in `git log` for `api/` between the previous `api/v*` semver tag and `HEAD`. +Pass `--previous-tag` to pick a different start (version or full tag, for example `v1.2.2` or `api/v1.2.2`). +Use `-o FILE` to write the notes, and `--attribution` to prepend a note that they are folder-scoped. + +Prerequisites: `gh` (authenticated to this repository) and a clone with tags fetched (`git fetch --tags`). + ### What runs in CI When you push a tag that matches **`api/v[0-9]+.[0-9]+.[0-9]+*`** (for example `api/v1.2.3`), [.github/workflows/release-api.yml](../.github/workflows/release-api.yml) runs: it uses the Go version from [`api/go.mod`](../api/go.mod) and runs **`make api`**. diff --git a/scripts/generate-folder-release-notes.sh b/scripts/generate-folder-release-notes.sh new file mode 100755 index 0000000000..2fd32e33db --- /dev/null +++ b/scripts/generate-folder-release-notes.sh @@ -0,0 +1,303 @@ +#!/usr/bin/env bash + +# 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. + +# Generate GitHub-style release notes scoped to a single folder. +# +# Calls the GitHub Release Notes API, then keeps only bullets whose linked PR +# also appears in git history for the folder (e.g. api/). +# +# Nested-module git tags are assumed to be FOLDER/vX.Y.Z (for example api/v1.0.0). +# +# Prerequisites: bash 3.2+, gh (authenticated), git. +# +# Example (kpt API module): +# scripts/generate-folder-release-notes.sh \ +# --folder api \ +# --new-tag v1.0.1 + +set -euo pipefail + +GITHUB_REPOSITORY=${GITHUB_REPOSITORY:-"kptdev/kpt"} + +usage() { + cat <<'EOF' +Usage: generate-folder-release-notes.sh [options] + +Generate release notes for changes under FOLDER/ by filtering the output of +the GitHub Release Notes API. Git tags are FOLDER/vX.Y.Z (for example api/v1.0.0). + +Options: + --folder PATH Folder to scope notes to (required, e.g. api) + --previous-tag VERSION Previous release version (e.g. v1.0.0 or api/v1.0.0); + default: latest semver tag for the folder + --new-tag VERSION New release version (required, e.g. v1.0.1) + --ref REF Commitish for the new release (default: HEAD) + --attribution Prepend a note that notes were auto-generated (uses gh user login) + -o, --output FILE Write notes to FILE instead of stdout + -h, --help Show this help + +EOF +} + +fail() { + echo "generate-folder-release-notes.sh: $*" >&2 + exit 1 +} + +missing_value() { + fail "missing value for $1" +} + +# Strip a trailing slash so "api/" and "api" are equivalent. +normalize_folder() { + local folder="$1" + folder="${folder#./}" + while [[ "$folder" == */ ]]; do + folder="${folder%/}" + done + printf '%s' "$folder" +} + +normalize_version() { + local version="$1" + local prefix="${folder_path}/" + if [[ -n "$folder_path" && "$version" == "${prefix}"* ]]; then + version="${version#"$prefix"}" + fi + if [[ "$version" =~ ^v ]]; then + printf '%s' "$version" + else + printf 'v%s' "$version" + fi +} + +folder_tag() { + local version + version="$(normalize_version "$1")" + printf '%s/%s' "$folder_path" "$version" +} + +# Latest strict SemVer long tag for the folder (FOLDER/vMAJOR.MINOR.PATCH). +latest_folder_tag() { + git tag -l "${folder_path}/v*" --sort=v:refname | + grep -E -- '/v[0-9]+\.[0-9]+\.[0-9]+$' | + tail -n 1 || true +} + +folder_path="" +previous_version="" +new_version="" +ref="HEAD" +add_attribution=0 +output_file="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --folder|--directory|--dir) + [[ $# -ge 2 ]] || missing_value "$1" + folder_path="$2" + shift 2 + ;; + --previous-tag|--prev-tag|--prev) + [[ $# -ge 2 ]] || missing_value "$1" + previous_version="$2" + shift 2 + ;; + --new-tag|--new|--next-tag|--next) + [[ $# -ge 2 ]] || missing_value "$1" + new_version="$2" + shift 2 + ;; + --ref|--target|--target-ref) + [[ $# -ge 2 ]] || missing_value "$1" + ref="$2" + shift 2 + ;; + --attribution) + add_attribution=1 + shift + ;; + -o | --output) + [[ $# -ge 2 ]] || missing_value "$1" + output_file="$2" + shift 2 + ;; + -h | --help) + usage + exit 0 + ;; + *) + fail "unknown argument: $1 (try --help)" + ;; + esac +done + +if [[ -z "$folder_path" ]]; then + fail "--folder is required (try --help)" +fi +if [[ -z "$new_version" ]]; then + fail "--new-tag is required" +fi + +folder_path="$(normalize_folder "$folder_path")" +if [[ -z "$folder_path" ]]; then + fail "--folder must be a non-empty path (e.g. api)" +fi +if [[ ! -d "$folder_path" ]]; then + fail "folder not found: ${folder_path}/" +fi + +if [[ -z "$previous_version" ]]; then + prev_long="$(latest_folder_tag)" + if [[ -z "$prev_long" ]]; then + fail "no prior semver tag ${folder_path}/vMAJOR.MINOR.PATCH; pass --previous-tag explicitly" + fi + previous_version="${prev_long##*/}" +fi + +previous_tag="$(folder_tag "$previous_version")" +new_tag="$(folder_tag "$new_version")" + +extract_pr_number() { + local line="$1" + if [[ "$line" =~ pull/([0-9]+) ]]; then + printf '%s' "${BASH_REMATCH[1]}" + fi +} + +extract_pr_number_from_subject() { + local subject="$1" + if [[ "$subject" =~ \(#([0-9]+)\)$ ]]; then + printf '%s' "${BASH_REMATCH[1]}" + elif [[ "$subject" =~ ^Merge[[:space:]]pull[[:space:]]request[[:space:]]#([0-9]+) ]]; then + printf '%s' "${BASH_REMATCH[1]}" + fi +} + +# Discover PRs that touched the folder via git history (newline-separated, sorted). +folder_pr_numbers="$( + git log "${previous_tag}..${ref}" --format='%s' -- "${folder_path}/" | + while IFS= read -r subject; do + [[ -z "$subject" ]] && continue + pr_number="$(extract_pr_number_from_subject "$subject")" + [[ -n "$pr_number" ]] || continue + printf '%s\n' "$pr_number" + done | sort -nu +)" + +pr_in_folder() { + local pr="$1" + [[ -n "$folder_pr_numbers" ]] || return 1 + printf '%s\n' "$folder_pr_numbers" | grep -Fqx "$pr" +} + +filter_bullet_line() { + local line="$1" + local pr_number + + pr_number="$(extract_pr_number "$line")" + [[ -n "$pr_number" ]] || return 1 + pr_in_folder "$pr_number" +} + +parse_and_filter_notes() { + local body="$1" + local section="" + local whats_changed=() + local new_contributors=() + local full_changelog="" + + while IFS= read -r line || [[ -n "$line" ]]; do + case "$line" in + "## What's Changed") + section="whats_changed" + continue + ;; + "## New Contributors") + section="new_contributors" + continue + ;; + "**Full Changelog"*) + full_changelog="$line" + section="" + continue + ;; + esac + + if [[ "$line" == "* "* ]]; then + if filter_bullet_line "$line"; then + case "$section" in + whats_changed) whats_changed+=("$line") ;; + new_contributors) new_contributors+=("$line") ;; + esac + fi + fi + done <<< "$body" + + printf '%s\n' "## What's Changed" + if [[ ${#whats_changed[@]} -eq 0 ]]; then + printf "* No changes under \`%s/\` in this range.\n" "$folder_path" + else + printf '%s\n' "${whats_changed[@]}" + fi + + if [[ ${#new_contributors[@]} -gt 0 ]]; then + printf '\n%s\n' "## New Contributors" + printf '%s\n' "${new_contributors[@]}" + fi + + if [[ -n "$full_changelog" ]]; then + printf '\n%s\n' "$full_changelog" + fi +} + +gh_user_login() { + gh api user --jq .login 2>/dev/null || true +} + +prepend_attribution() { + local notes="$1" + local login + + login="$(gh_user_login)" + if [[ -n "$login" ]]; then + printf "> **Note:** These release notes were auto-generated by @%s and include only changes under \`%s/\`.\n\n%s\n" \ + "$login" "$folder_path" "$notes" + else + printf "> **Note:** These release notes were auto-generated and include only changes under \`%s/\`.\n\n%s\n" \ + "$folder_path" "$notes" + fi +} + +notes_body="$( + gh api "repos/${GITHUB_REPOSITORY}/releases/generate-notes" \ + -f tag_name="$new_tag" \ + -f previous_tag_name="$previous_tag" \ + -f target_commitish="$ref" \ + --jq .body +)" + +filtered_notes="$(parse_and_filter_notes "$notes_body")" + +if [[ "$add_attribution" -eq 1 ]]; then + filtered_notes="$(prepend_attribution "$filtered_notes")" +fi + +if [[ -n "$output_file" ]]; then + printf '%s\n' "$filtered_notes" > "$output_file" +else + printf '%s\n' "$filtered_notes" +fi From 92a6148a71a6b5c4c87ebe567ca5a43f21e2e8af Mon Sep 17 00:00:00 2001 From: mozesl-nokia Date: Tue, 1 Sep 2026 08:18:38 +0200 Subject: [PATCH 16/29] use the go version of the main module (#4719) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mózes László Máté --- .github/workflows/release-api.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-api.yml b/.github/workflows/release-api.yml index 6bcbddf1d7..45286ed0c7 100644 --- a/.github/workflows/release-api.yml +++ b/.github/workflows/release-api.yml @@ -30,7 +30,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version-file: api/go.mod + go-version-file: go.mod cache-dependency-path: api/go.sum - name: Check for replace directives in api/go.mod uses: ./.github/actions/check-go-mod-replace From 3c346b9892dbe507413dac081b08cc43e252fb48 Mon Sep 17 00:00:00 2001 From: Aravindhan Ayyanathan Date: Tue, 1 Sep 2026 13:56:52 +0100 Subject: [PATCH 17/29] Docs: Fix installation page URLs, versioning, and SLSA verification instructions (#4717) * Docs: Fix installation page URLs, versioning, and SLSA verification instructions Signed-off-by: Aravindhan Ayyanathan * Use tabpane shortcodes for platform-specific install commands Signed-off-by: Aravindhan Ayyanathan --------- Signed-off-by: Aravindhan Ayyanathan --- .../content/en/installation/kpt-cli.md | 111 ++++++++++++++---- .../layouts/shortcodes/kpt_version.html | 2 +- 2 files changed, 90 insertions(+), 23 deletions(-) diff --git a/documentation/content/en/installation/kpt-cli.md b/documentation/content/en/installation/kpt-cli.md index 15445e9fe5..076dff055d 100644 --- a/documentation/content/en/installation/kpt-cli.md +++ b/documentation/content/en/installation/kpt-cli.md @@ -6,28 +6,104 @@ Users can get kpt CLI in a variety of ways: Download pre-compiled binaries: -- [Linux (amd64)][linux-amd64] -- [Linux (arm64)][linux-arm64] -- [MacOS (amd64)][darwin-amd64] -- [MacOS (arm64)][darwin-arm64] +{{< tabpane text=true >}} +{{% tab header="Linux amd64" %}} +```shell +curl -LO https://github.com/kptdev/kpt/releases/download/v{{< kpt_version >}}/kpt_linux_amd64-{{< kpt_version >}}.tar.gz +``` +{{% /tab %}} +{{% tab header="Linux arm64" %}} +```shell +curl -LO https://github.com/kptdev/kpt/releases/download/v{{< kpt_version >}}/kpt_linux_arm64-{{< kpt_version >}}.tar.gz +``` +{{% /tab %}} +{{% tab header="macOS amd64" %}} +```shell +curl -LO https://github.com/kptdev/kpt/releases/download/v{{< kpt_version >}}/kpt_darwin_amd64-{{< kpt_version >}}.tar.gz +``` +{{% /tab %}} +{{% tab header="macOS arm64" %}} +```shell +curl -LO https://github.com/kptdev/kpt/releases/download/v{{< kpt_version >}}/kpt_darwin_arm64-{{< kpt_version >}}.tar.gz +``` +{{% /tab %}} +{{< /tabpane >}} Optionally verify the [SLSA3 signatures](https://slsa.dev/) generated using the OpenSSF's [slsa-framework/slsa-github-generator](https://github.com/slsa-framework/slsa-github-generator) during the release -process. To verify a release binary: +process. To verify a release archive: 1. Install the verification tool from [slsa-framework/slsa-verifier#installation](https://github.com/slsa-framework/slsa-verifier#installation). -2. Download the signature file `multiple.intoto.jsonl` from the [GitHub releases page](https://github.com/kptdev/kpt/releases). +2. Download the signature file: + +```shell +curl -LO https://github.com/kptdev/kpt/releases/download/v{{< kpt_version >}}/multiple.intoto.jsonl +``` 3. Run the verifier: +{{< tabpane text=true >}} +{{% tab header="Linux amd64" %}} +```shell +slsa-verifier verify-artifact \ + --provenance-path multiple.intoto.jsonl \ + --source-uri github.com/kptdev/kpt \ + --source-versioned-tag v{{< kpt_version >}} \ + kpt_linux_amd64-{{< kpt_version >}}.tar.gz +``` +{{% /tab %}} +{{% tab header="Linux arm64" %}} +```shell +slsa-verifier verify-artifact \ + --provenance-path multiple.intoto.jsonl \ + --source-uri github.com/kptdev/kpt \ + --source-versioned-tag v{{< kpt_version >}} \ + kpt_linux_arm64-{{< kpt_version >}}.tar.gz +``` +{{% /tab %}} +{{% tab header="macOS amd64" %}} ```shell -slsa-verifier verify-artifact --provenance-path multiple.intoto.jsonl --source-uri github.com/kptdev/kpt --source-versioned-tag kpt__ +slsa-verifier verify-artifact \ + --provenance-path multiple.intoto.jsonl \ + --source-uri github.com/kptdev/kpt \ + --source-versioned-tag v{{< kpt_version >}} \ + kpt_darwin_amd64-{{< kpt_version >}}.tar.gz ``` +{{% /tab %}} +{{% tab header="macOS arm64" %}} +```shell +slsa-verifier verify-artifact \ + --provenance-path multiple.intoto.jsonl \ + --source-uri github.com/kptdev/kpt \ + --source-versioned-tag v{{< kpt_version >}} \ + kpt_darwin_arm64-{{< kpt_version >}}.tar.gz +``` +{{% /tab %}} +{{< /tabpane >}} -On Linux and MacOS, make it executable: +After downloading (and optionally verifying), extract the archive: +{{< tabpane text=true >}} +{{% tab header="Linux amd64" %}} +```shell +tar -xzf kpt_linux_amd64-{{< kpt_version >}}.tar.gz +``` +{{% /tab %}} +{{% tab header="Linux arm64" %}} +```shell +tar -xzf kpt_linux_arm64-{{< kpt_version >}}.tar.gz +``` +{{% /tab %}} +{{% tab header="macOS amd64" %}} ```shell -chmod +x kpt +tar -xzf kpt_darwin_amd64-{{< kpt_version >}}.tar.gz ``` +{{% /tab %}} +{{% tab header="macOS arm64" %}} +```shell +tar -xzf kpt_darwin_arm64-{{< kpt_version >}}.tar.gz +``` +{{% /tab %}} +{{< /tabpane >}} On MacOS the first time, it may be necessary to open the program from the finder with _ctrl-click open_. @@ -89,20 +165,20 @@ Running kpt via Docker does not install kpt on your machine. Each `docker run .. ### `kpt` ```shell -docker run ghcr.io/kptdev/kpt:{{< kpt_version >}} version +docker run ghcr.io/kptdev/kpt:v{{< kpt_version >}} version ``` To use kpt with files on your host, mount your current directory into the container and set a working directory: ```shell -docker run --rm -v "$PWD":/workdir -w /workdir ghcr.io/kptdev/kpt:{{< kpt_version >}} pkg tree -docker run --rm -v "$PWD":/workdir -w /workdir ghcr.io/kptdev/kpt:{{< kpt_version >}} fn render +docker run --rm -v "$PWD":/workdir -w /workdir ghcr.io/kptdev/kpt:v{{< kpt_version >}} pkg tree +docker run --rm -v "$PWD":/workdir -w /workdir ghcr.io/kptdev/kpt:v{{< kpt_version >}} fn render ``` On Windows PowerShell, use `${PWD}.Path` for the current directory: ```shell -docker run --rm -v "${PWD}.Path:/workdir" -w /workdir ghcr.io/kptdev/kpt:{{< kpt_version >}} pkg tree +docker run --rm -v "${PWD}.Path:/workdir" -w /workdir ghcr.io/kptdev/kpt:v{{< kpt_version >}} pkg tree ``` This pattern ensures kpt reads and writes files under `/workdir`, which maps to your current directory on the host. @@ -166,13 +242,4 @@ kpt version [ghcr.io/kptdev/kpt]: https://github.com/kptdev/kpt/pkgs/container/kpt [cloud-sdk]: https://github.com/GoogleCloudPlatform/cloud-sdk-docker - -[linux-amd64]: -https://github.com/kptdev/kpt/releases/download/{{< kpt_version >}}/kpt_linux_amd64 -[linux-arm64]: -https://github.com/kptdev/kpt/releases/download/{{< kpt_version >}}/kpt_linux_arm64 -[darwin-amd64]: -https://github.com/kptdev/kpt/releases/download/{{< kpt_version >}}/kpt_darwin_amd64 -[darwin-arm64]: -https://github.com/kptdev/kpt/releases/download/{{< kpt_version >}}/kpt_darwin_arm64 [bash-completion]: https://github.com/scop/bash-completion#installation diff --git a/documentation/layouts/shortcodes/kpt_version.html b/documentation/layouts/shortcodes/kpt_version.html index 2b652ff4c3..5f4b51cc9d 100644 --- a/documentation/layouts/shortcodes/kpt_version.html +++ b/documentation/layouts/shortcodes/kpt_version.html @@ -1 +1 @@ -{{- getenv "HUGO_KPT_VERSION" | default .Site.Params.kpt_version -}} \ No newline at end of file +{{- (getenv "HUGO_KPT_VERSION" | default .Site.Params.kpt_version) | strings.TrimPrefix "v" -}} From b37266678f8b95cc9d9b6e6593533c0d2bb835d7 Mon Sep 17 00:00:00 2001 From: Vishnu Kothakapu <116425661+vishnukothakapu@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:56:04 +0530 Subject: [PATCH 18/29] feat: Print skip reasons in live plan output (#4715) This change captures the skip reason generated by cli-utils and prints it in the output of the kpt live plan command, improving the developer experience. Assisted-by: Antigravity:Gemini 3.1 Pro (High) Signed-off-by: Vishnu Kothakapu --- commands/alpha/live/plan/command.go | 4 +- commands/alpha/live/plan/command_test.go | 64 ++++++ pkg/live/planner/cluster.go | 89 ++++---- pkg/live/planner/cluster_test.go | 253 +++++++++++++++++++++++ 4 files changed, 366 insertions(+), 44 deletions(-) create mode 100644 commands/alpha/live/plan/command_test.go diff --git a/commands/alpha/live/plan/command.go b/commands/alpha/live/plan/command.go index 17eef9b9a9..91dda2ab9e 100644 --- a/commands/alpha/live/plan/command.go +++ b/commands/alpha/live/plan/command.go @@ -178,8 +178,10 @@ func printText(plan *kptplanner.Plan, objs []*unstructured.Unstructured, ioStrea printEntry(" ", action, ioStreams) findAndPrintDiff(action.Original, action.Updated, ContentPrefix, ioStreams) case kptplanner.Skip: - // TODO: provide more information about why the resource was skipped. printEntryWithColor("=", print.YELLOW, action, ioStreams) + if action.SkipReason != "" { + printWithPrefix(action.SkipReason, ContentPrefix, ioStreams) + } case kptplanner.Error: printEntry("!", action, ioStreams) printWithPrefix(action.Error, ContentPrefix, ioStreams) diff --git a/commands/alpha/live/plan/command_test.go b/commands/alpha/live/plan/command_test.go new file mode 100644 index 0000000000..354326383b --- /dev/null +++ b/commands/alpha/live/plan/command_test.go @@ -0,0 +1,64 @@ +package plan + +import ( + "bytes" + "testing" + + kptplanner "github.com/kptdev/kpt/pkg/live/planner" + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/cli-runtime/pkg/genericclioptions" +) + +func TestPrintTextSkip(t *testing.T) { + testCases := map[string]struct { + plan *kptplanner.Plan + expected string + }{ + "skipped resource with reason": { + plan: &kptplanner.Plan{ + Actions: []kptplanner.Action{ + { + Type: kptplanner.Skip, + Group: "apps", + Kind: "Deployment", + Name: "foo", + Namespace: "default", + SkipReason: "some skip reason", + }, + }, + }, + expected: "kpt will perform the following actions:\n\x1b[33m\t= apps/Deployment default/foo\n\x1b[0m\t\tsome skip reason\n\n", + }, + "skipped resource without reason": { + plan: &kptplanner.Plan{ + Actions: []kptplanner.Action{ + { + Type: kptplanner.Skip, + Group: "apps", + Kind: "Deployment", + Name: "foo", + Namespace: "default", + }, + }, + }, + expected: "kpt will perform the following actions:\n\x1b[33m\t= apps/Deployment default/foo\n\x1b[0m\n", + }, + } + + for tn := range testCases { + tc := testCases[tn] + t.Run(tn, func(t *testing.T) { + var buf bytes.Buffer + ioStreams := genericclioptions.IOStreams{ + Out: &buf, + ErrOut: &buf, + } + var objs []*unstructured.Unstructured + err := printText(tc.plan, objs, ioStreams) + assert.NoError(t, err) + + assert.Equal(t, tc.expected, buf.String()) + }) + } +} diff --git a/pkg/live/planner/cluster.go b/pkg/live/planner/cluster.go index e537c2b6aa..673d9b77aa 100644 --- a/pkg/live/planner/cluster.go +++ b/pkg/live/planner/cluster.go @@ -110,14 +110,15 @@ type Plan struct { } type Action struct { - Type ActionType - Group string - Kind string - Name string - Namespace string - Original *unstructured.Unstructured - Updated *unstructured.Unstructured - Error string + Type ActionType + Group string + Kind string + Name string + Namespace string + Original *unstructured.Unstructured + Updated *unstructured.Unstructured + Error string + SkipReason string } type Options struct { @@ -195,63 +196,65 @@ func (r *ClusterPlanner) dryRunForPlan( } func handleApplyEvent(e event.Event, a Action) Action { + if e.ApplyEvent.Status == event.ApplySkipped { + a.Type = Skip + if e.ApplyEvent.Error != nil { + a.SkipReason = e.ApplyEvent.Error.Error() + } + return a + } if e.ApplyEvent.Error != nil { a.Type = Error a.Error = e.ApplyEvent.Error.Error() - } else { - switch e.ApplyEvent.Status { - case event.ApplySkipped: - a.Type = Skip - case event.ApplySuccessful: - a.Updated = e.ApplyEvent.Resource - if a.Original != nil { - // TODO: Unclear if we should diff the full resources here. It doesn't work - // well with client-side apply as the managedFields property shows up as - // changes. It also means there is a race with controllers that might change - // the status of resources. - if reflect.DeepEqual(a.Original, a.Updated) { - a.Type = Unchanged - } else { - a.Type = Update - } + } else if e.ApplyEvent.Status == event.ApplySuccessful { + a.Updated = e.ApplyEvent.Resource + if a.Original != nil { + // TODO: Unclear if we should diff the full resources here. It doesn't work + // well with client-side apply as the managedFields property shows up as + // changes. It also means there is a race with controllers that might change + // the status of resources. + if reflect.DeepEqual(a.Original, a.Updated) { + a.Type = Unchanged } else { - a.Type = Create + a.Type = Update } + } else { + a.Type = Create } } return a } func handlePruneEvent(e event.Event, a Action) Action { + if e.PruneEvent.Status == event.PruneSkipped { + a.Type = Skip + if e.PruneEvent.Error != nil { + a.SkipReason = e.PruneEvent.Error.Error() + } + return a + } if e.PruneEvent.Error != nil { a.Type = Error a.Error = e.PruneEvent.Error.Error() - } else { - switch e.PruneEvent.Status { - case event.PruneSuccessful: - a.Type = Delete - // Lifecycle directives can cause resources to remain in the - // live state even if they would normally be pruned. - // TODO: Handle reason for skipped resources that has recently - // been added to the actuation library. - case event.PruneSkipped: - a.Type = Skip - } + } else if e.PruneEvent.Status == event.PruneSuccessful { + a.Type = Delete } return a } func handleDeleteEvent(e event.Event, a Action) Action { + if e.DeleteEvent.Status == event.DeleteSkipped { + a.Type = Skip + if e.DeleteEvent.Error != nil { + a.SkipReason = e.DeleteEvent.Error.Error() + } + return a + } if e.DeleteEvent.Error != nil { a.Type = Error a.Error = e.DeleteEvent.Error.Error() - } else { - switch e.DeleteEvent.Status { - case event.DeleteSuccessful: - a.Type = Delete - case event.DeleteSkipped: - a.Type = Skip - } + } else if e.DeleteEvent.Status == event.DeleteSuccessful { + a.Type = Delete } return a } diff --git a/pkg/live/planner/cluster_test.go b/pkg/live/planner/cluster_test.go index f72a43f035..5fbff89820 100644 --- a/pkg/live/planner/cluster_test.go +++ b/pkg/live/planner/cluster_test.go @@ -16,6 +16,7 @@ package planner import ( "context" + "fmt" "testing" "github.com/google/go-cmp/cmp" @@ -93,6 +94,258 @@ func TestClusterPlanner(t *testing.T) { }, }, }, + "skipped resource with reason": { + resources: []*unstructured.Unstructured{ + testutil.Unstructured(t, deploymentYAML), + }, + clusterResources: []*unstructured.Unstructured{}, + events: []event.Event{ + { + Type: event.InitType, + InitEvent: event.InitEvent{ + ActionGroups: event.ActionGroupList{ + { + Action: event.ApplyAction, + Name: "apply-1", + Identifiers: []object.ObjMetadata{ + testutil.ToIdentifier(t, deploymentYAML), + }, + }, + }, + }, + }, + { + Type: event.ApplyType, + ApplyEvent: event.ApplyEvent{ + GroupName: "apply-1", + Identifier: testutil.ToIdentifier(t, deploymentYAML), + Status: event.ApplySkipped, + Error: fmt.Errorf("some skip reason"), + }, + }, + }, + expectedPlan: &Plan{ + Actions: []Action{ + { + Type: Skip, + Name: "foo", + Namespace: "default", + Group: "apps", + Kind: "Deployment", + SkipReason: "some skip reason", + }, + }, + }, + }, + "skipped apply resource without reason": { + resources: []*unstructured.Unstructured{ + testutil.Unstructured(t, deploymentYAML), + }, + clusterResources: []*unstructured.Unstructured{}, + events: []event.Event{ + { + Type: event.InitType, + InitEvent: event.InitEvent{ + ActionGroups: event.ActionGroupList{ + { + Action: event.ApplyAction, + Name: "apply-1", + Identifiers: []object.ObjMetadata{ + testutil.ToIdentifier(t, deploymentYAML), + }, + }, + }, + }, + }, + { + Type: event.ApplyType, + ApplyEvent: event.ApplyEvent{ + GroupName: "apply-1", + Identifier: testutil.ToIdentifier(t, deploymentYAML), + Status: event.ApplySkipped, + }, + }, + }, + expectedPlan: &Plan{ + Actions: []Action{ + { + Type: Skip, + Name: "foo", + Namespace: "default", + Group: "apps", + Kind: "Deployment", + }, + }, + }, + }, + "skipped prune resource with reason": { + resources: []*unstructured.Unstructured{ + testutil.Unstructured(t, deploymentYAML), + }, + clusterResources: []*unstructured.Unstructured{}, + events: []event.Event{ + { + Type: event.InitType, + InitEvent: event.InitEvent{ + ActionGroups: event.ActionGroupList{ + { + Action: event.PruneAction, + Name: "prune-1", + Identifiers: []object.ObjMetadata{ + testutil.ToIdentifier(t, deploymentYAML), + }, + }, + }, + }, + }, + { + Type: event.PruneType, + PruneEvent: event.PruneEvent{ + GroupName: "prune-1", + Identifier: testutil.ToIdentifier(t, deploymentYAML), + Status: event.PruneSkipped, + Error: fmt.Errorf("some skip reason"), + }, + }, + }, + expectedPlan: &Plan{ + Actions: []Action{ + { + Type: Skip, + Name: "foo", + Namespace: "default", + Group: "apps", + Kind: "Deployment", + SkipReason: "some skip reason", + }, + }, + }, + }, + "skipped prune resource without reason": { + resources: []*unstructured.Unstructured{ + testutil.Unstructured(t, deploymentYAML), + }, + clusterResources: []*unstructured.Unstructured{}, + events: []event.Event{ + { + Type: event.InitType, + InitEvent: event.InitEvent{ + ActionGroups: event.ActionGroupList{ + { + Action: event.PruneAction, + Name: "prune-1", + Identifiers: []object.ObjMetadata{ + testutil.ToIdentifier(t, deploymentYAML), + }, + }, + }, + }, + }, + { + Type: event.PruneType, + PruneEvent: event.PruneEvent{ + GroupName: "prune-1", + Identifier: testutil.ToIdentifier(t, deploymentYAML), + Status: event.PruneSkipped, + }, + }, + }, + expectedPlan: &Plan{ + Actions: []Action{ + { + Type: Skip, + Name: "foo", + Namespace: "default", + Group: "apps", + Kind: "Deployment", + }, + }, + }, + }, + "skipped delete resource with reason": { + resources: []*unstructured.Unstructured{ + testutil.Unstructured(t, deploymentYAML), + }, + clusterResources: []*unstructured.Unstructured{}, + events: []event.Event{ + { + Type: event.InitType, + InitEvent: event.InitEvent{ + ActionGroups: event.ActionGroupList{ + { + Action: event.DeleteAction, + Name: "delete-1", + Identifiers: []object.ObjMetadata{ + testutil.ToIdentifier(t, deploymentYAML), + }, + }, + }, + }, + }, + { + Type: event.DeleteType, + DeleteEvent: event.DeleteEvent{ + GroupName: "delete-1", + Identifier: testutil.ToIdentifier(t, deploymentYAML), + Status: event.DeleteSkipped, + Error: fmt.Errorf("some skip reason"), + }, + }, + }, + expectedPlan: &Plan{ + Actions: []Action{ + { + Type: Skip, + Name: "foo", + Namespace: "default", + Group: "apps", + Kind: "Deployment", + SkipReason: "some skip reason", + }, + }, + }, + }, + "skipped delete resource without reason": { + resources: []*unstructured.Unstructured{ + testutil.Unstructured(t, deploymentYAML), + }, + clusterResources: []*unstructured.Unstructured{}, + events: []event.Event{ + { + Type: event.InitType, + InitEvent: event.InitEvent{ + ActionGroups: event.ActionGroupList{ + { + Action: event.DeleteAction, + Name: "delete-1", + Identifiers: []object.ObjMetadata{ + testutil.ToIdentifier(t, deploymentYAML), + }, + }, + }, + }, + }, + { + Type: event.DeleteType, + DeleteEvent: event.DeleteEvent{ + GroupName: "delete-1", + Identifier: testutil.ToIdentifier(t, deploymentYAML), + Status: event.DeleteSkipped, + }, + }, + }, + expectedPlan: &Plan{ + Actions: []Action{ + { + Type: Skip, + Name: "foo", + Namespace: "default", + Group: "apps", + Kind: "Deployment", + }, + }, + }, + }, } for tn := range testCases { From 47a6c654a1fd157c183667e17aeebd9fd15972d7 Mon Sep 17 00:00:00 2001 From: Kushal Harish Naidu Date: Wed, 2 Sep 2026 10:33:01 +0100 Subject: [PATCH 19/29] Fix copy-merge to preserve locally-added files in upstream-deleted directories (#4720) * Fix copy-merge to preserve locally-added files in upstream-deleted directories Signed-off-by: Kushal Harish Naidu * Add negative test for file deletes during copy-merge Signed-off-by: Kushal Harish Naidu * Update wording in comment Signed-off-by: Kushal Harish Naidu --------- Signed-off-by: Kushal Harish Naidu --- pkg/lib/pkg/util.go | 22 ++++- pkg/lib/pkg/util_test.go | 130 +++++++++++++++++++++++++++++- pkg/lib/update/copy-merge_test.go | 14 +++- 3 files changed, 159 insertions(+), 7 deletions(-) diff --git a/pkg/lib/pkg/util.go b/pkg/lib/pkg/util.go index 077e51923d..7bcf4cf697 100644 --- a/pkg/lib/pkg/util.go +++ b/pkg/lib/pkg/util.go @@ -171,6 +171,9 @@ func CopyPackage(src, dst string, copyRootKptfile bool, matcher SubpackageMatche // RemoveStaleItems removes files and directories from the dst package that were present in the org package, // but are not present in the src package. It does not remove the root Kptfile of the dst package. +// Directories that are stale but still contain locally-added files are preserved rather than +// causing an error, since this represents a legitimate merge outcome where upstream deleted a +// directory but the local package added files to it. func RemoveStaleItems(org, src, dst string, _ bool, _ SubpackageMatcher) error { var dirsToDelete []string walkErr := filepath.Walk(dst, func(path string, info os.FileInfo, err error) error { @@ -215,11 +218,28 @@ func RemoveStaleItems(org, src, dst string, _ bool, _ SubpackageMatcher) error { if walkErr != nil { return walkErr } + // Sort directories deepest-first so children are processed before parents. sort.Slice(dirsToDelete, SubPkgFirstSorter(dirsToDelete)) for _, dir := range dirsToDelete { - if err := os.Remove(dir); err != nil { + // Check whether the directory is empty before removing it. It may still + // contain files that were added locally (not present in upstream) and therefore + // intentionally preserved by the filepath walk above. Removing a non-empty + // directory with os.Remove would fail; instead we skip it so those + // locally-added files survive the merge. + f, err := os.Open(dir) + if err != nil { return err } + _, readErr := f.Readdirnames(1) + f.Close() + if readErr == io.EOF { + // Directory is empty — safe to remove. + if err := os.Remove(dir); err != nil { + return err + } + } + // Otherwise the directory still has contents (locally-added files); + // leave it in place. } return nil diff --git a/pkg/lib/pkg/util_test.go b/pkg/lib/pkg/util_test.go index 9613b37225..12da37c28f 100644 --- a/pkg/lib/pkg/util_test.go +++ b/pkg/lib/pkg/util_test.go @@ -566,7 +566,7 @@ func TestRemoveStaleItems_RemovesFile(t *testing.T) { assert.True(t, os.IsNotExist(err)) } -func TestRemoveStaleItems_ErrorOnRemove(t *testing.T) { +func TestRemoveStaleItems_PreservesNonEmptyLocalDir(t *testing.T) { org := t.TempDir() src := t.TempDir() dst := t.TempDir() @@ -578,12 +578,136 @@ func TestRemoveStaleItems_ErrorOnRemove(t *testing.T) { assert.NoError(t, os.WriteFile(filePathOrg, []byte("content"), 0644)) assert.NoError(t, os.WriteFile(filePathDst, []byte("content"), 0644)) - // Replace file in dst with a non-empty directory to force os.Remove error + // Replace file in dst with a non-empty directory (simulates upstream deleting + // a path that locally became a directory with added files). assert.NoError(t, os.Remove(filePathDst)) assert.NoError(t, os.Mkdir(filePathDst, 0755)) assert.NoError(t, os.WriteFile(filepath.Join(filePathDst, "dummy"), []byte("x"), 0644)) + // RemoveStaleItems should succeed and preserve the non-empty directory. + err := RemoveStaleItems(org, src, dst, true, All) + assert.NoError(t, err) + + // The directory and its locally-added file must still exist. + _, err = os.Stat(filePathDst) + assert.NoError(t, err, "non-empty stale directory should be preserved") + _, err = os.Stat(filepath.Join(filePathDst, "dummy")) + assert.NoError(t, err, "locally-added file inside stale directory should be preserved") +} + +func TestRemoveStaleItems_PreservesLocalFilesInDeletedDir(t *testing.T) { + org := t.TempDir() + src := t.TempDir() + dst := t.TempDir() + + // Simulate: origin has configs/base.yaml, upstream deletes entire configs/ dir, + // but local added configs/custom.yaml. + configsDirOrg := filepath.Join(org, "configs") + configsDirDst := filepath.Join(dst, "configs") + + assert.NoError(t, os.Mkdir(configsDirOrg, 0755)) + assert.NoError(t, os.WriteFile(filepath.Join(configsDirOrg, "base.yaml"), []byte("original"), 0644)) + + assert.NoError(t, os.Mkdir(configsDirDst, 0755)) + assert.NoError(t, os.WriteFile(filepath.Join(configsDirDst, "base.yaml"), []byte("original"), 0644)) + assert.NoError(t, os.WriteFile(filepath.Join(configsDirDst, "custom.yaml"), []byte("local-addition"), 0644)) + + // src (new upstream) has no configs/ directory at all. + + err := RemoveStaleItems(org, src, dst, true, All) + assert.NoError(t, err) + + // base.yaml was in origin and not in upstream — should be removed. + _, err = os.Stat(filepath.Join(configsDirDst, "base.yaml")) + assert.True(t, os.IsNotExist(err), "stale file base.yaml should be removed") + + // custom.yaml was NOT in origin — should be preserved. + _, err = os.Stat(filepath.Join(configsDirDst, "custom.yaml")) + assert.NoError(t, err, "locally-added file custom.yaml should be preserved") + + // configs/ directory should still exist because it contains custom.yaml. + info, err := os.Stat(configsDirDst) + assert.NoError(t, err, "directory with local files should be preserved") + assert.True(t, info.IsDir()) +} + +func TestRemoveStaleItems_RemovesEmptyDirAfterStaleFileCleanup(t *testing.T) { + org := t.TempDir() + src := t.TempDir() + dst := t.TempDir() + + // Simulate: origin has configs/base.yaml, upstream deletes the directory, + // and local has no additions — directory should be removed entirely. + configsDirOrg := filepath.Join(org, "configs") + configsDirDst := filepath.Join(dst, "configs") + + assert.NoError(t, os.Mkdir(configsDirOrg, 0755)) + assert.NoError(t, os.WriteFile(filepath.Join(configsDirOrg, "base.yaml"), []byte("original"), 0644)) + + assert.NoError(t, os.Mkdir(configsDirDst, 0755)) + assert.NoError(t, os.WriteFile(filepath.Join(configsDirDst, "base.yaml"), []byte("original"), 0644)) + + err := RemoveStaleItems(org, src, dst, true, All) + assert.NoError(t, err) + + // Both the file and directory should be gone. + _, err = os.Stat(filepath.Join(configsDirDst, "base.yaml")) + assert.True(t, os.IsNotExist(err), "stale file should be removed") + _, err = os.Stat(configsDirDst) + assert.True(t, os.IsNotExist(err), "empty stale directory should be removed") +} + +func TestRemoveStaleItems_NestedDirsWithLocalFile(t *testing.T) { + org := t.TempDir() + src := t.TempDir() + dst := t.TempDir() + + // Origin has configs/nested/base.yaml and configs/top.yaml. + // Upstream (src) deletes everything. + // Local added configs/nested/custom.yaml. + assert.NoError(t, os.MkdirAll(filepath.Join(org, "configs", "nested"), 0755)) + assert.NoError(t, os.WriteFile(filepath.Join(org, "configs", "top.yaml"), []byte("orig"), 0644)) + assert.NoError(t, os.WriteFile(filepath.Join(org, "configs", "nested", "base.yaml"), []byte("orig"), 0644)) + + assert.NoError(t, os.MkdirAll(filepath.Join(dst, "configs", "nested"), 0755)) + assert.NoError(t, os.WriteFile(filepath.Join(dst, "configs", "top.yaml"), []byte("orig"), 0644)) + assert.NoError(t, os.WriteFile(filepath.Join(dst, "configs", "nested", "base.yaml"), []byte("orig"), 0644)) + assert.NoError(t, os.WriteFile(filepath.Join(dst, "configs", "nested", "custom.yaml"), []byte("local"), 0644)) + + err := RemoveStaleItems(org, src, dst, true, All) + assert.NoError(t, err) + + // Stale files should be removed. + _, err = os.Stat(filepath.Join(dst, "configs", "top.yaml")) + assert.True(t, os.IsNotExist(err), "stale file top.yaml should be removed") + _, err = os.Stat(filepath.Join(dst, "configs", "nested", "base.yaml")) + assert.True(t, os.IsNotExist(err), "stale file base.yaml should be removed") + + // Locally-added file should be preserved. + _, err = os.Stat(filepath.Join(dst, "configs", "nested", "custom.yaml")) + assert.NoError(t, err, "locally-added file custom.yaml should be preserved") + + // Both parent directories should be preserved because nested/ still has content. + _, err = os.Stat(filepath.Join(dst, "configs", "nested")) + assert.NoError(t, err, "nested dir with local files should be preserved") + _, err = os.Stat(filepath.Join(dst, "configs")) + assert.NoError(t, err, "parent dir should be preserved when child dir has content") +} + +func TestRemoveStaleItems_ErrorOnFileRemovePermission(t *testing.T) { + org := t.TempDir() + src := t.TempDir() + dst := t.TempDir() + + // Create a stale file (in org and dst, not in src). + assert.NoError(t, os.WriteFile(filepath.Join(org, "stale.yaml"), []byte("orig"), 0644)) + assert.NoError(t, os.WriteFile(filepath.Join(dst, "stale.yaml"), []byte("orig"), 0644)) + + // Revoke write permission on dst so os.Remove fails. + assert.NoError(t, os.Chmod(dst, 0555)) + t.Cleanup(func() { _ = os.Chmod(dst, 0755) }) + err := RemoveStaleItems(org, src, dst, true, All) assert.Error(t, err) - assert.Contains(t, err.Error(), "directory not empty") + assert.Contains(t, err.Error(), "permission denied") } diff --git a/pkg/lib/update/copy-merge_test.go b/pkg/lib/update/copy-merge_test.go index 63eeca0060..0c176dc268 100644 --- a/pkg/lib/update/copy-merge_test.go +++ b/pkg/lib/update/copy-merge_test.go @@ -411,7 +411,7 @@ func TestCopyMergeDifferentMetadata(t *testing.T) { } } -func TestCopyMergeErrorRemovingFile(t *testing.T) { +func TestCopyMergePreservesLocalFilesInDeletedDir(t *testing.T) { src := t.TempDir() dst := t.TempDir() org := t.TempDir() @@ -424,6 +424,8 @@ func TestCopyMergeErrorRemovingFile(t *testing.T) { assert.NoError(t, os.WriteFile(filePathDst, []byte("content"), 0644)) assert.NoError(t, os.WriteFile(filePathOrg, []byte("content"), 0644)) + // Replace file in dst with a non-empty directory (simulates upstream deleting + // a path that locally became a directory with added files). assert.NoError(t, os.Remove(filePathDst)) assert.NoError(t, os.Mkdir(filePathDst, 0755)) assert.NoError(t, os.WriteFile(filepath.Join(filePathDst, "dummy"), []byte("x"), 0644)) @@ -436,7 +438,13 @@ func TestCopyMergeErrorRemovingFile(t *testing.T) { IsRoot: true, } + // Should succeed — the non-empty directory is preserved with its locally-added file. err := updater.Update(options) - assert.Error(t, err) - assert.Contains(t, err.Error(), "directory not empty") + assert.NoError(t, err) + + // Verify the directory and its contents survive. + _, err = os.Stat(filePathDst) + assert.NoError(t, err, "non-empty directory should be preserved") + _, err = os.Stat(filepath.Join(filePathDst, "dummy")) + assert.NoError(t, err, "locally-added file should be preserved") } From 35206248c93bf3412d2fc20ea5ebee059c910152 Mon Sep 17 00:00:00 2001 From: Aravindhan Ayyanathan Date: Fri, 4 Sep 2026 08:24:30 +0100 Subject: [PATCH 20/29] Bump Homebrew formula to v1.0.0 (#4722) Signed-off-by: Aravindhan Ayyanathan --- Formula/kpt.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Formula/kpt.rb b/Formula/kpt.rb index 467895e8f1..4373ba3386 100644 --- a/Formula/kpt.rb +++ b/Formula/kpt.rb @@ -15,8 +15,8 @@ class Kpt < Formula desc "A toolchain for composing, customizing, and deploying Kubernetes packages at scale" homepage "https://kpt.dev" - url "https://github.com/kptdev/kpt/archive/v1.0.0-beta.67.tar.gz" - sha256 "358f6dc09e0ed494432d1b669ee1a9133bc20c285defbdc495ca29994b43b424" + url "https://github.com/kptdev/kpt/archive/v1.0.0.tar.gz" + sha256 "334bfa273fd57af06324f30e7447306c93b03d7146ddbc2aae8b63dd52b6fc4e" depends_on "go" => :build From 6edbfc1a20f56fe73ab1ea2287e384c45d269d4d Mon Sep 17 00:00:00 2001 From: mozesl-nokia Date: Tue, 8 Sep 2026 08:32:30 +0200 Subject: [PATCH 21/29] add applyconfigurations (#4701) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-by: Cursor:grok-4.5 Signed-off-by: Mózes László Máté --- api/doc.go | 20 +++ api/fnresult/v1/types.go | 6 +- .../applyconfigurations/fnresult/v1/field.go | 58 +++++++ .../fnresult/v1/resultitem.go | 96 +++++++++++ .../kptfile/v1/condition.go | 68 ++++++++ .../kptfile/v1/function.go | 159 ++++++++++++++++++ .../kptfile/v1/genericlock.go | 63 +++++++ .../applyconfigurations/kptfile/v1/git.go | 62 +++++++ .../applyconfigurations/kptfile/v1/gitlock.go | 74 ++++++++ .../kptfile/v1/inventory.go | 91 ++++++++++ .../applyconfigurations/kptfile/v1/locator.go | 64 +++++++ .../kptfile/v1/packageinfo.go | 121 +++++++++++++ .../kptfile/v1/pipeline.go | 61 +++++++ .../kptfile/v1/pipelinestepresult.go | 111 ++++++++++++ .../kptfile/v1/readinessgate.go | 37 ++++ .../kptfile/v1/renderstatus.go | 68 ++++++++ .../kptfile/v1/resourcereference.go | 72 ++++++++ .../kptfile/v1/selector.go | 103 ++++++++++++ .../applyconfigurations/kptfile/v1/status.go | 51 ++++++ .../kptfile/v1/upstream.go | 64 +++++++ api/go.mod | 3 +- api/go.sum | 8 +- api/kptfile/v1/types.go | 6 +- api/schema/v1/group_version.go | 2 - 24 files changed, 1457 insertions(+), 11 deletions(-) create mode 100644 api/doc.go create mode 100644 api/generated/applyconfigurations/fnresult/v1/field.go create mode 100644 api/generated/applyconfigurations/fnresult/v1/resultitem.go create mode 100644 api/generated/applyconfigurations/kptfile/v1/condition.go create mode 100644 api/generated/applyconfigurations/kptfile/v1/function.go create mode 100644 api/generated/applyconfigurations/kptfile/v1/genericlock.go create mode 100644 api/generated/applyconfigurations/kptfile/v1/git.go create mode 100644 api/generated/applyconfigurations/kptfile/v1/gitlock.go create mode 100644 api/generated/applyconfigurations/kptfile/v1/inventory.go create mode 100644 api/generated/applyconfigurations/kptfile/v1/locator.go create mode 100644 api/generated/applyconfigurations/kptfile/v1/packageinfo.go create mode 100644 api/generated/applyconfigurations/kptfile/v1/pipeline.go create mode 100644 api/generated/applyconfigurations/kptfile/v1/pipelinestepresult.go create mode 100644 api/generated/applyconfigurations/kptfile/v1/readinessgate.go create mode 100644 api/generated/applyconfigurations/kptfile/v1/renderstatus.go create mode 100644 api/generated/applyconfigurations/kptfile/v1/resourcereference.go create mode 100644 api/generated/applyconfigurations/kptfile/v1/selector.go create mode 100644 api/generated/applyconfigurations/kptfile/v1/status.go create mode 100644 api/generated/applyconfigurations/kptfile/v1/upstream.go diff --git a/api/doc.go b/api/doc.go new file mode 100644 index 0000000000..2e130028f1 --- /dev/null +++ b/api/doc.go @@ -0,0 +1,20 @@ +// 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 apis contains the kpt API type definitions. +package apis + +//go:generate go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.21.0 object:headerFile="../hack/boilerplate.go.txt",year=$YEAR_GEN applyconfiguration:headerFile="../hack/boilerplate.go.txt" paths=./... +// Drop generated helpers that pull in k8s.io/apimachinery; typed ACs are enough. +//go:generate rm -rf generated/applyconfigurations/utils.go generated/applyconfigurations/internal diff --git a/api/fnresult/v1/types.go b/api/fnresult/v1/types.go index 2b87983b83..b2d063475e 100644 --- a/api/fnresult/v1/types.go +++ b/api/fnresult/v1/types.go @@ -13,6 +13,9 @@ // limitations under the License. // +kubebuilder:object:generate=true +// +kubebuilder:ac:generate=true +// +kubebuilder:ac:output:package=../../generated/applyconfigurations +// +groupName=kpt.dev package v1 import ( @@ -25,8 +28,6 @@ import ( "sigs.k8s.io/kustomize/kyaml/yaml" ) -//go:generate go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.21.0 object:headerFile="../../../hack/boilerplate.go.txt",year=$YEAR_GEN - // Result contains the structured result from an individual function type Result struct { // Image is the full name of the image that generates this result @@ -71,6 +72,7 @@ func ResultListGVK() schema.GroupVersionKind { } // ResultList contains aggregated results from multiple functions +// +kubebuilder:ac:generate=true type ResultList struct { yaml.ResourceMeta `yaml:",inline"` // ExitCode is the exit code of kpt command diff --git a/api/generated/applyconfigurations/fnresult/v1/field.go b/api/generated/applyconfigurations/fnresult/v1/field.go new file mode 100644 index 0000000000..cc911950f5 --- /dev/null +++ b/api/generated/applyconfigurations/fnresult/v1/field.go @@ -0,0 +1,58 @@ +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +// FieldApplyConfiguration represents a declarative configuration of the Field type for use +// with apply. +// +// Field is a modified version of sigs.k8s.io/kustomize/kyaml/fn/framework.Field +// where CurrentValue and ProposedValue are strings instead of the original any type. +type FieldApplyConfiguration struct { + Path *string `json:"path,omitempty"` + CurrentValue *string `json:"currentValue,omitempty"` + ProposedValue *string `json:"proposedValue,omitempty"` +} + +// FieldApplyConfiguration constructs a declarative configuration of the Field type for use with +// apply. +func Field() *FieldApplyConfiguration { + return &FieldApplyConfiguration{} +} + +// WithPath sets the Path field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Path field is set to the value of the last call. +func (b *FieldApplyConfiguration) WithPath(value string) *FieldApplyConfiguration { + b.Path = &value + return b +} + +// WithCurrentValue sets the CurrentValue field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CurrentValue field is set to the value of the last call. +func (b *FieldApplyConfiguration) WithCurrentValue(value string) *FieldApplyConfiguration { + b.CurrentValue = &value + return b +} + +// WithProposedValue sets the ProposedValue field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ProposedValue field is set to the value of the last call. +func (b *FieldApplyConfiguration) WithProposedValue(value string) *FieldApplyConfiguration { + b.ProposedValue = &value + return b +} diff --git a/api/generated/applyconfigurations/fnresult/v1/resultitem.go b/api/generated/applyconfigurations/fnresult/v1/resultitem.go new file mode 100644 index 0000000000..4cbd887c49 --- /dev/null +++ b/api/generated/applyconfigurations/fnresult/v1/resultitem.go @@ -0,0 +1,96 @@ +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +import ( + framework "sigs.k8s.io/kustomize/kyaml/fn/framework" + yaml "sigs.k8s.io/kustomize/kyaml/yaml" +) + +// ResultItemApplyConfiguration represents a declarative configuration of the ResultItem type for use +// with apply. +// +// ResultItem is a modified version of sigs.k8s.io/kustomize/kyaml/fn/framework.Result +// with a simplified Field field. +type ResultItemApplyConfiguration struct { + Message *string `json:"message,omitempty"` + Severity *framework.Severity `json:"severity,omitempty"` + ResourceRef *yaml.ResourceIdentifier `json:"resourceRef,omitempty"` + Field *FieldApplyConfiguration `json:"field,omitempty"` + File *framework.File `json:"file,omitempty"` + Tags map[string]string `json:"tags,omitempty"` +} + +// ResultItemApplyConfiguration constructs a declarative configuration of the ResultItem type for use with +// apply. +func ResultItem() *ResultItemApplyConfiguration { + return &ResultItemApplyConfiguration{} +} + +// WithMessage sets the Message field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Message field is set to the value of the last call. +func (b *ResultItemApplyConfiguration) WithMessage(value string) *ResultItemApplyConfiguration { + b.Message = &value + return b +} + +// WithSeverity sets the Severity field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Severity field is set to the value of the last call. +func (b *ResultItemApplyConfiguration) WithSeverity(value framework.Severity) *ResultItemApplyConfiguration { + b.Severity = &value + return b +} + +// WithResourceRef sets the ResourceRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceRef field is set to the value of the last call. +func (b *ResultItemApplyConfiguration) WithResourceRef(value yaml.ResourceIdentifier) *ResultItemApplyConfiguration { + b.ResourceRef = &value + return b +} + +// WithField sets the Field field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Field field is set to the value of the last call. +func (b *ResultItemApplyConfiguration) WithField(value *FieldApplyConfiguration) *ResultItemApplyConfiguration { + b.Field = value + return b +} + +// WithFile sets the File field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the File field is set to the value of the last call. +func (b *ResultItemApplyConfiguration) WithFile(value framework.File) *ResultItemApplyConfiguration { + b.File = &value + return b +} + +// WithTags puts the entries into the Tags field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Tags field, +// overwriting an existing map entries in Tags field with the same key. +func (b *ResultItemApplyConfiguration) WithTags(entries map[string]string) *ResultItemApplyConfiguration { + if b.Tags == nil && len(entries) > 0 { + b.Tags = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Tags[k] = v + } + return b +} diff --git a/api/generated/applyconfigurations/kptfile/v1/condition.go b/api/generated/applyconfigurations/kptfile/v1/condition.go new file mode 100644 index 0000000000..fc357e137f --- /dev/null +++ b/api/generated/applyconfigurations/kptfile/v1/condition.go @@ -0,0 +1,68 @@ +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +import ( + kptfilev1 "github.com/kptdev/kpt/api/kptfile/v1" +) + +// ConditionApplyConfiguration represents a declarative configuration of the Condition type for use +// with apply. +type ConditionApplyConfiguration struct { + Type *string `json:"type,omitempty"` + Status *kptfilev1.ConditionStatus `json:"status,omitempty"` + Reason *string `json:"reason,omitempty"` + Message *string `json:"message,omitempty"` +} + +// ConditionApplyConfiguration constructs a declarative configuration of the Condition type for use with +// apply. +func Condition() *ConditionApplyConfiguration { + return &ConditionApplyConfiguration{} +} + +// WithType sets the Type field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Type field is set to the value of the last call. +func (b *ConditionApplyConfiguration) WithType(value string) *ConditionApplyConfiguration { + b.Type = &value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *ConditionApplyConfiguration) WithStatus(value kptfilev1.ConditionStatus) *ConditionApplyConfiguration { + b.Status = &value + return b +} + +// WithReason sets the Reason field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Reason field is set to the value of the last call. +func (b *ConditionApplyConfiguration) WithReason(value string) *ConditionApplyConfiguration { + b.Reason = &value + return b +} + +// WithMessage sets the Message field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Message field is set to the value of the last call. +func (b *ConditionApplyConfiguration) WithMessage(value string) *ConditionApplyConfiguration { + b.Message = &value + return b +} diff --git a/api/generated/applyconfigurations/kptfile/v1/function.go b/api/generated/applyconfigurations/kptfile/v1/function.go new file mode 100644 index 0000000000..8fca78ed05 --- /dev/null +++ b/api/generated/applyconfigurations/kptfile/v1/function.go @@ -0,0 +1,159 @@ +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +// FunctionApplyConfiguration represents a declarative configuration of the Function type for use +// with apply. +// +// Function specifies a KRM function. +type FunctionApplyConfiguration struct { + // `Image` specifies the function container image. + // It can either be fully qualified, e.g.: + // + // image: ghcr.io/kptdev/krm-functions-catalog/set-labels + // + // Optionally, kpt can be configured to use a image + // registry host-path that will be used to resolve the image path in case + // the image path is missing (Defaults to ghcr.io/kptdev/krm-functions-catalog). + // e.g. The following resolves to ghcr.io/kptdev/krm-functions-catalog/set-labels + // + // image: set-labels + Image *string `json:"image,omitempty"` + // Exec specifies the function binary executable. + // The executable can be fully qualified or it must exists in the $PATH e.g: + // + // exec: set-namespace + // exec: /usr/local/bin/my-custom-fn + Exec *string `json:"exec,omitempty"` + // `ConfigPath` specifies a slash-delimited relative path to a file in the current directory + // containing a KRM resource used as the function config. The function will + // not see this resource in its input items — it is passed as configuration + // only. Other functions in the pipeline can still see and modify the file. + ConfigPath *string `json:"configPath,omitempty"` + // `ConfigMap` is a convenient way to specify a function config of kind ConfigMap. + ConfigMap map[string]string `json:"configMap,omitempty"` + // `ConfigRef` references an existing resource in the package as the function config. + // The resource is identified by apiVersion, kind, and name. This is mutually exclusive + // with `configPath` and `configMap`. + ConfigRef *ResourceReferenceApplyConfiguration `json:"configRef,omitempty"` + // `Name` is used to uniquely identify the function declaration + // this is primarily used for merging function declaration with upstream counterparts + Name *string `json:"name,omitempty"` + // `Tag` is an optional field for specifying/overriding the tag of the function image. + // Can be a semver constraint satisfying this spec: https://github.com/Masterminds/semver?tab=readme-ov-file#checking-version-constraints + Tag *string `json:"tag,omitempty"` + // `Selectors` are used to specify resources on which the function should be executed + // if not specified, all resources are selected + Selectors []SelectorApplyConfiguration `json:"selectors,omitempty"` + // `Exclude` are used to specify resources on which the function should NOT be executed. + // If not specified, all resources selected by `Selectors` are selected. + Exclusions []SelectorApplyConfiguration `json:"exclude,omitempty"` +} + +// FunctionApplyConfiguration constructs a declarative configuration of the Function type for use with +// apply. +func Function() *FunctionApplyConfiguration { + return &FunctionApplyConfiguration{} +} + +// WithImage sets the Image field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Image field is set to the value of the last call. +func (b *FunctionApplyConfiguration) WithImage(value string) *FunctionApplyConfiguration { + b.Image = &value + return b +} + +// WithExec sets the Exec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Exec field is set to the value of the last call. +func (b *FunctionApplyConfiguration) WithExec(value string) *FunctionApplyConfiguration { + b.Exec = &value + return b +} + +// WithConfigPath sets the ConfigPath field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ConfigPath field is set to the value of the last call. +func (b *FunctionApplyConfiguration) WithConfigPath(value string) *FunctionApplyConfiguration { + b.ConfigPath = &value + return b +} + +// WithConfigMap puts the entries into the ConfigMap field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the ConfigMap field, +// overwriting an existing map entries in ConfigMap field with the same key. +func (b *FunctionApplyConfiguration) WithConfigMap(entries map[string]string) *FunctionApplyConfiguration { + if b.ConfigMap == nil && len(entries) > 0 { + b.ConfigMap = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ConfigMap[k] = v + } + return b +} + +// WithConfigRef sets the ConfigRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ConfigRef field is set to the value of the last call. +func (b *FunctionApplyConfiguration) WithConfigRef(value *ResourceReferenceApplyConfiguration) *FunctionApplyConfiguration { + b.ConfigRef = value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *FunctionApplyConfiguration) WithName(value string) *FunctionApplyConfiguration { + b.Name = &value + return b +} + +// WithTag sets the Tag field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Tag field is set to the value of the last call. +func (b *FunctionApplyConfiguration) WithTag(value string) *FunctionApplyConfiguration { + b.Tag = &value + return b +} + +// WithSelectors adds the given value to the Selectors field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Selectors field. +func (b *FunctionApplyConfiguration) WithSelectors(values ...*SelectorApplyConfiguration) *FunctionApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithSelectors") + } + b.Selectors = append(b.Selectors, *values[i]) + } + return b +} + +// WithExclusions adds the given value to the Exclusions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Exclusions field. +func (b *FunctionApplyConfiguration) WithExclusions(values ...*SelectorApplyConfiguration) *FunctionApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithExclusions") + } + b.Exclusions = append(b.Exclusions, *values[i]) + } + return b +} diff --git a/api/generated/applyconfigurations/kptfile/v1/genericlock.go b/api/generated/applyconfigurations/kptfile/v1/genericlock.go new file mode 100644 index 0000000000..93b07fdd16 --- /dev/null +++ b/api/generated/applyconfigurations/kptfile/v1/genericlock.go @@ -0,0 +1,63 @@ +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +// GenericLockApplyConfiguration represents a declarative configuration of the GenericLock type for use +// with apply. +// +// GenericLock is a minimal locator for a package stored in a generic storage backend. +type GenericLockApplyConfiguration struct { + // StoreID is a descriptor of the underlying storage type. + // e.g. 'DB' for database + StoreID *string `json:"storeID,omitempty"` + // ResourceID is a unique identifier of the resource. + // The format depends on the underlying storage. + ResourceID *string `json:"resourceID,omitempty"` + // ResourceVersion indicates the last fetched version of the resource. + // The format depends on the underlying storage. + ResourceVersion *string `json:"resourceVersion,omitempty"` +} + +// GenericLockApplyConfiguration constructs a declarative configuration of the GenericLock type for use with +// apply. +func GenericLock() *GenericLockApplyConfiguration { + return &GenericLockApplyConfiguration{} +} + +// WithStoreID sets the StoreID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the StoreID field is set to the value of the last call. +func (b *GenericLockApplyConfiguration) WithStoreID(value string) *GenericLockApplyConfiguration { + b.StoreID = &value + return b +} + +// WithResourceID sets the ResourceID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceID field is set to the value of the last call. +func (b *GenericLockApplyConfiguration) WithResourceID(value string) *GenericLockApplyConfiguration { + b.ResourceID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *GenericLockApplyConfiguration) WithResourceVersion(value string) *GenericLockApplyConfiguration { + b.ResourceVersion = &value + return b +} diff --git a/api/generated/applyconfigurations/kptfile/v1/git.go b/api/generated/applyconfigurations/kptfile/v1/git.go new file mode 100644 index 0000000000..2cc32ccc69 --- /dev/null +++ b/api/generated/applyconfigurations/kptfile/v1/git.go @@ -0,0 +1,62 @@ +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +// GitApplyConfiguration represents a declarative configuration of the Git type for use +// with apply. +// +// Git is the user-specified locator for a package on Git. +type GitApplyConfiguration struct { + // Repo is the git repository the package. + // e.g. 'https://github.com/kubernetes/examples.git' + Repo *string `json:"repo,omitempty"` + // Directory is the sub directory of the git repository. + // e.g. 'staging/cockroachdb' + Directory *string `json:"directory,omitempty"` + // Ref can be a Git branch, tag, or a commit SHA-1. + Ref *string `json:"ref,omitempty"` +} + +// GitApplyConfiguration constructs a declarative configuration of the Git type for use with +// apply. +func Git() *GitApplyConfiguration { + return &GitApplyConfiguration{} +} + +// WithRepo sets the Repo field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Repo field is set to the value of the last call. +func (b *GitApplyConfiguration) WithRepo(value string) *GitApplyConfiguration { + b.Repo = &value + return b +} + +// WithDirectory sets the Directory field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Directory field is set to the value of the last call. +func (b *GitApplyConfiguration) WithDirectory(value string) *GitApplyConfiguration { + b.Directory = &value + return b +} + +// WithRef sets the Ref field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Ref field is set to the value of the last call. +func (b *GitApplyConfiguration) WithRef(value string) *GitApplyConfiguration { + b.Ref = &value + return b +} diff --git a/api/generated/applyconfigurations/kptfile/v1/gitlock.go b/api/generated/applyconfigurations/kptfile/v1/gitlock.go new file mode 100644 index 0000000000..f6ddb65d3c --- /dev/null +++ b/api/generated/applyconfigurations/kptfile/v1/gitlock.go @@ -0,0 +1,74 @@ +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +// GitLockApplyConfiguration represents a declarative configuration of the GitLock type for use +// with apply. +// +// GitLock is the resolved locator for a package on Git. +type GitLockApplyConfiguration struct { + // Repo is the git repository that was fetched. + // e.g. 'https://github.com/kubernetes/examples.git' + Repo *string `json:"repo,omitempty"` + // Directory is the sub directory of the git repository that was fetched. + // e.g. 'staging/cockroachdb' + Directory *string `json:"directory,omitempty"` + // Ref can be a Git branch, tag, or a commit SHA-1 that was fetched. + // e.g. 'master' + Ref *string `json:"ref,omitempty"` + // Commit is the SHA-1 for the last fetch of the package. + // This is set by kpt for bookkeeping purposes. + Commit *string `json:"commit,omitempty"` +} + +// GitLockApplyConfiguration constructs a declarative configuration of the GitLock type for use with +// apply. +func GitLock() *GitLockApplyConfiguration { + return &GitLockApplyConfiguration{} +} + +// WithRepo sets the Repo field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Repo field is set to the value of the last call. +func (b *GitLockApplyConfiguration) WithRepo(value string) *GitLockApplyConfiguration { + b.Repo = &value + return b +} + +// WithDirectory sets the Directory field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Directory field is set to the value of the last call. +func (b *GitLockApplyConfiguration) WithDirectory(value string) *GitLockApplyConfiguration { + b.Directory = &value + return b +} + +// WithRef sets the Ref field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Ref field is set to the value of the last call. +func (b *GitLockApplyConfiguration) WithRef(value string) *GitLockApplyConfiguration { + b.Ref = &value + return b +} + +// WithCommit sets the Commit field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Commit field is set to the value of the last call. +func (b *GitLockApplyConfiguration) WithCommit(value string) *GitLockApplyConfiguration { + b.Commit = &value + return b +} diff --git a/api/generated/applyconfigurations/kptfile/v1/inventory.go b/api/generated/applyconfigurations/kptfile/v1/inventory.go new file mode 100644 index 0000000000..573b1bcb5d --- /dev/null +++ b/api/generated/applyconfigurations/kptfile/v1/inventory.go @@ -0,0 +1,91 @@ +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +// InventoryApplyConfiguration represents a declarative configuration of the Inventory type for use +// with apply. +// +// Inventory encapsulates the parameters for the inventory resource applied to a cluster. +// All of the the parameters are required if any are set. +type InventoryApplyConfiguration struct { + // Namespace for the inventory resource. + Namespace *string `json:"namespace,omitempty"` + // Name of the inventory resource. + Name *string `json:"name,omitempty"` + // Unique label to identify inventory resource in cluster. + InventoryID *string `json:"inventoryID,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + Annotations map[string]string `json:"annotations,omitempty"` +} + +// InventoryApplyConfiguration constructs a declarative configuration of the Inventory type for use with +// apply. +func Inventory() *InventoryApplyConfiguration { + return &InventoryApplyConfiguration{} +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *InventoryApplyConfiguration) WithNamespace(value string) *InventoryApplyConfiguration { + b.Namespace = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *InventoryApplyConfiguration) WithName(value string) *InventoryApplyConfiguration { + b.Name = &value + return b +} + +// WithInventoryID sets the InventoryID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the InventoryID field is set to the value of the last call. +func (b *InventoryApplyConfiguration) WithInventoryID(value string) *InventoryApplyConfiguration { + b.InventoryID = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *InventoryApplyConfiguration) WithLabels(entries map[string]string) *InventoryApplyConfiguration { + if b.Labels == nil && len(entries) > 0 { + b.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *InventoryApplyConfiguration) WithAnnotations(entries map[string]string) *InventoryApplyConfiguration { + if b.Annotations == nil && len(entries) > 0 { + b.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Annotations[k] = v + } + return b +} diff --git a/api/generated/applyconfigurations/kptfile/v1/locator.go b/api/generated/applyconfigurations/kptfile/v1/locator.go new file mode 100644 index 0000000000..0fc4acc64f --- /dev/null +++ b/api/generated/applyconfigurations/kptfile/v1/locator.go @@ -0,0 +1,64 @@ +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +import ( + kptfilev1 "github.com/kptdev/kpt/api/kptfile/v1" +) + +// LocatorApplyConfiguration represents a declarative configuration of the Locator type for use +// with apply. +// +// Locator is a resolved locator for the last fetch of the package. +type LocatorApplyConfiguration struct { + // Type is the type of origin. + Type *kptfilev1.OriginType `json:"type,omitempty"` + // Git is the resolved locator for a package on Git. + Git *GitLockApplyConfiguration `json:"git,omitempty"` + // Generic is a minimal locator for a package stored in some generic storage type (e.g.: a database) + Generic *GenericLockApplyConfiguration `json:"generic,omitempty"` +} + +// LocatorApplyConfiguration constructs a declarative configuration of the Locator type for use with +// apply. +func Locator() *LocatorApplyConfiguration { + return &LocatorApplyConfiguration{} +} + +// WithType sets the Type field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Type field is set to the value of the last call. +func (b *LocatorApplyConfiguration) WithType(value kptfilev1.OriginType) *LocatorApplyConfiguration { + b.Type = &value + return b +} + +// WithGit sets the Git field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Git field is set to the value of the last call. +func (b *LocatorApplyConfiguration) WithGit(value *GitLockApplyConfiguration) *LocatorApplyConfiguration { + b.Git = value + return b +} + +// WithGeneric sets the Generic field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generic field is set to the value of the last call. +func (b *LocatorApplyConfiguration) WithGeneric(value *GenericLockApplyConfiguration) *LocatorApplyConfiguration { + b.Generic = value + return b +} diff --git a/api/generated/applyconfigurations/kptfile/v1/packageinfo.go b/api/generated/applyconfigurations/kptfile/v1/packageinfo.go new file mode 100644 index 0000000000..1d791d4cc7 --- /dev/null +++ b/api/generated/applyconfigurations/kptfile/v1/packageinfo.go @@ -0,0 +1,121 @@ +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +// PackageInfoApplyConfiguration represents a declarative configuration of the PackageInfo type for use +// with apply. +// +// PackageInfo contains optional information about the package such as license, documentation, etc. +// These fields are not consumed by any functionality in kpt and are simply passed through. +// Note that like any other KRM resource, humans and automation can also use `metadata.labels` and +// `metadata.annotations` as the extension mechanism. +type PackageInfoApplyConfiguration struct { + // Site is the URL for package web page. + Site *string `json:"site,omitempty"` + // Email is the list of emails for the package authors. + Emails []string `json:"emails,omitempty"` + // SPDX license identifier (e.g. "Apache-2.0"). See: https://spdx.org/licenses/ + License *string `json:"license,omitempty"` + // Relative slash-delimited path to the license file (e.g. LICENSE.txt) + LicenseFile *string `json:"licenseFile,omitempty"` + // Description contains a short description of the package. + Description *string `json:"description,omitempty"` + // Keywords is a list of keywords for this package. + Keywords []string `json:"keywords,omitempty"` + // Man is the path to documentation about the package + Man *string `json:"man,omitempty"` + ReadinessGates []ReadinessGateApplyConfiguration `json:"readinessGates,omitempty"` +} + +// PackageInfoApplyConfiguration constructs a declarative configuration of the PackageInfo type for use with +// apply. +func PackageInfo() *PackageInfoApplyConfiguration { + return &PackageInfoApplyConfiguration{} +} + +// WithSite sets the Site field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Site field is set to the value of the last call. +func (b *PackageInfoApplyConfiguration) WithSite(value string) *PackageInfoApplyConfiguration { + b.Site = &value + return b +} + +// WithEmails adds the given value to the Emails field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Emails field. +func (b *PackageInfoApplyConfiguration) WithEmails(values ...string) *PackageInfoApplyConfiguration { + for i := range values { + b.Emails = append(b.Emails, values[i]) + } + return b +} + +// WithLicense sets the License field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the License field is set to the value of the last call. +func (b *PackageInfoApplyConfiguration) WithLicense(value string) *PackageInfoApplyConfiguration { + b.License = &value + return b +} + +// WithLicenseFile sets the LicenseFile field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LicenseFile field is set to the value of the last call. +func (b *PackageInfoApplyConfiguration) WithLicenseFile(value string) *PackageInfoApplyConfiguration { + b.LicenseFile = &value + return b +} + +// WithDescription sets the Description field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Description field is set to the value of the last call. +func (b *PackageInfoApplyConfiguration) WithDescription(value string) *PackageInfoApplyConfiguration { + b.Description = &value + return b +} + +// WithKeywords adds the given value to the Keywords field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Keywords field. +func (b *PackageInfoApplyConfiguration) WithKeywords(values ...string) *PackageInfoApplyConfiguration { + for i := range values { + b.Keywords = append(b.Keywords, values[i]) + } + return b +} + +// WithMan sets the Man field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Man field is set to the value of the last call. +func (b *PackageInfoApplyConfiguration) WithMan(value string) *PackageInfoApplyConfiguration { + b.Man = &value + return b +} + +// WithReadinessGates adds the given value to the ReadinessGates field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the ReadinessGates field. +func (b *PackageInfoApplyConfiguration) WithReadinessGates(values ...*ReadinessGateApplyConfiguration) *PackageInfoApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithReadinessGates") + } + b.ReadinessGates = append(b.ReadinessGates, *values[i]) + } + return b +} diff --git a/api/generated/applyconfigurations/kptfile/v1/pipeline.go b/api/generated/applyconfigurations/kptfile/v1/pipeline.go new file mode 100644 index 0000000000..4fe038fda0 --- /dev/null +++ b/api/generated/applyconfigurations/kptfile/v1/pipeline.go @@ -0,0 +1,61 @@ +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +// PipelineApplyConfiguration represents a declarative configuration of the Pipeline type for use +// with apply. +// +// Pipeline declares a pipeline of functions used to mutate or validate resources. +type PipelineApplyConfiguration struct { + // Mutators defines a list of of KRM functions that mutate resources. + Mutators []FunctionApplyConfiguration `json:"mutators,omitempty"` + // Validators defines a list of KRM functions that validate resources. + // Validators are not permitted to mutate resources. + Validators []FunctionApplyConfiguration `json:"validators,omitempty"` +} + +// PipelineApplyConfiguration constructs a declarative configuration of the Pipeline type for use with +// apply. +func Pipeline() *PipelineApplyConfiguration { + return &PipelineApplyConfiguration{} +} + +// WithMutators adds the given value to the Mutators field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Mutators field. +func (b *PipelineApplyConfiguration) WithMutators(values ...*FunctionApplyConfiguration) *PipelineApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithMutators") + } + b.Mutators = append(b.Mutators, *values[i]) + } + return b +} + +// WithValidators adds the given value to the Validators field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Validators field. +func (b *PipelineApplyConfiguration) WithValidators(values ...*FunctionApplyConfiguration) *PipelineApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithValidators") + } + b.Validators = append(b.Validators, *values[i]) + } + return b +} diff --git a/api/generated/applyconfigurations/kptfile/v1/pipelinestepresult.go b/api/generated/applyconfigurations/kptfile/v1/pipelinestepresult.go new file mode 100644 index 0000000000..5cd26d865c --- /dev/null +++ b/api/generated/applyconfigurations/kptfile/v1/pipelinestepresult.go @@ -0,0 +1,111 @@ +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +import ( + fnresultv1 "github.com/kptdev/kpt/api/fnresult/v1" +) + +// PipelineStepResultApplyConfiguration represents a declarative configuration of the PipelineStepResult type for use +// with apply. +// +// PipelineStepResult contains the structured result from an individual function +// call in the pipeline. +type PipelineStepResultApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Image *string `json:"image,omitempty"` + ExecPath *string `json:"exec,omitempty"` + ExecutionError *string `json:"executionError,omitempty"` + Stderr *string `json:"stderr,omitempty"` + ExitCode *int `json:"exitCode,omitempty"` + Results []fnresultv1.ResultItem `json:"results,omitempty"` + ErrorResults []fnresultv1.ResultItem `json:"errorResults,omitempty"` +} + +// PipelineStepResultApplyConfiguration constructs a declarative configuration of the PipelineStepResult type for use with +// apply. +func PipelineStepResult() *PipelineStepResultApplyConfiguration { + return &PipelineStepResultApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *PipelineStepResultApplyConfiguration) WithName(value string) *PipelineStepResultApplyConfiguration { + b.Name = &value + return b +} + +// WithImage sets the Image field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Image field is set to the value of the last call. +func (b *PipelineStepResultApplyConfiguration) WithImage(value string) *PipelineStepResultApplyConfiguration { + b.Image = &value + return b +} + +// WithExecPath sets the ExecPath field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ExecPath field is set to the value of the last call. +func (b *PipelineStepResultApplyConfiguration) WithExecPath(value string) *PipelineStepResultApplyConfiguration { + b.ExecPath = &value + return b +} + +// WithExecutionError sets the ExecutionError field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ExecutionError field is set to the value of the last call. +func (b *PipelineStepResultApplyConfiguration) WithExecutionError(value string) *PipelineStepResultApplyConfiguration { + b.ExecutionError = &value + return b +} + +// WithStderr sets the Stderr field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Stderr field is set to the value of the last call. +func (b *PipelineStepResultApplyConfiguration) WithStderr(value string) *PipelineStepResultApplyConfiguration { + b.Stderr = &value + return b +} + +// WithExitCode sets the ExitCode field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ExitCode field is set to the value of the last call. +func (b *PipelineStepResultApplyConfiguration) WithExitCode(value int) *PipelineStepResultApplyConfiguration { + b.ExitCode = &value + return b +} + +// WithResults adds the given value to the Results field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Results field. +func (b *PipelineStepResultApplyConfiguration) WithResults(values ...fnresultv1.ResultItem) *PipelineStepResultApplyConfiguration { + for i := range values { + b.Results = append(b.Results, values[i]) + } + return b +} + +// WithErrorResults adds the given value to the ErrorResults field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the ErrorResults field. +func (b *PipelineStepResultApplyConfiguration) WithErrorResults(values ...fnresultv1.ResultItem) *PipelineStepResultApplyConfiguration { + for i := range values { + b.ErrorResults = append(b.ErrorResults, values[i]) + } + return b +} diff --git a/api/generated/applyconfigurations/kptfile/v1/readinessgate.go b/api/generated/applyconfigurations/kptfile/v1/readinessgate.go new file mode 100644 index 0000000000..ffcfeabf94 --- /dev/null +++ b/api/generated/applyconfigurations/kptfile/v1/readinessgate.go @@ -0,0 +1,37 @@ +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +// ReadinessGateApplyConfiguration represents a declarative configuration of the ReadinessGate type for use +// with apply. +type ReadinessGateApplyConfiguration struct { + ConditionType *string `json:"conditionType,omitempty"` +} + +// ReadinessGateApplyConfiguration constructs a declarative configuration of the ReadinessGate type for use with +// apply. +func ReadinessGate() *ReadinessGateApplyConfiguration { + return &ReadinessGateApplyConfiguration{} +} + +// WithConditionType sets the ConditionType field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ConditionType field is set to the value of the last call. +func (b *ReadinessGateApplyConfiguration) WithConditionType(value string) *ReadinessGateApplyConfiguration { + b.ConditionType = &value + return b +} diff --git a/api/generated/applyconfigurations/kptfile/v1/renderstatus.go b/api/generated/applyconfigurations/kptfile/v1/renderstatus.go new file mode 100644 index 0000000000..11c2baaff8 --- /dev/null +++ b/api/generated/applyconfigurations/kptfile/v1/renderstatus.go @@ -0,0 +1,68 @@ +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +// RenderStatusApplyConfiguration represents a declarative configuration of the RenderStatus type for use +// with apply. +// +// RenderStatus represents the result of performing render operation +// on a package's resources. +type RenderStatusApplyConfiguration struct { + MutationSteps []PipelineStepResultApplyConfiguration `json:"mutationSteps,omitempty"` + ValidationSteps []PipelineStepResultApplyConfiguration `json:"validationSteps,omitempty"` + ErrorSummary *string `json:"errorSummary,omitempty"` +} + +// RenderStatusApplyConfiguration constructs a declarative configuration of the RenderStatus type for use with +// apply. +func RenderStatus() *RenderStatusApplyConfiguration { + return &RenderStatusApplyConfiguration{} +} + +// WithMutationSteps adds the given value to the MutationSteps field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the MutationSteps field. +func (b *RenderStatusApplyConfiguration) WithMutationSteps(values ...*PipelineStepResultApplyConfiguration) *RenderStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithMutationSteps") + } + b.MutationSteps = append(b.MutationSteps, *values[i]) + } + return b +} + +// WithValidationSteps adds the given value to the ValidationSteps field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the ValidationSteps field. +func (b *RenderStatusApplyConfiguration) WithValidationSteps(values ...*PipelineStepResultApplyConfiguration) *RenderStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithValidationSteps") + } + b.ValidationSteps = append(b.ValidationSteps, *values[i]) + } + return b +} + +// WithErrorSummary sets the ErrorSummary field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ErrorSummary field is set to the value of the last call. +func (b *RenderStatusApplyConfiguration) WithErrorSummary(value string) *RenderStatusApplyConfiguration { + b.ErrorSummary = &value + return b +} diff --git a/api/generated/applyconfigurations/kptfile/v1/resourcereference.go b/api/generated/applyconfigurations/kptfile/v1/resourcereference.go new file mode 100644 index 0000000000..146bdcedc0 --- /dev/null +++ b/api/generated/applyconfigurations/kptfile/v1/resourcereference.go @@ -0,0 +1,72 @@ +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +// ResourceReferenceApplyConfiguration represents a declarative configuration of the ResourceReference type for use +// with apply. +// +// ResourceReference identifies a resource within the package by its API identity. +type ResourceReferenceApplyConfiguration struct { + // APIVersion of the referenced resource (e.g. "v1"). Optional; when omitted, + // matches resources regardless of apiVersion. + APIVersion *string `json:"apiVersion,omitempty"` + // Kind of the referenced resource (e.g. "ConfigMap"). Required. + Kind *string `json:"kind,omitempty"` + // Name of the referenced resource. Required. + Name *string `json:"name,omitempty"` + // Namespace of the referenced resource. Optional; when omitted, matches + // resources regardless of namespace. + Namespace *string `json:"namespace,omitempty"` +} + +// ResourceReferenceApplyConfiguration constructs a declarative configuration of the ResourceReference type for use with +// apply. +func ResourceReference() *ResourceReferenceApplyConfiguration { + return &ResourceReferenceApplyConfiguration{} +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *ResourceReferenceApplyConfiguration) WithAPIVersion(value string) *ResourceReferenceApplyConfiguration { + b.APIVersion = &value + return b +} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *ResourceReferenceApplyConfiguration) WithKind(value string) *ResourceReferenceApplyConfiguration { + b.Kind = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *ResourceReferenceApplyConfiguration) WithName(value string) *ResourceReferenceApplyConfiguration { + b.Name = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *ResourceReferenceApplyConfiguration) WithNamespace(value string) *ResourceReferenceApplyConfiguration { + b.Namespace = &value + return b +} diff --git a/api/generated/applyconfigurations/kptfile/v1/selector.go b/api/generated/applyconfigurations/kptfile/v1/selector.go new file mode 100644 index 0000000000..2136f81afa --- /dev/null +++ b/api/generated/applyconfigurations/kptfile/v1/selector.go @@ -0,0 +1,103 @@ +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +// SelectorApplyConfiguration represents a declarative configuration of the Selector type for use +// with apply. +// +// Selector specifies the selection criteria +// please update IsEmpty method if more properties are added +type SelectorApplyConfiguration struct { + // APIVersion of the target resources + APIVersion *string `json:"apiVersion,omitempty"` + // Kind of the target resources + Kind *string `json:"kind,omitempty"` + // Name of the target resources + Name *string `json:"name,omitempty"` + // Namespace of the target resources + Namespace *string `json:"namespace,omitempty"` + // Labels on the target resources + Labels map[string]string `json:"labels,omitempty"` + // Annotations on the target resources + Annotations map[string]string `json:"annotations,omitempty"` +} + +// SelectorApplyConfiguration constructs a declarative configuration of the Selector type for use with +// apply. +func Selector() *SelectorApplyConfiguration { + return &SelectorApplyConfiguration{} +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *SelectorApplyConfiguration) WithAPIVersion(value string) *SelectorApplyConfiguration { + b.APIVersion = &value + return b +} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *SelectorApplyConfiguration) WithKind(value string) *SelectorApplyConfiguration { + b.Kind = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *SelectorApplyConfiguration) WithName(value string) *SelectorApplyConfiguration { + b.Name = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *SelectorApplyConfiguration) WithNamespace(value string) *SelectorApplyConfiguration { + b.Namespace = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *SelectorApplyConfiguration) WithLabels(entries map[string]string) *SelectorApplyConfiguration { + if b.Labels == nil && len(entries) > 0 { + b.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *SelectorApplyConfiguration) WithAnnotations(entries map[string]string) *SelectorApplyConfiguration { + if b.Annotations == nil && len(entries) > 0 { + b.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Annotations[k] = v + } + return b +} diff --git a/api/generated/applyconfigurations/kptfile/v1/status.go b/api/generated/applyconfigurations/kptfile/v1/status.go new file mode 100644 index 0000000000..069ebbc3c8 --- /dev/null +++ b/api/generated/applyconfigurations/kptfile/v1/status.go @@ -0,0 +1,51 @@ +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +// StatusApplyConfiguration represents a declarative configuration of the Status type for use +// with apply. +type StatusApplyConfiguration struct { + Conditions []ConditionApplyConfiguration `json:"conditions,omitempty"` + RenderStatus *RenderStatusApplyConfiguration `json:"renderStatus,omitempty"` +} + +// StatusApplyConfiguration constructs a declarative configuration of the Status type for use with +// apply. +func Status() *StatusApplyConfiguration { + return &StatusApplyConfiguration{} +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *StatusApplyConfiguration) WithConditions(values ...*ConditionApplyConfiguration) *StatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} + +// WithRenderStatus sets the RenderStatus field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RenderStatus field is set to the value of the last call. +func (b *StatusApplyConfiguration) WithRenderStatus(value *RenderStatusApplyConfiguration) *StatusApplyConfiguration { + b.RenderStatus = value + return b +} diff --git a/api/generated/applyconfigurations/kptfile/v1/upstream.go b/api/generated/applyconfigurations/kptfile/v1/upstream.go new file mode 100644 index 0000000000..9a839fb7b6 --- /dev/null +++ b/api/generated/applyconfigurations/kptfile/v1/upstream.go @@ -0,0 +1,64 @@ +// 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. + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +import ( + kptfilev1 "github.com/kptdev/kpt/api/kptfile/v1" +) + +// UpstreamApplyConfiguration represents a declarative configuration of the Upstream type for use +// with apply. +// +// Upstream is a user-specified upstream locator for a package. +type UpstreamApplyConfiguration struct { + // Type is the type of origin. + Type *kptfilev1.OriginType `json:"type,omitempty"` + // Git is the locator for a package stored on Git. + Git *GitApplyConfiguration `json:"git,omitempty"` + // UpdateStrategy declares how a package will be updated from upstream. + UpdateStrategy *kptfilev1.UpdateStrategyType `json:"updateStrategy,omitempty"` +} + +// UpstreamApplyConfiguration constructs a declarative configuration of the Upstream type for use with +// apply. +func Upstream() *UpstreamApplyConfiguration { + return &UpstreamApplyConfiguration{} +} + +// WithType sets the Type field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Type field is set to the value of the last call. +func (b *UpstreamApplyConfiguration) WithType(value kptfilev1.OriginType) *UpstreamApplyConfiguration { + b.Type = &value + return b +} + +// WithGit sets the Git field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Git field is set to the value of the last call. +func (b *UpstreamApplyConfiguration) WithGit(value *GitApplyConfiguration) *UpstreamApplyConfiguration { + b.Git = value + return b +} + +// WithUpdateStrategy sets the UpdateStrategy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UpdateStrategy field is set to the value of the last call. +func (b *UpstreamApplyConfiguration) WithUpdateStrategy(value kptfilev1.UpdateStrategyType) *UpstreamApplyConfiguration { + b.UpdateStrategy = &value + return b +} diff --git a/api/go.mod b/api/go.mod index 282a2327ef..387f166a19 100644 --- a/api/go.mod +++ b/api/go.mod @@ -20,6 +20,7 @@ require ( github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/spf13/pflag v1.0.9 // indirect github.com/xlab/treeprint v1.2.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect @@ -28,6 +29,6 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/api/go.sum b/api/go.sum index 84f50d971d..ce03967a33 100644 --- a/api/go.sum +++ b/api/go.sum @@ -47,8 +47,8 @@ github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -80,8 +80,8 @@ k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hk k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -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/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/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI= sigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= diff --git a/api/kptfile/v1/types.go b/api/kptfile/v1/types.go index 8b97cbb7ac..b1dcfb2248 100644 --- a/api/kptfile/v1/types.go +++ b/api/kptfile/v1/types.go @@ -16,6 +16,9 @@ // Version: v1 // swagger:meta // +kubebuilder:object:generate=true +// +kubebuilder:ac:generate=true +// +kubebuilder:ac:output:package=../../generated/applyconfigurations +// +groupName=kpt.dev package v1 import ( @@ -27,8 +30,6 @@ import ( "sigs.k8s.io/kustomize/kyaml/yaml" ) -//go:generate go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.21.0 object:headerFile="../../../hack/boilerplate.go.txt",year=$YEAR_GEN - const ( KptFileName = "Kptfile" @@ -68,6 +69,7 @@ var TypeMeta = yaml.ResourceMeta{ // KptFile contains information about a package managed with kpt. // swagger:model kptfile +// +kubebuilder:ac:generate=true type KptFile struct { yaml.ResourceMeta `yaml:",inline" json:",inline"` diff --git a/api/schema/v1/group_version.go b/api/schema/v1/group_version.go index d40915deb9..c6283b2e53 100644 --- a/api/schema/v1/group_version.go +++ b/api/schema/v1/group_version.go @@ -25,8 +25,6 @@ import ( "strings" ) -//go:generate go run sigs.k8s.io/controller-tools/cmd/controller-gen@v0.21.0 object:headerFile="../../../hack/boilerplate.go.txt",year=$YEAR_GEN - // ParseResourceArg takes the common style of string which may be either `resource.group.com` or `resource.version.group.com` // and parses it out into both possibilities. This code takes no responsibility for knowing which representation was intended // but with a knowledge of all GroupVersions, calling code can take a very good guess. If there are only two segments, then From 3e042e3792cc4f3e934fdfe6c45b2fd2724a21a2 Mon Sep 17 00:00:00 2001 From: Aravindhan Ayyanathan Date: Tue, 8 Sep 2026 09:34:42 +0100 Subject: [PATCH 22/29] Docs: inline function Dockerfile, remove kfn download link (#4734) Signed-off-by: Aravindhan Ayyanathan --- .../en/book/05-developing-functions/_index.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/documentation/content/en/book/05-developing-functions/_index.md b/documentation/content/en/book/05-developing-functions/_index.md index 255f04c9b6..c7e0076283 100644 --- a/documentation/content/en/book/05-developing-functions/_index.md +++ b/documentation/content/en/book/05-developing-functions/_index.md @@ -350,9 +350,18 @@ standard input. You can now debug the KRM function in the VSCode debugger. Build the image -The "get-started" package provides the `Dockerfile` that you can download using: -```shell -wget https://raw.githubusercontent.com/kptdev/krm-functions-sdk/main/go/kfn/commands/embed/Dockerfile +Create a `Dockerfile` in your function's directory: +```dockerfile +FROM golang:1.26-alpine3.23 +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=0 /usr/local/bin/function /usr/local/bin/function +ENTRYPOINT ["function"] ``` ```shell From 4a75c991a02681b828c886c5ed713f8b64c22e9c Mon Sep 17 00:00:00 2001 From: Aravindhan Ayyanathan Date: Tue, 8 Sep 2026 10:17:17 +0100 Subject: [PATCH 23/29] Improve kpt version resolution in setup-go-kpt composite action (#4730) Signed-off-by: Aravindhan Ayyanathan --- .github/actions/setup-go-kpt/action.yml | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/actions/setup-go-kpt/action.yml b/.github/actions/setup-go-kpt/action.yml index ff428d8a93..6fb4ffc732 100644 --- a/.github/actions/setup-go-kpt/action.yml +++ b/.github/actions/setup-go-kpt/action.yml @@ -23,7 +23,7 @@ inputs: kpt-fallback-version: description: "Fallback kpt version if extraction from go.mod fails" required: false - default: "v1.0.0-beta.65" + default: "v1.0.0" go-version: description: "Exact version for Go version setup, go-version-file is ignored if set" required: false @@ -31,6 +31,10 @@ inputs: description: "Path to go.mod file for Go version setup" required: false default: "go.mod" + kpt-version-file: + description: "Path to the go.mod file used to resolve the kpt version. Defaults to go-version-file." + required: false + default: "" go-cache: description: "Enable Go module caching" required: false @@ -62,14 +66,26 @@ runs: shell: bash env: GO_VERSION_FILE: ${{ inputs.go-version-file }} + KPT_VERSION_FILE: ${{ inputs.kpt-version-file }} KPT_FALLBACK: ${{ inputs.kpt-fallback-version }} run: | - GO_MOD_DIR=$(dirname -- "$GO_VERSION_FILE") - KPT_VERSION=$(cd "$GO_MOD_DIR" && go list -m -f '{{.Version}}' github.com/kptdev/kpt 2>/dev/null) + set -euo pipefail + VERSION_FILE="${KPT_VERSION_FILE:-$GO_VERSION_FILE}" + KPT_VERSION="" + if [ ! -f "$VERSION_FILE" ]; then + echo "::warning::kpt version file '${VERSION_FILE}' not found; using fallback ${KPT_FALLBACK}." + else + GO_MOD_DIR=$(dirname -- "$VERSION_FILE") + KPT_VERSION=$(cd "$GO_MOD_DIR" && go mod edit -json \ + | jq -r '(.Require[]? | select(.Path=="github.com/kptdev/kpt") | .Version) // ""') + if [ -z "$KPT_VERSION" ]; then + echo "::warning::Could not resolve github.com/kptdev/kpt from ${VERSION_FILE}; using fallback ${KPT_FALLBACK}." + fi + fi if [ -z "$KPT_VERSION" ]; then - echo "Warning: Could not resolve kpt version from ${GO_VERSION_FILE}, using fallback." KPT_VERSION="$KPT_FALLBACK" fi + echo "Using kpt version: ${KPT_VERSION}" echo "version=${KPT_VERSION}" >> "$GITHUB_OUTPUT" - name: Install kpt From 9bc3ad31389993f129f0742d5384b5ab61243307 Mon Sep 17 00:00:00 2001 From: Aravindhan Ayyanathan Date: Tue, 8 Sep 2026 11:36:08 +0100 Subject: [PATCH 24/29] Docs: Add external link validation with lychee (#4707) * Docs: Add external link validation with lychee Signed-off-by: Aravindhan Ayyanathan * Address review comments Signed-off-by: Aravindhan Ayyanathan --------- Signed-off-by: Aravindhan Ayyanathan --- .github/workflows/docs-weekly.yml | 73 +++++++++++++++++++ README.md | 2 +- documentation/Makefile | 5 ++ documentation/README.md | 28 +++++++ documentation/content/en/_index.md | 2 +- .../en/book/01-getting-started/_index.md | 2 +- .../en/book/04-using-functions/_index.md | 2 +- .../en/book/05-developing-functions/_index.md | 2 +- .../07-effective-customizations/_index.md | 2 +- .../content/en/installation/migration.md | 10 +-- .../en/reference/cli/fn/eval/_index.md | 2 +- .../_index.md | 6 +- .../schema/crd-status-convention/_index.md | 2 +- documentation/layouts/partials/footer.html | 4 +- documentation/lychee.toml | 51 +++++++++++++ 15 files changed, 175 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/docs-weekly.yml create mode 100644 documentation/lychee.toml diff --git a/.github/workflows/docs-weekly.yml b/.github/workflows/docs-weekly.yml new file mode 100644 index 0000000000..40833bf72a --- /dev/null +++ b/.github/workflows/docs-weekly.yml @@ -0,0 +1,73 @@ +# 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. + +# Weekly documentation verification: spelling and external link checking. + +name: Docs Weekly +on: + schedule: + - cron: '0 3 * * 1' # Every Monday at 03:00 UTC + workflow_dispatch: # Allow manual triggering + +jobs: + docs-verify: + name: docs-verify + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + submodules: true + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: documentation/go.mod + cache: false + + - name: Get Hugo version + id: hugo-version + run: echo "version=$(grep -m1 'HUGO_VERSION' documentation/netlify.toml | cut -d'"' -f2)" >> "$GITHUB_OUTPUT" + + - name: Set up Hugo + uses: peaceiris/actions-hugo@v3 + with: + hugo-version: ${{ steps.hugo-version.outputs.version }} + extended: true + + - name: Set up Node.js + uses: actions/setup-node@v7 + with: + node-version-file: documentation/package.json + + - name: Install Node dependencies + working-directory: documentation + run: npm install + + - name: Verify spelling + working-directory: documentation + run: make verify + + - name: Build site + working-directory: documentation + run: make production-build + + - name: Check external links + uses: lycheeverse/lychee-action@v2 + with: + args: '-vv --config lychee.toml --root-dir public --extensions html public' + lycheeVersion: v0.24.2 + fail: true + workingDirectory: documentation diff --git a/README.md b/README.md index b18688df98..0f2bcc031d 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ scale by manipulating declarative Configuration as Data. See [the FAQ](https://kpt.dev/faq/) for more details about how kpt is different from alternatives. -Use our [public Dosu space](https://github.dosu.com/kptdev/kpt) to ask anything about kpt. +Use our [public Dosu space](https://app.dosu.dev/b19fb34f-d249-48ae-a3b1-2bd8ec156e41/ask) to ask anything about kpt. ## Why kpt? diff --git a/documentation/Makefile b/documentation/Makefile index a767c9f1eb..35af898ad2 100644 --- a/documentation/Makefile +++ b/documentation/Makefile @@ -16,6 +16,11 @@ serve: verify: go run github.com/golangci/misspell/cmd/misspell@v0.8.0 -error content/ +.PHONY: check-links-external +check-links-external: production-build + @command -v lychee >/dev/null 2>&1 || { echo "Error: lychee not found. Install it from https://github.com/lycheeverse/lychee#installation"; exit 1; } + lychee -vv --config lychee.toml --root-dir public --extensions html public + production-build: git submodule update --init --recursive git fetch --tags diff --git a/documentation/README.md b/documentation/README.md index 40c20cb484..715ad5c053 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -33,6 +33,34 @@ The site pulls some dependencies via Git submodules. If `npm install` succeeds b git submodule update --init --recursive ``` +## Checking external links + +To validate external links in the documentation, use: + +```sh +make check-links-external +``` + +This builds the site with Hugo and runs [lychee](https://github.com/lycheeverse/lychee) against the rendered HTML. + +### Prerequisites + +- **Hugo** — installed via `npm install` (from `devDependencies`) or [standalone](https://gohugo.io/installation/) +- **lychee** — install via one of: + - macOS: `brew install lychee` + - Linux/macOS (Cargo): `cargo install lychee` + - Binary download: see [lychee releases](https://github.com/lycheeverse/lychee/releases) + +### Using a GitHub token + +To avoid GitHub rate limiting, pass a token: + +```sh +GITHUB_TOKEN=$(gh auth token) make check-links-external +``` + +The token is only sent to github.com domains. + ## Style guide for documentation 1. Use US English in the documentation diff --git a/documentation/content/en/_index.md b/documentation/content/en/_index.md index 868a56a0be..6852f130a5 100644 --- a/documentation/content/en/_index.md +++ b/documentation/content/en/_index.md @@ -47,7 +47,7 @@ kpt is an open source project and anyone can [contribute](https://github.com/kpt To get familiar with kpt, the best way to start is with the first 4 chapters of the kpt [Book]({{% relref "book" %}}). Furthermore it is useful to check the [references]({{% relref "reference" %}}) and the catalog of [selected krm functions](https://catalog.kpt.dev). -Use our [public Dosu space](https://github.dosu.com/kptdev/kpt) to ask anything about kpt. +Use our [public Dosu space](https://app.dosu.dev/b19fb34f-d249-48ae-a3b1-2bd8ec156e41/ask) to ask anything about kpt. # For admins diff --git a/documentation/content/en/book/01-getting-started/_index.md b/documentation/content/en/book/01-getting-started/_index.md index 293ca8d4eb..5fe7f18fd9 100644 --- a/documentation/content/en/book/01-getting-started/_index.md +++ b/documentation/content/en/book/01-getting-started/_index.md @@ -30,7 +30,7 @@ configured. #### Docker -Follow the [instructions](https://docs.docker.com/get-docker) to install and configure Docker. +Follow the [instructions](https://docs.docker.com/get-started/get-docker/) to install and configure Docker. #### Podman diff --git a/documentation/content/en/book/04-using-functions/_index.md b/documentation/content/en/book/04-using-functions/_index.md index 6e2bf08c0a..592a14c572 100644 --- a/documentation/content/en/book/04-using-functions/_index.md +++ b/documentation/content/en/book/04-using-functions/_index.md @@ -847,7 +847,7 @@ kpt fn eval wordpress -i kubeconform:latest --network -- schema_location="https: #### Mounting directories -By default, the functions cannot access the host file system. You can use the `--mount` flag to mount the host volumes. kpt accepts the same options to `--mount`, as specified on the [Docker Volumes](https://docs.docker.com/storage/volumes/) page. +By default, the functions cannot access the host file system. You can use the `--mount` flag to mount the host volumes. kpt accepts the same options to `--mount`, as specified on the [Docker Volumes](https://docs.docker.com/engine/storage/volumes/) page. The `kubeconform` function can, for example, consume a JSON schema file, as follows: diff --git a/documentation/content/en/book/05-developing-functions/_index.md b/documentation/content/en/book/05-developing-functions/_index.md index c7e0076283..62d66d5b1e 100644 --- a/documentation/content/en/book/05-developing-functions/_index.md +++ b/documentation/content/en/book/05-developing-functions/_index.md @@ -127,7 +127,7 @@ for writing functions that manipulate KRM. Go provides: - [Install kpt]({{% relref "/installation/kpt-cli" %}}) -- [Install Docker](https://docs.docker.com/get-docker/) +- [Install Docker](https://docs.docker.com/get-started/get-docker/) - [Golang](https://go.dev/dl/) (at least version 1.24) diff --git a/documentation/content/en/book/07-effective-customizations/_index.md b/documentation/content/en/book/07-effective-customizations/_index.md index b106cf3ae0..54a14208f2 100644 --- a/documentation/content/en/book/07-effective-customizations/_index.md +++ b/documentation/content/en/book/07-effective-customizations/_index.md @@ -326,7 +326,7 @@ The mutation pipeline fails because the Rego policy has been violated. When using template languages I am able to provide conditional statements based on parameter values. This allows me to ask the user for a little bit of information and generate a lot of boilerplate configuration. Some template -languages like [Jinja](https://palletsprojects.com/p/jinja/) are very robust and feature rich. +languages like [Jinja](https://palletsprojects.com/projects/jinja/) are very robust and feature rich. ### Problems: diff --git a/documentation/content/en/installation/migration.md b/documentation/content/en/installation/migration.md index 79c10c0169..37b2fa7d65 100644 --- a/documentation/content/en/installation/migration.md +++ b/documentation/content/en/installation/migration.md @@ -357,7 +357,7 @@ kpt `v0.39`) to `v1` version(compatible with kpt `v1.0`). [v0.39 commands]: https://kptdev.github.io/kpt/reference/ [v1.0 commands]: /reference/cli/ -[v1 kptfile]: https://github.com/kptdev/kpt/blob/main/pkg/api/kptfile/v1/types.go +[v1 kptfile]: https://github.com/kptdev/kpt/blob/main/api/kptfile/v1/types.go [starlark function]: https://catalog.kpt.dev/starlark/v0.2/ [apply-setters]: https://catalog.kpt.dev/apply-setters/v0.1/ [setter inheritance]: https://kptdev.github.io/kpt/concepts/setters/#inherit-setter-values-from-parent-package @@ -365,10 +365,10 @@ kpt `v0.39`) to `v1` version(compatible with kpt `v1.0`). [required setters]: https://kptdev.github.io/kpt/guides/producer/setters/#required-setters [auto-setters]: https://kptdev.github.io/kpt/concepts/setters/#auto-setters [migrating inventory objects]: https://kptdev.github.io/kpt/reference/live/alpha/ -[live migration]: https://kptdev.github.io/kpt/reference/cli/live/alpha/ +[live migration]: https://kptdev.github.io/kpt/reference/live/alpha/ [configpath]: /book/04-using-functions/01-declarative-function-execution?id=configpath -[example kpt package]: https://github.com/kptdev/krm-functions-catalog/tree/master/testdata/fix -[simple example]: https://github.com/kptdev/krm-functions-catalog/tree/master/functions/go/fix#examples +[example kpt package]: https://github.com/kptdev/krm-functions-catalog/tree/main/archived/functions/go/fix/testdata/fix +[simple example]: https://github.com/kptdev/krm-functions-catalog/tree/main/archived/functions/go/fix#examples [function config]: /book/04-using-functions/01-declarative-function-execution?id=configpath [starlark runtime]: https://kptdev.github.io/kpt/guides/producer/functions/starlark/ [update guide]: /book/03-packages/05-updating-a-package @@ -379,6 +379,6 @@ kpt `v0.39`) to `v1` version(compatible with kpt `v1.0`). [installation instructions]: /installation/ [install]: /installation/ [kpt-functions-catalog]: https://catalog.kpt.dev/ -[v1alpha1 kptfile]: https://github.com/kptdev/kpt/blob/master/pkg/kptfile/pkgfile.go#L39 +[v1alpha1 kptfile]: https://github.com/kptdev/kpt/blob/v0/pkg/kptfile/pkgfile.go#L39 [git clone]: https://git-scm.com/docs/git-clone [publish your package]: /book/03-packages/08-publishing-a-package diff --git a/documentation/content/en/reference/cli/fn/eval/_index.md b/documentation/content/en/reference/cli/fn/eval/_index.md index 3432935448..a3a6588172 100644 --- a/documentation/content/en/reference/cli/fn/eval/_index.md +++ b/documentation/content/en/reference/cli/fn/eval/_index.md @@ -329,5 +329,5 @@ $ KRM_FN_RUNTIME=podman kpt fn eval DIR -i ghcr.io/example.com/my-fn -[docker volumes]: https://docs.docker.com/storage/volumes/ +[docker volumes]: https://docs.docker.com/engine/storage/volumes/ [imperative function execution]: /book/04-using-functions/#imperative-function-execution diff --git a/documentation/content/en/reference/schema/config-connector-status-convention/_index.md b/documentation/content/en/reference/schema/config-connector-status-convention/_index.md index 442b9da15e..24fbeb9860 100644 --- a/documentation/content/en/reference/schema/config-connector-status-convention/_index.md +++ b/documentation/content/en/reference/schema/config-connector-status-convention/_index.md @@ -8,11 +8,11 @@ menu: parent: "Schema Reference" --- -`kpt` includes custom rules for [Config Connector](https://cloud.google.com/config-connector/docs/overview) resources to +`kpt` includes custom rules for [Config Connector](https://docs.cloud.google.com/config-connector/docs/overview) resources to make them easier to work with. This document describes how kpt uses fields and conditions on Config Connector resources to compute [reconcile status]({{% relref "/book/06-deploying-packages#reconcile-status" %}}). -[Config Connector](https://cloud.google.com/config-connector/docs/how-to/monitoring-your-resources) resources expose the +[Config Connector](https://docs.cloud.google.com/config-connector/docs/how-to/monitoring-your-resources) resources expose the `observedGeneration` field in the status object, and `kpt` will always report a resource as being `InProgress` if the `observedGeneration` doesn't match the value of `metadata.generation`. @@ -22,7 +22,7 @@ as `Current`, i.e it has been successfully reconciled. If the `Ready` condition is `False`, `kpt` will look at the `Reason` field on the condition object to determine whether the resource is making progress towards reconciliation. The possible values mirrors those used by -[Config Connector events](https://cloud.google.com/config-connector/docs/how-to/monitoring-your-resources#viewing_events). +[Config Connector events](https://docs.cloud.google.com/config-connector/docs/how-to/monitoring-your-resources#viewing_events). If the value is one of the following, the resource is considered to have failed reconciliation: - `ManagementConflict` diff --git a/documentation/content/en/reference/schema/crd-status-convention/_index.md b/documentation/content/en/reference/schema/crd-status-convention/_index.md index 6a76f57939..93710684ad 100644 --- a/documentation/content/en/reference/schema/crd-status-convention/_index.md +++ b/documentation/content/en/reference/schema/crd-status-convention/_index.md @@ -10,7 +10,7 @@ menu: To enable kpt to calculate the [reconcile status]({{% relref "/book/06-deploying-packages#reconcile-status" %}}) for CRDs, this document provides additional conventions for status conditions following the -[Kubernetes API Guideline](https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md). +[Kubernetes API Guideline](https://github.com/kubernetes/community/blob/main/contributors/devel/sig-architecture/api-conventions.md). Custom controllers should use the following conditions types to signal whether a resource has been fully reconciled, and whether it has encountered any problems: diff --git a/documentation/layouts/partials/footer.html b/documentation/layouts/partials/footer.html index c37a8aae5c..a6ae508286 100644 --- a/documentation/layouts/partials/footer.html +++ b/documentation/layouts/partials/footer.html @@ -93,8 +93,8 @@ kpt follows The CNCF Code of Conduct | This site was built with the Docsy Hugo theme and deployed to Netlify. - Deploys by Netlify
- For website terms of use, trademark policy and other project policies please see lfprojects.org/policies.
+ Deploys by Netlify
+ For website terms of use, trademark policy and other project policies please see lfprojects.org/policies.
The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page. {{ if not .Site.Params.ui.footer_about_disable }} diff --git a/documentation/lychee.toml b/documentation/lychee.toml new file mode 100644 index 0000000000..f5dd31fd8a --- /dev/null +++ b/documentation/lychee.toml @@ -0,0 +1,51 @@ +# 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. + +# lychee configuration for external link validation. +# See https://lychee.cli.rs for full documentation. +# +# Run: make check-links-external +# With GitHub token (recommended): GITHUB_TOKEN= make check-links-external + +# Only check http/https links. +scheme = ["https", "http"] + +# Resolve root-relative links against the Hugo output directory. +root_dir = "public" + +require_https = true +timeout = 30 +max_retries = 3 +max_concurrency = 64 +accept_timeouts = true +user_agent = "Mozilla/5.0 (compatible; kpt-link-checker; +https://kpt.dev)" + +# Exclude URLs from checking (regex patterns). +exclude = [ + # Slack requires authentication; always returns 403 to bots. + "^https://kubernetes\\.slack\\.com", + "^https://slack\\.k8s\\.io", + # GitHub org project boards require a browser session. + "^https://github\\.com/orgs/.*/projects/", + # Netlify intermittently blocks CI runners. + "^https://www\\.netlify\\.com", + # Docsy theme "Edit this page" and "Create issue" links always redirect to login. + "^https://github\\.com/kptdev/kpt/edit/", + "^https://github\\.com/kptdev/kpt/issues/new", +] + +# Per-host rate limiting to avoid triggering abuse detection. +[hosts."github.com"] +concurrency = 1 +request_interval = "3s" From b479f20d0be23a904a90d12bc9057dd9ab742300 Mon Sep 17 00:00:00 2001 From: Liam Fallon <35595825+liamfallon@users.noreply.github.com> Date: Wed, 9 Sep 2026 11:18:50 +0100 Subject: [PATCH 25/29] Add a script to generate PR and release reports (#4729) * Add a script to generate PR and release reports Signed-off-by: liamfallon * Addressed copilot comments Signed-off-by: liamfallon --------- Signed-off-by: liamfallon --- scripts/reports/create-repo-report.sh | 90 +++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100755 scripts/reports/create-repo-report.sh diff --git a/scripts/reports/create-repo-report.sh b/scripts/reports/create-repo-report.sh new file mode 100755 index 0000000000..8043871f69 --- /dev/null +++ b/scripts/reports/create-repo-report.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash + +# 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. + +repos=( + "https://github.com/kptdev/kpt" + "https://github.com/kptdev/porch" + "https://github.com/kptdev/krm-functions-catalog" + "https://github.com/kptdev/krm-functions-sdk" + "https://github.com/kptdev/kpt-backstage-plugins" + "https://github.com/kptdev/kpt-samples" +) + +function clone_repos () { + repo_dir=$1 + + for repo in "${repos[@]}" + do + git clone "$repo" > /dev/null 2>&1 + done +} + +function create_pr_table () { + echo "## PRs to be reviewed" + echo "" + echo "Generated on $(TZ=UTC date)" + echo "" + + for repo in "${repos[@]}" + do + repo_heading=$(echo "$repo" | awk -F'/' '{printf("### [%s](https://%s/%s/%s/pulls)\n", $5, $3, $4, $5)}') + kpt_repo=$(echo "$repo" | sed 's/.*\///') + pushd "$kpt_repo" > /dev/null || exit + + echo "$repo_heading" + echo "" + echo "| Number | Title | Author | Draft | Updated | Comment |" + echo "|-|-|-|-|-|-|" + + gh pr ls -L 200 --json title,author,isDraft,updatedAt,url | jq -r '.[] | "| \(.url) | \(.title) | \(.author.login) | \(.isDraft) | \(.updatedAt) | |"' || exit + popd > /dev/null || exit + echo "" + done +} + +function create_release_table () { + echo "## Releases in kptdev" + echo "" + echo "Generated on $(TZ=UTC date)" + echo "" + + for repo in "${repos[@]}" + do + repo_heading=$(echo "$repo" | awk -F'/' '{printf("### [%s](https://%s/%s/%s/releases)\n", $5, $3, $4, $5)}') + kpt_repo=$(echo "$repo" | sed 's/.*\///') + pushd "$kpt_repo" > /dev/null || exit + + echo "$repo_heading" + echo "" + echo "| Tag | Name | Latest | Pre Release | Published |" + echo "|-|-|-|-|-|" + + gh release ls -L 1000 --exclude-drafts --json tagName,name,isLatest,isPrerelease,publishedAt | jq -r '.[] | "| \(.tagName) | \(.name) | \(.isLatest) | \(.isPrerelease) | \(.publishedAt) | |" ' | grep ' 202[4-6]-' || exit + popd > /dev/null || exit + echo "" + done +} + +repo_dir=$(mktemp -d) +trap 'rm -rf "$repo_dir"' EXIT + +pushd "$repo_dir" > /dev/null || exit + +clone_repos "$repo_dir" +create_pr_table "$repo_dir" +create_release_table "$repo_dir" + +popd > /dev/null || exit From 8a233d3b03ca1a5338331f341c7e03eefaef40a0 Mon Sep 17 00:00:00 2001 From: Fiachra Corcoran Date: Wed, 9 Sep 2026 11:30:07 +0100 Subject: [PATCH 26/29] docs: add KRM function developer guide (#4726) * docs: add KRM function developer guide Add a developer guide for writing KRM functions with the Go SDK, covering a tutorial, interface selection (fn.Runner vs fn.ResourceListProcessor), testing, and containerizing. Sourced from the krm-functions-sdk docs and adapted into a Hugo subsection under guides/. Refs kptdev/kpt#4725 Signed-off-by: Fiachra Corcoran * docs: clarify golden-test, interface, and file-mode behavior Add behavioral details verified against the SDK source: - testing: Kptfile counted as input, non-YAML files ignored, inputs read in sorted order, and WRITE_GOLDEN_OUTPUT intentionally fails the writing run - interfaces: note the ConfigMap .data functionConfig path and that the Runner no-add/remove rule is a convention, not a compile-time restriction - containerizing: note file mode uses an empty functionConfig Refs kptdev/kpt#4725 Signed-off-by: Fiachra Corcoran * docs: reconcile developing-functions chapter with the new guide - Update the Chapter 5 Go quickstart to match the overhauled get-started scaffold (HelloWorld, hello-world testdata, results.Infof) and use the current KubeObject/Results API instead of the deprecated helpers - Cross-link Chapter 5 to the KRM Function Developer Guide for interface choice, testing, and containerizing - Add an interface-selection recommendation to the guide's interfaces page (transformers/validators -> fn.Runner; generators/complex -> fn.ResourceListProcessor) Refs kptdev/kpt#4725 Signed-off-by: Fiachra Corcoran --------- Signed-off-by: Fiachra Corcoran --- .../en/book/05-developing-functions/_index.md | 101 +++++--- documentation/content/en/guides/_index.md | 1 + .../content/en/guides/krm-functions/_index.md | 26 ++ .../en/guides/krm-functions/containerizing.md | 148 +++++++++++ .../en/guides/krm-functions/interfaces.md | 209 +++++++++++++++ .../en/guides/krm-functions/testing.md | 232 +++++++++++++++++ .../en/guides/krm-functions/tutorial.md | 237 ++++++++++++++++++ 7 files changed, 926 insertions(+), 28 deletions(-) create mode 100644 documentation/content/en/guides/krm-functions/_index.md create mode 100644 documentation/content/en/guides/krm-functions/containerizing.md create mode 100644 documentation/content/en/guides/krm-functions/interfaces.md create mode 100644 documentation/content/en/guides/krm-functions/testing.md create mode 100644 documentation/content/en/guides/krm-functions/tutorial.md diff --git a/documentation/content/en/book/05-developing-functions/_index.md b/documentation/content/en/book/05-developing-functions/_index.md index 62d66d5b1e..ed06f4e8a8 100644 --- a/documentation/content/en/book/05-developing-functions/_index.md +++ b/documentation/content/en/book/05-developing-functions/_index.md @@ -133,8 +133,14 @@ for writing functions that manipulate KRM. Go provides: ### Quickstart -In this quickstart, we will write a function called "set-annotation" that adds an annotation -`config.kubernetes.io/managed-by=kpt` to all `Deployment` resources. +In this quickstart, we will start from the get-started scaffold — a small +"hello world" function that stamps a greeting annotation on every resource — and +adapt it into a function that adds `config.kubernetes.io/managed-by=kpt` to all +`Deployment` resources. + +For a deeper treatment of function development — choosing an interface, testing +with golden files, and containerizing — see the +[KRM Function Developer Guide]({{% relref "/guides/krm-functions" %}}). #### Set up your project @@ -181,6 +187,7 @@ package main import ( "context" _ "embed" + "fmt" "os" "github.com/kptdev/krm-functions-sdk/go/fn" @@ -192,26 +199,48 @@ var readme []byte //go:embed metadata.yaml var metadata []byte -var _ fn.Runner = &YourFunction{} +// greetingAnnotation is the annotation this example stamps onto every resource. +const greetingAnnotation = "example.kpt.dev/greeting" -// TODO: Change to your functionConfig "Kind" name. -type YourFunction struct { - FnConfigBool bool - FnConfigInt int - FnConfigFoo string +var _ fn.Runner = &HelloWorld{} + +// HelloWorld is the functionConfig for this example. The struct name is used as +// the functionConfig `kind`, and each exported field is populated from the +// matching functionConfig key via its JSON tag. +// +// TODO: Rename this struct to your functionConfig "kind" and replace the fields +// with the configuration your function needs. +type HelloWorld struct { + Greeting string `json:"greeting,omitempty"` + Name string `json:"name,omitempty"` } // Run is the main function logic. // `items` is parsed from the STDIN "ResourceList.Items". -// `functionConfig` is from the STDIN "ResourceList.FunctionConfig". The value has been assigned to the r attributes +// `functionConfig` is from the STDIN "ResourceList.FunctionConfig". Its values +// have already been unmarshaled into the receiver's fields. // `results` is the "ResourceList.Results" that you can write result info to. -func (r *YourFunction) Run(ctx *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects, results *fn.Results) bool { - // TODO: Write your code. - return true +func (r *HelloWorld) Run(ctx *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects, results *fn.Results) bool { + greeting := r.Greeting + if greeting == "" { + greeting = "Hello" + } + name := r.Name + if name == "" { + name = "world" + } + message := fmt.Sprintf("%s, %s!", greeting, name) + for _, obj := range items { + if err := obj.SetAnnotation(greetingAnnotation, message); err != nil { + results.ErrorE(err) + } + } + results.Infof("greeted %d resource(s) with %q", len(items), message) + return results.ExitCode() == 0 } func main() { - runner := fn.WithContext(context.Background(), &YourFunction{}) + runner := fn.WithContext(context.Background(), &HelloWorld{}) if err := fn.AsMain(runner, fn.WithDocs(readme, metadata)); err != nil { os.Exit(1) } @@ -225,19 +254,21 @@ Basically, the KRM resource `ResourceList.FunctionConfig` and KRM resources `Res `KubeObject` objects. You can use `KubeObject` in a similar manner to [`unstructured.Unstructured`](https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured). -The set-annotation function (see below) iterates the `ResourceList.Items`, finds out the `Deployment` resources and -adds the annotation. After the iteration, it adds some user message to the `ResourceList.Results` +The set-annotation function (see below) iterates the `ResourceList.Items`, finds the `Deployment` resources and +adds the annotation. After the iteration, it reports a user message to the `ResourceList.Results` via `results.Infof`. ```go func (r *YourFunction) Run(ctx *fn.Context, functionConfig *fn.KubeObject, items fn.KubeObjects, results *fn.Results) bool { for _, kubeObject := range items { - if kubeObject.IsGVK("apps", "v1", "Deployment") { - kubeObject.SetAnnotation("config.kubernetes.io/managed-by", "kpt") + if kubeObject.GetKind() == "Deployment" { + if err := kubeObject.SetAnnotation("config.kubernetes.io/managed-by", "kpt"); err != nil { + results.ErrorE(err) + } } } - // This result message will be displayed in the function evaluation time. - *results = append(*results, fn.GeneralResult("Add config.kubernetes.io/managed-by=kpt to all `Deployment` resources", fn.Info)) - return true + // This result message will be displayed at function evaluation time. + results.Infof("added config.kubernetes.io/managed-by=kpt to all Deployment resources") + return results.ExitCode() == 0 } ``` @@ -248,16 +279,16 @@ Learn more about the `KubeObject` from the [go documentation](https://pkg.go.dev The "get-started" package contains a `./testdata` directory. You can use this to test out your functions. ```shell -# Edit the `testdata/noop-passthrough/resources.yaml` with your KRM resources. -# resources.yaml already has a `Deployment` and `Service` as test data. -vim testdata/noop-passthrough/resources.yaml +# Edit `testdata/hello-world/resources.yaml` with your KRM resources. +# Add a `Deployment` so the set-annotation logic above has something to match. +vim testdata/hello-world/resources.yaml # Convert the KRM resources and FunctionConfig resource to `ResourceList`, and # then pipe the ResourceList as StdIn to your function kpt fn source testdata | go run main.go ``` -Verify the KRM function behavior in the StdOutput `ResourceList` by looking for the new annotation on the "nginx-deplyment": +Verify the KRM function behavior in the StdOutput `ResourceList` by looking for the new annotation on the `Deployment`: ```yaml apiVersion: apps/v1 @@ -281,10 +312,19 @@ kubeObject.SetAnnotation("config.kubernetes.io/managed-by", "kpt") to ```shell -kubeObject.SetAnnotation("config.kubernetes.io/managed-by", r.FnConfigFoo) +kubeObject.SetAnnotation("config.kubernetes.io/managed-by", r.ManagedBy) +``` + +Add a `ManagedBy` field to your struct so the value can be read from the +functionConfig: + +```go +type YourFunction struct { + ManagedBy string `json:"managedBy,omitempty"` +} ``` -The annotation value will be set from the value of the `FnConfigFoo` field. +The annotation value will be set from the value of the `managedBy` field. Create the configuration information so that we can concatenate it onto the ResourceList generated by the `kpt fn source` command. This configuration specifies that the "config.kubernetes.io/managed-by" annotation should be set to a value of "bar". @@ -298,7 +338,7 @@ functionConfig: name: test annotations: internal.kpt.dev/upstream-identifier: 'fn.kpt.dev|YourFunction|default|test' - fnConfigFoo: bar + managedBy: bar EOF ``` @@ -372,11 +412,16 @@ docker build . -t ${FN_CONTAINER_REGISTRY}/${FUNCTION_NAME}:${TAG} To verify the image using the same `./testdata` resources ```shell -kpt fn eval ./testdata/noop-passthrough/resources.yaml --image ${FN_CONTAINER_REGISTRY}/${FUNCTION_NAME}:${TAG} +kpt fn eval ./testdata/hello-world/resources.yaml --image ${FN_CONTAINER_REGISTRY}/${FUNCTION_NAME}:${TAG} ``` ### Next Steps +- Read the [KRM Function Developer Guide]({{% relref "/guides/krm-functions" %}}) + for choosing an [interface]({{% relref "/guides/krm-functions/interfaces" %}}) + (`fn.Runner` vs `fn.ResourceListProcessor`), + [testing]({{% relref "/guides/krm-functions/testing" %}}) with golden files, and + [containerizing]({{% relref "/guides/krm-functions/containerizing" %}}) your function. - See other [go documentation examples](https://pkg.go.dev/github.com/kptdev/krm-functions-sdk/go/fn/examples) to use KubeObject. - To contribute to KRM catalog functions, please follow the [contributor guide](https://github.com/kptdev/krm-functions-catalog/blob/main/CONTRIBUTING.md) - For the `metadata.yaml` schema reference (required fields, allowed tags), see the [metadata schema documentation](https://catalog.kpt.dev/metadata-schema/) diff --git a/documentation/content/en/guides/_index.md b/documentation/content/en/guides/_index.md index c77bd41329..6f8560792d 100644 --- a/documentation/content/en/guides/_index.md +++ b/documentation/content/en/guides/_index.md @@ -15,3 +15,4 @@ menu: - [Value Propagation Pattern]({{% relref "/guides/value-propagation" %}}) - [Tenant Onboarding]({{% relref "/guides/tenant-onboarding" %}}) - [Understanding 3-Way Merge in kpt]({{% relref "/guides/3-way-merge" %}}) +- [KRM Function Developer Guide]({{% relref "/guides/krm-functions" %}}) diff --git a/documentation/content/en/guides/krm-functions/_index.md b/documentation/content/en/guides/krm-functions/_index.md new file mode 100644 index 0000000000..efa109c729 --- /dev/null +++ b/documentation/content/en/guides/krm-functions/_index.md @@ -0,0 +1,26 @@ +--- +title: KRM Function Developer Guide +linkTitle: KRM Function Developer Guide +description: Write your own KRM functions with the Go SDK. +toc_hide: false +menu: + main: + parent: "Guides" +--- +This guide walks through writing KRM functions with the +[Go SDK](https://github.com/kptdev/krm-functions-sdk). Start with the tutorial, +then dig into the topic guides as needed. + +- [Tutorial]({{% relref "/guides/krm-functions/tutorial" %}}) — build a working + function end to end, with embedded documentation, golden tests, and support for + `--help`, `--doc`, and standalone file mode. +- [Interfaces]({{% relref "/guides/krm-functions/interfaces" %}}) — choose between + `fn.Runner` (transformers, validators) and `fn.ResourceListProcessor` + (generators, complex functions). +- [Testing]({{% relref "/guides/krm-functions/testing" %}}) — golden test patterns + and unit testing in depth. +- [Containerizing]({{% relref "/guides/krm-functions/containerizing" %}}) — package + your function as a container image. + +For a complete working example, see +[`go/get-started/`](https://github.com/kptdev/krm-functions-sdk/tree/main/go/get-started). diff --git a/documentation/content/en/guides/krm-functions/containerizing.md b/documentation/content/en/guides/krm-functions/containerizing.md new file mode 100644 index 0000000000..fb509d528c --- /dev/null +++ b/documentation/content/en/guides/krm-functions/containerizing.md @@ -0,0 +1,148 @@ +--- +title: Containerizing +linkTitle: Containerizing +description: Package a KRM function as a container image. +toc_hide: false +menu: + main: + parent: "KRM Function Developer Guide" + weight: 40 +--- +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 the 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 +``` + +Note: file mode assembles the given files into a ResourceList with an **empty +functionConfig**. Functions that require configuration should be run via STDIN +(or a `kpt` pipeline) so the functionConfig is provided. + +### 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. 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/documentation/content/en/guides/krm-functions/interfaces.md b/documentation/content/en/guides/krm-functions/interfaces.md new file mode 100644 index 0000000000..678e418e7d --- /dev/null +++ b/documentation/content/en/guides/krm-functions/interfaces.md @@ -0,0 +1,209 @@ +--- +title: Interfaces +linkTitle: Interfaces +description: Choose between fn.Runner and fn.ResourceListProcessor. +toc_hide: false +menu: + main: + parent: "KRM Function Developer Guide" + weight: 20 +--- +The SDK provides two interfaces for implementing KRM functions. Choose according +to your function requirements. + +> The `main` functions below call `fn.AsMain` without `fn.WithDocs` to keep the +> interface examples focused. Production functions should embed documentation and +> pass `fn.WithDocs(readme, metadata)` — see the +> [tutorial]({{% relref "/guides/krm-functions/tutorial" %}}). + +## 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. + A typed functionConfig (its `kind` matching your struct name) is unmarshaled via + JSON tags; alternatively, a `ConfigMap` functionConfig has its `.data` map assigned + to a `map[string]string` field on your struct. +- You can **modify** existing items, but adding or removing items is not supported. + This is a convention, not a compile-time restriction: the SDK does not read back + items appended inside `Run`, so adds and removes are effectively dropped. Use + `fn.ResourceListProcessor` when you need to add or remove items. +- 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 | + +As a rule of thumb, pick the interface by what your function does: + +- **Transformers and validators** — use `fn.Runner`. It auto-parses the + functionConfig and keeps the function focused on modifying items. Examples: + set-labels, set-namespace. +- **Generators and functions needing full ResourceList access** (adding or + removing items, reading results from earlier functions) — use + `fn.ResourceListProcessor`. Examples: render-helm-chart, starlark. + +Both produce spec-compliant ResourceList I/O; the choice is about ergonomics, so +use the one that fits your function rather than a hard requirement. + +## 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 the following: +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]({{% relref "/guides/krm-functions/testing" %}}) — golden test patterns for verifying your function. diff --git a/documentation/content/en/guides/krm-functions/testing.md b/documentation/content/en/guides/krm-functions/testing.md new file mode 100644 index 0000000000..ae27c66e4d --- /dev/null +++ b/documentation/content/en/guides/krm-functions/testing.md @@ -0,0 +1,232 @@ +--- +title: Testing +linkTitle: Testing +description: Golden tests and unit tests for KRM functions. +toc_hide: false +menu: + main: + parent: "KRM Function Developer Guide" + weight: 30 +--- +The SDK provides a golden test framework in `fn/testhelpers` for snapshot-based +testing of KRM functions. + +## Golden Test Pattern + +Golden tests compare the function output against the 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`/`.yml` files (and a `Kptfile`, if present) are parsed as input + resources. Files with any other extension are ignored. +- You can have multiple input files (e.g., `deployments.yaml`, `services.yaml`). + Input files are read in sorted (alphabetical) order, so the assembled item + ordering is deterministic. + +### 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(), &SetLabels{}) + 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 does not 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 any `_expected.yaml` that differs from the actual output. Any +non-empty value enables write mode (`WRITE_GOLDEN_OUTPUT=1`, `=true`, etc.). +Note that the run which writes a golden file is reported as a **test failure** +(`wrote output to ...`) — this is intentional, so a rewrite never silently +passes in CI. Re-run the tests without the env var to confirm they pass, and +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 have 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 also catch unintentional formatting changes. This helps to maintain a 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 the functions running inside the containers against `kpt fn render`. + +--- + +Next: [Containerizing]({{% relref "/guides/krm-functions/containerizing" %}}) — packaging your function as a container image. diff --git a/documentation/content/en/guides/krm-functions/tutorial.md b/documentation/content/en/guides/krm-functions/tutorial.md new file mode 100644 index 0000000000..194ff5fbf9 --- /dev/null +++ b/documentation/content/en/guides/krm-functions/tutorial.md @@ -0,0 +1,237 @@ +--- +title: Tutorial +linkTitle: Tutorial +description: Build a KRM function end to end with the Go SDK. +toc_hide: false +menu: + main: + parent: "KRM Function Developer Guide" + weight: 10 +--- +This tutorial walks through the end-to-end workflow for building a KRM function +using the Go SDK. By the end, you will 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/`](https://github.com/kptdev/krm-functions-sdk/tree/main/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 is a minimal function that sets labels on all the 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-catalog/tree/main/functions/go/set-labels +examplePackageURLs: + - https://github.com/kptdev/krm-functions-catalog/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]({{% relref "/guides/krm-functions/testing" %}}) for more details. + +## 5. Next Steps + +- [Interfaces]({{% relref "/guides/krm-functions/interfaces" %}}) — when to use `fn.Runner` vs `fn.ResourceListProcessor` +- [Testing]({{% relref "/guides/krm-functions/testing" %}}) — golden test patterns in depth +- [Containerizing]({{% relref "/guides/krm-functions/containerizing" %}}) — packaging your function as a container image From b976853e606f4efce3e55a7692c05771fe77f93a Mon Sep 17 00:00:00 2001 From: Gergely Csatari Date: Wed, 9 Sep 2026 16:38:05 +0300 Subject: [PATCH 27/29] Proofreading up to kptfile annotations. Signed-off-by: Michael Greaves Signed-off-by: Gergely Csatari --- .../content/en/book/02-concepts/_index.md | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/documentation/content/en/book/02-concepts/_index.md b/documentation/content/en/book/02-concepts/_index.md index 763c4c12e9..bc0ea29fcb 100644 --- a/documentation/content/en/book/02-concepts/_index.md +++ b/documentation/content/en/book/02-concepts/_index.md @@ -48,19 +48,17 @@ There are a number of key principles to be borne in mind, with regard to configu The kpt toolchain includes the following components: -- [**kpt CLI**]({{% relref "/reference/cli" %}}): The kpt CLI supports package and function - operations, as well as deployment, either through direct apply or through GitOps. By - keeping an inventory of deployed resources, kpt enables resource pruning, aggregated - status and observability, and an improved preview experience. +- [**kpt CLI**]({{% relref "/reference/cli" %}}): The kpt CLI supports package and function operations, as well as + deployment, either through direct apply or through GitOps. By keeping an inventory of deployed resources, kpt enables + resource pruning, aggregated status and observability, and an improved preview experience. -- [**Function SDK**](https://github.com/kptdev/krm-functions-sdk): Any general-purpose or - domain-specific language can be used to create functions to transform and/or validate the YAML - KRM input/output format. However, we provide software development kits (SDKs) to simplify the - function authoring process, in [Go]({{% relref "/book/05-developing-functions#developing-in-go" %}}). +- [**Function SDK**](https://github.com/kptdev/krm-functions-sdk): Any general-purpose or domain-specific language can + be used to create functions to transform and/or validate the YAML KRM input/output format. However, we provide SDKs to + simplify the function authoring process, in [Go]({{% relref "/book/05-developing-functions#developing-in-Go" %}}). -- [**Function catalog**](https://catalog.kpt.dev): This is a catalog of off-the-shelf, tested functions. - kpt makes configurations easy to create and transform, via reusable functions. Because the functions - are expected to be used for in-place transformation, they need to be idempotent. +- [**Function catalog**](https://catalog.kpt.dev): This is a catalog of off-the-shelf, tested functions. kpt makes + configurations easy to create and transform, via reusable functions. Because the functions are expected to be used for + in-place transformation, they need to be idempotent. ## Packages @@ -166,11 +164,15 @@ For example, `spark` is essentially a vanilla directory of KRM: kpt pkg get https://github.com/kubernetes/examples/tree/master/_archived/spark ``` +<<<<<<< HEAD <<<<<<< HEAD Details of how to work with packages are set out in [Chapter 3](../03-packages). ======= We will go into details of how to work with packages in [Chapter 3]({{% relref "/book/03-packages" %}}). >>>>>>> main +======= +Details of how to work with packages are set out in [Chapter 3]({{% relref "/book/03-packages" %}}). +>>>>>>> bba110b3d (Proofreading up to kptfile annotations.) ### Local Configuration @@ -242,7 +244,11 @@ Let us suppose that you have rendered the package, and would like to deploy it t - **Apply**: Use the `kpt live apply` command. - **Observe**: Use the `kpt live status` command. +<<<<<<< HEAD First, use the `kpt live apply --dry-run` command to validate the resources in your package and verify that the expected resources will be applied and pruned. If the preview looks good, then apply the package, using the `kpt live apply` command. Afterwards, you may observe the status of the package on the cluster. +======= +First, use the kpt `live apply --dry-run` command to validate the resources in your package and verify that the expected resources will be applied and pruned. If the preview looks good, then apply the package, using the `kpt live apply` command. Afterwards, you may observe the status of the package on the cluster. +>>>>>>> bba110b3d (Proofreading up to kptfile annotations.) Typically, it is best to store the package in Git: @@ -254,7 +260,11 @@ The publishing flow is orthogonal to the deployment flow. This allows you to act ## Functions +<<<<<<< HEAD A Kubernetes Resource Model (KRM) function (formerly called a _kpt function_) is a containerized program that can perform create, read, update, and delete (CRUD) operations on KRM resources stored on the local filesystem. KRM functions are the extensible mechanism to automate the mutation and validation of KRM resources. The following are some example use cases: +======= +A Kubernetes Resource Model (KRM) function (formerly called a _kpt_ function_) is a containerized program that can perform create, read, update, and delete (CRUD) operations on KRM resources stored on the local filesystem. kpt functions are the extensible mechanism to automate the mutation and validation of KRM resources. The following are some example use cases: +>>>>>>> bba110b3d (Proofreading up to kptfile annotations.) - Enforce all `Namespace` resources to have a `cost-center` label. - Add a label to resources based on certain filtering criteria. From efca0f043dacf01dd7fdcdf507e27146c281b857 Mon Sep 17 00:00:00 2001 From: Michael Greaves Date: Thu, 16 Apr 2026 10:56:42 +0200 Subject: [PATCH 28/29] Chapter 2 proofreading. Signed-off-by: Michael Greaves --- documentation/content/en/book/02-concepts/_index.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/documentation/content/en/book/02-concepts/_index.md b/documentation/content/en/book/02-concepts/_index.md index bc0ea29fcb..0b7744404d 100644 --- a/documentation/content/en/book/02-concepts/_index.md +++ b/documentation/content/en/book/02-concepts/_index.md @@ -53,8 +53,9 @@ The kpt toolchain includes the following components: resource pruning, aggregated status and observability, and an improved preview experience. - [**Function SDK**](https://github.com/kptdev/krm-functions-sdk): Any general-purpose or domain-specific language can - be used to create functions to transform and/or validate the YAML KRM input/output format. However, we provide SDKs to - simplify the function authoring process, in [Go]({{% relref "/book/05-developing-functions#developing-in-Go" %}}). + be used to create functions to transform and/or validate the YAML KRM input/output format. However, we provide + software development kits (SDKs) to simplify the function authoring process, in + [Go](../05-developing-functions/#developing-in-Go). - [**Function catalog**](https://catalog.kpt.dev): This is a catalog of off-the-shelf, tested functions. kpt makes configurations easy to create and transform, via reusable functions. Because the functions are expected to be used for @@ -327,6 +328,9 @@ We will discuss how to run functions in [Chapter 4](../04-using-functions), and ======= We will discuss how to run functions in [Chapter 4]({{% relref "/book/04-using-functions" %}}) and how to develop functions in [Chapter 5]({{% relref "/book/05-developing-functions" %}}). +<<<<<<< HEAD >>>>>>> main +======= +>>>>>>> dfe37451e (Chapter 2 proofreading.) From 0ee9faf62dbda0151875c9e834051c52bc7c06b0 Mon Sep 17 00:00:00 2001 From: mpgreaves <103421482+mpgreaves@users.noreply.github.com> Date: Thu, 23 Apr 2026 10:59:54 +0200 Subject: [PATCH 29/29] Apply suggestions from code review Signed-off-by: Michael Greaves Assisted-by: Copilot Code Review: not-known Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- documentation/content/en/book/02-concepts/_index.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/documentation/content/en/book/02-concepts/_index.md b/documentation/content/en/book/02-concepts/_index.md index 0b7744404d..934602c236 100644 --- a/documentation/content/en/book/02-concepts/_index.md +++ b/documentation/content/en/book/02-concepts/_index.md @@ -74,7 +74,7 @@ Validation goes hand-in-hand with customization. KRM functions can be used to au A kpt package is a bundle of configuration _data_. It is represented as a directory tree containing the KRM resources using YAML as the file format. -A package is explicitly declared using a file named `Kptfile`. This file contains a KRM resource of kind `Kptfile`. The Kptfile contains metadata about the package and is simply a regular resource in the YAML format. +A package is explicitly declared using a file named `Kptfile`. This file contains a KRM resource of type `Kptfile`. The Kptfile contains metadata about the package and is simply a regular resource in the YAML format. Just as directories can be nested, a package can contain another package. This is called a _subpackage_. @@ -245,11 +245,15 @@ Let us suppose that you have rendered the package, and would like to deploy it t - **Apply**: Use the `kpt live apply` command. - **Observe**: Use the `kpt live status` command. +<<<<<<< HEAD <<<<<<< HEAD First, use the `kpt live apply --dry-run` command to validate the resources in your package and verify that the expected resources will be applied and pruned. If the preview looks good, then apply the package, using the `kpt live apply` command. Afterwards, you may observe the status of the package on the cluster. ======= First, use the kpt `live apply --dry-run` command to validate the resources in your package and verify that the expected resources will be applied and pruned. If the preview looks good, then apply the package, using the `kpt live apply` command. Afterwards, you may observe the status of the package on the cluster. >>>>>>> bba110b3d (Proofreading up to kptfile annotations.) +======= +First, use the `kpt live apply --dry-run` command to validate the resources in your package and verify that the expected resources will be applied and pruned. If the preview looks good, then apply the package, using the `kpt live apply` command. Afterwards, you may observe the status of the package on the cluster. +>>>>>>> aa9e252df (Apply suggestions from code review) Typically, it is best to store the package in Git: