github.com/GoogleContainerTools/kpt@v1.0.0-beta.50.0.20240520170205-c25345ffcbee/docs/design-docs/08-package-variant.md (about)

     1  # Package Variant Controller
     2  
     3  * Author(s): @johnbelamaric, @natasha41575
     4  * Approver: @mortent
     5  
     6  ## Why
     7  
     8  When deploying workloads across large fleets of clusters, it is often necessary
     9  to modify the workload configuration for a specific cluster. Additionally, those
    10  workloads may evolve over time with security or other patches that require
    11  updates. [Configuration as Data](06-config-as-data.md) in general and [Package
    12  Orchestration](07-package-orchestration.md) in particular can assist in this.
    13  However, they are still centered around manual, one-by-one hydration and
    14  configuration of a workload.
    15  
    16  This proposal introduces concepts and a set of resources for automating the
    17  creation and lifecycle management of package variants. These are designed to
    18  address several different dimensions of scalability:
    19  - Number of different workloads for a given cluster
    20  - Number of clusters across which those workloads are deployed
    21  - Different types or characteristics of those clusters
    22  - Complexity of the organizations deploying those workloads
    23  - Changes to those workloads over time
    24  
    25  ## See Also
    26  - [Package Orchestration](07-package-orchestration.md)
    27  - [#3347](https://github.com/GoogleContainerTools/kpt/issues/3347) Bulk package
    28    creation
    29  - [#3243](https://github.com/GoogleContainerTools/kpt/issues/3243) Support bulk
    30    package upgrades
    31  - [#3488](https://github.com/GoogleContainerTools/kpt/issues/3488) Porch:
    32    BaseRevision controller aka Fan Out controller - but more
    33  - [Managing Package
    34     Revisions](https://docs.google.com/document/d/1EzUUDxLm5jlEG9d47AQOxA2W6HmSWVjL1zqyIFkqV1I/edit?usp=sharing)
    35  - [Porch UpstreamPolicy Resource
    36    API](https://docs.google.com/document/d/1OxNon_1ri4YOqNtEQivBgeRzIPuX9sOyu-nYukjwN1Q/edit?usp=sharing&resourcekey=0-2nDYYH5Kw58IwCatA4uDQw)
    37  
    38  ## Core Concepts
    39  
    40  For this solution, "workloads" are represented by packages. "Package" is a more
    41  general concept, being an arbitrary bundle of resources, and therefore is
    42  sufficient to solve the originally stated problem.
    43  
    44  The basic idea here is to introduce a PackageVariant resource that manages the
    45  derivation of a variant of a package from the original source package, and to
    46  manage the evolution of that variant over time. This effectively automates the
    47  human-centered process for variant creation one might use with `kpt`:
    48  1. Clone an upstream package locally
    49  1. Make changes to the local package, setting values in resources and
    50     executing KRM functions
    51  1. Push the package to a new repository and tag it as a new version
    52  
    53  Similarly, PackageVariant can manage the process of updating a package when a
    54  new version of the upstream package is published. In the human-centered
    55  workflow, a user would use `kpt pkg update` to pull in changes to their
    56  derivative package. When using a PackageVariant resource, the change would be
    57  made to the upstream specification in the resource, and the controller would
    58  propose a new Draft package reflecting the outcome of `kpt pkg update`.
    59  
    60  By automating this process, we open up the possibility of performing systematic
    61  changes that tie back to our different dimensions of scalability. We can use
    62  data about the specific variant we are creating to lookup additional context in
    63  the Porch cluster, and copy that information into the variant. That context is a
    64  well-structured resource, not simply key/value pairs. KRM functions within the
    65  package can interpret the resource, modifying other resources in the package
    66  accordingly.  The context can come from multiple sources that vary differently
    67  along those dimensions of scalability. For example, one piece of information may
    68  vary by region, another by individual site, another by cloud provider, and yet
    69  another based on whether we are deploying to development, staging, or production.
    70  By utilizing resources in the Porch cluster as our input model, we can represent
    71  this complexity in a manageable model that is reused across many packages,
    72  rather than scattered in package-specific templates or key/value pairs without
    73  any structure. KRM functions, also reused across packages but configured as
    74  needed for the specific package, are used to interpret the resources within the
    75  package. This decouples authoring of the packages, creation of the input model,
    76  and deploy-time use of that input model within the packages, allowing those
    77  activities to be performed by different teams or organizations.
    78  
    79  We refer to the mechanism described above as *configuration injection*. It
    80  enables dynamic, context-aware creation of variants. Another way to think about
    81  it is as a continuous reconciliation, much like other Kubernetes controllers. In
    82  this case, the inputs are a parent package `P` and a context `C` (which may be a
    83  collection of many independent resources), with the output being the derived
    84  package `D`. When a new version of `C` is created by updates to in-cluster
    85  resources, we get a new revision of `D`, customized based upon the updated
    86  context. Similarly, the user (or an automation) can monitor for new versions of
    87  `P`; when one arrives, the PackageVariant can be updated to point to that new
    88  version, resulting in a newly proposed Draft of `D`, updated to reflect the
    89  upstream changes. This will be explained in more detail below.
    90  
    91  This proposal also introduces a way to "fan-out", or create multiple
    92  PackageVariant resources declaratively based upon a list or selector, with the
    93  PackageVariantSet resource. This is combined with the injection mechanism to
    94  enable generation of large sets of variants that are specialized to a particular
    95  target repository, cluster, or other resource.
    96  
    97  ## Basic Package Cloning
    98  
    99  The PackageVariant resource controls the creation and lifecycle of a variant
   100  of a package. That is, it defines the original (upstream) package, the new
   101  (downstream) package, and the changes (mutations) that need to be made to
   102  transform the upstream into the downstream. It also allows the user to specify
   103  policies around adoption, deletion, and update of package revisions that are
   104  under the control of the package variant controller.
   105  
   106  The simple clone operation is shown in *Figure 1*.
   107  
   108  | ![Figure 1: Basic Package Cloning](packagevariant-clone.png) | ![Legend](packagevariant-legend.png) |
   109  | :---: | :---: |
   110  | *Figure 1: Basic Package Cloning* | *Legend* |
   111  
   112  
   113  Note that *proposal* and *approval* are not handled by the package variant
   114  controller. Those are left to humans or other controllers. The exception is the
   115  proposal of deletion (there is no concept of a "Draft" deletion), which the
   116  package variant control will do, depending upon the specified deletion policy.
   117  
   118  ### PackageRevision Metadata
   119  
   120  The package variant controller utilizes Porch APIs. This means that it is not
   121  just doing a `clone` operation, but in fact creating a Porch PackageRevision
   122  resource. In particular, that resource can contain Kubernetes metadata that is
   123  not part of the package as stored in the repository.
   124  
   125  Some of that metadata is necessary for the management of the PackageRevision
   126  by the package variant controller - for example, the owner reference indicating
   127  which PackageVariant created the PackageRevision. These are not under the user's
   128  control. However, the PackageVariant resource does make the annotations and
   129  labels of the PackageRevision available as values that may be controlled
   130  during the creation of the PackageRevision. This can assist in additional
   131  automation workflows.
   132  
   133  ## Introducing Variance
   134  Just cloning is not that interesting, so the PackageVariant resource also
   135  allows you to control various ways of mutating the original package to create
   136  the variant.
   137  
   138  ### Package Context[^porch17]
   139  Every kpt package that is fetched with `--for-deployment` will contain a
   140  ConfigMap called `kptfile.kpt.dev`. Analogously, when Porch creates a package
   141  in a deployment repository, it will create this ConfigMap, if it does not
   142  already exist. Kpt (or Porch) will automatically add a key `name` to the
   143  ConfigMap data, with the value of the package name. This ConfigMap can then
   144  be used as input to functions in the Kpt function pipeline.
   145  
   146  This process holds true for package revisions created via the package variant
   147  controller as well. Additionally, the author of the PackageVariant resource
   148  can specify additional key-value pairs to insert into the package
   149  context, as shown in *Figure 2*.
   150  
   151  | ![Figure 2: Package Context Mutation](packagevariant-context.png) |
   152  | :---: |
   153  | *Figure 2: Package Context Mutation * |
   154  
   155  While this is convenient, it can be easily abused, leading to
   156  over-parameterization. The preferred approach is configuration injection, as
   157  described below, since it allows inputs to adhere to a well-defined, reusable
   158  schema, rather than simple key/value pairs.
   159  
   160  ### Kptfile Function Pipeline Editing[^porch18]
   161  In the manual workflow, one of the ways we edit packages is by running KRM
   162  functions imperatively. PackageVariant offers a similar capability, by
   163  allowing the user to add functions to the beginning of the downstream package
   164  `Kptfile` mutators pipeline. These functions will then execute before the
   165  functions present in the upstream pipeline. It is not exactly the same as
   166  running functions imperatively, because they will also be run in every
   167  subsequent execution of the downstream package function pipeline. But it can
   168  achieve the same goals.
   169  
   170  For example, consider an upstream package that includes a Namespace resource.
   171  In many organizations, the deployer of the workload may not have the permissions
   172  to provision cluster-scoped resources like namespaces. This means that they
   173  would not be able to use this upstream package without removing the Namespace
   174  resource (assuming that they only have access to a pipeline that deploys with
   175  constrained permissions). By adding a function that removes Namespace resources,
   176  and a call to `set-namespace`, they can take advantage of the upstream package.
   177  
   178  Similarly, the Kptfile pipeline editing feature provides an easy mechanism for
   179  the deployer to create and set the namespace if their downstream package
   180  application pipeline allows it, as seen in *Figure 3*.[^setns]
   181  
   182  | ![Figure 3: KRM Function Pipeline Editing](packagevariant-function.png) |
   183  | :---: |
   184  | *Figure 3: Kptfile Function Pipeline Editing * |
   185  
   186  ### Configuration Injection[^porch18]
   187  
   188  Adding values to the package context or functions to the pipeline works
   189  for configuration that is under the control of the creator of the PackageVariant
   190  resource. However, in more advanced use cases, we may need to specialize the
   191  package based upon other contextual information. This particularly comes into
   192  play when the user deploying the workload does not have direct control over the
   193  context in which it is being deployed. For example, one part of the organization
   194  may manage the infrastructure - that is, the cluster in which we are deploying
   195  the workload - and another part the actual workload. We would like to be able to
   196  pull in inputs specified by the infrastructure team automatically, based the
   197  cluster to which we are deploying the workload, or perhaps the region in which
   198  that cluster is deployed.
   199  
   200  To facilitate this, the package variant controller can "inject" configuration
   201  directly into the package. This means it will use information specific to this
   202  instance of the package to lookup a resource in the Porch cluster, and copy that
   203  information into the package. Of course, the package has to be ready to receive
   204  this information. So, there is a protocol for facilitating this dance:
   205  - Packages may contain resources annotated with `kpt.dev/config-injection`
   206  - Often, these will also be `config.kubernetes.io/local-config` resources, as
   207    they are likely just used by local functions as input. But this is not
   208    mandatory.
   209  - The package variant controller will look for any resource in the Kubernetes
   210    cluster matching the Group, Version, and Kind of the package resource, and
   211    satisfying the *injection selector*.
   212  - The package variant controller will copy the `spec` field from the matching
   213    in-cluster resource to the in-package resource, or the `data` field in the
   214    case of a ConfigMap.
   215  
   216  | ![Figure 4: Configuration Injection](packagevariant-config-injection.png) |
   217  | :---: |
   218  | *Figure 4: Configuration Injection* |
   219  
   220  
   221  Note that because we are injecting data *from the Kubernetes cluster*, we can
   222  also monitor that data for changes. For each resource we inject, the package
   223  variant controller will establish a Kubernetes "watch" on the resource (or
   224  perhaps on the collection of such resources). A change to that resource will
   225  result in a new Draft package with the updated configuration injected.
   226  
   227  There are a number of additional details that will be described in the detailed
   228  design below, along with the specific API definition.
   229  
   230  ## Lifecycle Management
   231  
   232  ### Upstream Changes
   233  The package variant controller allows you to specific a specific upstream
   234  package revision to clone, or you can specify a floating tag[^notimplemented].
   235  
   236  If you specify a specific upstream revision, then the downstream will not be
   237  changed unless the PackageVariant resource itself is modified to point to a new
   238  revision. That is, the user must edit the PackageVariant, and change the
   239  upstream package reference. When that is done, the package variant controller
   240  will update any existing Draft package under its ownership by doing the
   241  equivalent of a `kpt pkg update` to update the downstream to be based upon
   242  the new upstream revision. If a Draft does not exist, then the package variant
   243  controller will create a new Draft based on the current published downstream,
   244  and apply the `kpt pkg update`. This updated Draft must then be proposed and
   245  approved like any other package change.
   246  
   247  If a floating tag is used, then explicit modification of the PackageVariant is
   248  not needed. Rather, when the floating tag is moved to a new tagged revision of
   249  the upstream package, the package revision controller will notice and
   250  automatically propose and update to that revision. For example, the upstream
   251  package author may designate three floating tags: stable, beta, and alpha. The
   252  upstream package author can move these tags to specific revisions, and any
   253  PackageVariant resource tracking them will propose updates to their downstream
   254  packages.
   255  
   256  ### Adoption and Deletion Policies
   257  When a PackageVariant resource is created, it will have a particular
   258  repository and package name as the downstream. The adoption policy controls
   259  whether the package variant controller takes over an existing package with that
   260  name, in that repository.
   261  
   262  Analogously, when a PackageVariant resource is deleted, a decision must be
   263  made about whether or not to delete the downstream package. This is controlled
   264  by the deletion policy.
   265  
   266  ## Fan Out of Variant Generation[^pvsimpl]
   267  
   268  When used with a single package, the package variant controller mostly helps us
   269  handle the time dimension - producing new versions of a package as the upstream
   270  changes, or as injected resources are updated. It can also be useful for
   271  automating common, systematic changes made when bringing an external package
   272  into an organization, or an organizational package into a team repository.
   273  
   274  That is useful, but not extremely compelling by itself. More interesting is when
   275  we use PackageVariant as a primitive for automations that act on other
   276  dimensions of scale. That means writing controllers that emit PackageVariant
   277  resources. For example, we can create a controller that instantiates a
   278  PackageVariant for each developer in our organization, or we can create
   279  a controller to manage PackageVariants across environments. The ability to not
   280  only clone a package, but make systematic changes to that package enables
   281  flexible automation.
   282  
   283  Workload controllers in Kubernetes are a useful analogy. In Kubernetes, we have
   284  different workload controllers such as Deployment, StatefulSet, and DaemonSet.
   285  Ultimately, all of these result in Pods; however, the decisions about what Pods
   286  to create, how to schedule them across Nodes, how to configure those Pods, and
   287  how to manage those Pods as changes happen are very different with each workload
   288  controller. Similarly, we can build different controllers to handle different
   289  ways in which we want to generate PackageRevisions. The PackageVariant
   290  resource provides a convenient primitive for all of those controllers, allowing
   291  a them to leverage a range of well-defined operations to mutate the packages as
   292  needed.
   293  
   294  A common need is the ability to generate many variants of a package based on
   295  a simple list of some entity. Some examples include generating package variants
   296  to spin up development environments for each developer in an organization;
   297  instantiating the same package, with slight configuration changes, across a
   298  fleet of clusters; or instantiating some package per customer.
   299  
   300  The package variant set controller is designed to fill this common need. This
   301  controller consumes PackageVariantSet resources, and outputs PackageVariant
   302  resources. The PackageVariantSet defines:
   303  - the upstream package
   304  - targeting criteria
   305  - a template for generating one PackageVariant per target
   306  
   307  Three types of targeting are supported:
   308  - An explicit list of repositories and package names
   309  - A label selector for Repository objects
   310  - An arbitrary object selector
   311  
   312  Rules for generating a PackageVariant are associated with a list of targets
   313  using a template. That template can have explicit values for various
   314  PackageVariant fields, or it can use [Common Expression Language
   315  (CEL)](https://github.com/google/cel-go) expressions to specify the field
   316  values.
   317  
   318  *Figure 5* shows an example of creating PackageVariant resources based upon the
   319  explicitly list of repositories. In this example, for the `cluster-01` and
   320  `cluster-02` repositories, no template is defined the resulting PackageVariants;
   321  it simply takes the defaults. However, for `cluster-03`, a template is defined
   322  to change the downstream package name to `bar`.
   323  
   324  | ![Figure 5: PackageVariantSet with Repository List](packagevariantset-target-list.png) |
   325  | :---: |
   326  | *Figure 5: PackageVariantSet with Repository List* |
   327  
   328  It is also possible to target the same package to a repository more than once,
   329  using different names. This is useful, for example, if the package is used to
   330  provision namespaces and you would like to provision many namespaces in the same
   331  cluster. It is also useful if a repository is shared across multiple clusters.
   332  In *Figure 6*, two PackageVariant resources for creating the `foo` package in
   333  the repository `cluster-01` are generated, one for each listed package name.
   334  Since no `packageNames` field is listed for `cluster-02`, only one instance is
   335  created for that repository.
   336  
   337  | ![Figure 6: PackageVariantSet with Package List](packagevariantset-target-list-with-packages.png) |
   338  | :---: |
   339  | *Figure 6: PackageVariantSet with Package List* |
   340  
   341  *Figure 7* shows an example that combines a repository label selector with
   342  configuration injection that various based upon the target. The template for the
   343  PackageVariant includes a CEL expression for the one of the injectors, so that
   344  the injection varies systematically based upon attributes of the target.
   345  
   346  | ![Figure 7: PackageVariantSet with Repository Selector](packagevariantset-target-repo-selector.png) |
   347  | :---: |
   348  | *Figure 7: PackageVariantSet with Repository Selector* |
   349  
   350  ## Detailed Design
   351  
   352  ### PackageVariant API
   353  
   354  The Go types below defines the `PackageVariantSpec`.
   355  
   356  ```go
   357  type PackageVariantSpec struct {
   358          Upstream   *Upstream   `json:"upstream,omitempty"`
   359          Downstream *Downstream `json:"downstream,omitempty"`
   360  
   361          AdoptionPolicy AdoptionPolicy `json:"adoptionPolicy,omitempty"`
   362          DeletionPolicy DeletionPolicy `json:"deletionPolicy,omitempty"`
   363  
   364          Labels      map[string]string `json:"labels,omitempty"`
   365          Annotations map[string]string `json:"annotations,omitempty"`
   366  
   367          PackageContext *PackageContext     `json:"packageContext,omitempty"`
   368          Pipeline       *kptfilev1.Pipeline `json:"pipeline,omitempty"`
   369          Injectors      []InjectionSelector `json:"injectors,omitempty"`
   370  }
   371  
   372  type Upstream struct {
   373          Repo     string `json:"repo,omitempty"`
   374          Package  string `json:"package,omitempty"`
   375          Revision string `json:"revision,omitempty"`
   376  }
   377  
   378  type Downstream struct {
   379          Repo    string `json:"repo,omitempty"`
   380          Package string `json:"package,omitempty"`
   381  }
   382  
   383  type PackageContext struct {
   384          Data       map[string]string `json:"data,omitempty"`
   385          RemoveKeys []string          `json:"removeKeys,omitempty"`
   386  }
   387  
   388  type InjectionSelector struct {
   389          Group   *string `json:"group,omitempty"`
   390          Version *string `json:"version,omitempty"`
   391          Kind    *string `json:"kind,omitempty"`
   392          Name    string  `json:"name"`
   393  }
   394  
   395  ```
   396  
   397  #### Basic Spec Fields
   398  
   399  The `Upstream` and `Downstream` fields specify the source package and
   400  destination repository and package name. The `Repo` fields refer to the names
   401  Porch Repository resources in the same namespace as the PackageVariant resource.
   402  The `Downstream` does not contain a revision, because the package variant
   403  controller will only create Draft packages. The `Revision` of the eventual
   404  PackageRevision resource will be determined by Porch at the time of approval.
   405  
   406  The `Labels` and `Annotations` fields list metadata to include on the created
   407  PackageRevision. These values are set *only* at the time a Draft package is
   408  created. They are ignored for subsequent operations, even if the PackageVariant
   409  itself has been modified. This means users are free to change these values on
   410  the PackageRevision; the package variant controller will not touch them again.
   411  
   412  `AdoptionPolicy` controls how the package variant controller behaves if it finds
   413  an existing PackageRevision Draft matching the `Downstream`. If the
   414  `AdoptionPolicy` is `adoptExisting`, then the package variant controller will
   415  take ownership of the Draft, associating it with this PackageVariant. This means
   416  that it will begin to reconcile the Draft, just as if it had created it in the
   417  first place. An `AdoptionPolicy` of `adoptNone` (the default) will simply ignore
   418  any matching Drafts that were not created by the controller.
   419  
   420  `DeletionPolicy` controls how the package variant controller behaves with
   421  respect to PackageRevisions that it has created when the PackageVariant resource
   422  itself is deleted. A value of `delete` (the default) will delete the
   423  PackageRevision (potentially removing it from a running cluster, if the
   424  downstream package has been deployed). A value of `orphan` will remove the owner
   425  references and leave the PackageRevisions in place.
   426  
   427  #### Package Context Injection
   428  
   429  PackageVariant resource authors may specify key-value pairs in the
   430  `spec.packageContext.data` field of the resource. These key-value pairs will be
   431  automatically added to the `data` of the `kptfile.kpt.dev` ConfigMap, if it
   432  exists.
   433  
   434  Specifying the key `name` is invalid and must fail validation of the
   435  PackageVariant. This key is reserved for kpt or Porch to set to the package
   436  name. Similarly, `package-path` is reserved and will result in an error.
   437  
   438  The `spec.packageContext.removeKeys` field can also be used to specify a list of
   439  keys that the package variant controller should remove from the `data` field of
   440  the `kptfile.kpt.dev` ConfigMap.
   441  
   442  When creating or updating a package, the package variant controller will ensure
   443  that:
   444  - The `kptfile.kpt.dev` ConfigMap exists, failing if not
   445  - All of the key-value pairs in `spec.packageContext.data` exist in the `data`
   446    field of the ConfigMap.
   447  - None of the keys listed in `spec.packageContext.removeKeys` exist in the
   448    ConfigMap.
   449  
   450  Note that if a user adds a key via PackageVariant, then changes the
   451  PackageVariant to no longer add that key, it will NOT be removed automatically,
   452  unless the user also lists the key in the `removeKeys` list. This avoids the
   453  need to track which keys were added by PackageVariant.
   454  
   455  Similarly, if a user manually adds a key in the downstream that is also listed
   456  in the `removeKeys` field, the package variant controller will remove that key
   457  the next time it needs to update the downstream package. There will be no
   458  attempt to coordinate "ownership" of these keys.
   459  
   460  If the controller is unable to modify the ConfigMap for some reason, this is
   461  considered an error and should prevent generation of the Draft. This will result
   462  in the condition `Ready` being set to `False`.
   463  
   464  #### Kptfile Function Pipeline Editing
   465  
   466  PackageVariant resource creators may specify a list of KRM functions to add to
   467  the beginning of the Kptfile's pipeline. These functions are listed in the field
   468  `spec.pipeline`, which is a
   469  [Pipeline](https://github.com/GoogleContainerTools/kpt/blob/cf1f326486214f6b4469d8432287a2fa705b48f5/pkg/api/kptfile/v1/types.go#L236),
   470  just as in the Kptfile. The user can therefore prepend both `validators` and
   471  `mutators`.
   472  
   473  Functions added in this way are always added to the *beginning* of the Kptfile
   474  pipeline. In order to enable management of the list on subsequent
   475  reconciliations, functions added by the package variant controller will use the
   476  `Name` field of the
   477  [Function](https://github.com/GoogleContainerTools/kpt/blob/cf1f326486214f6b4469d8432287a2fa705b48f5/pkg/api/kptfile/v1/types.go#L283).
   478  In the Kptfile, each function will be named as the dot-delimited concatenation
   479  of `PackageVariant`, the name of the PackageVariant resource, the function name
   480  as specified in the pipeline of the PackageVariant resource (if present), and
   481  the positional location of the function in the array.
   482  
   483  For example, if the PackageVariant resource contains:
   484  
   485  ```yaml
   486  apiVersion: config.porch.kpt.dev/v1alpha1
   487  kind: PackageVariant
   488  metadata:
   489    namespace: default
   490    name: my-pv
   491  spec:
   492    ...
   493    pipeline:
   494      mutators:
   495      - image: gcr.io/kpt-fn/set-namespace:v0.1
   496        configMap:
   497          namespace: my-ns
   498        name: my-func
   499      - image: gcr.io/kpt-fn/set-labels:v0.1
   500        configMap:
   501          app: foo
   502  ```
   503  
   504  Then the resulting Kptfile will have these two entries prepended to its
   505  `mutators` list:
   506  
   507  ```yaml
   508    pipeline:
   509      mutators:
   510      - image: gcr.io/kpt-fn/set-namespace:v0.1
   511        configMap:
   512          namespace: my-ns
   513        name: PackageVariant.my-pv.my-func.0
   514      - image: gcr.io/kpt-fn/set-labels:v0.1
   515        configMap:
   516          app: foo
   517        name: PackageVariant.my-pv..1
   518  ```
   519  
   520  During subsequent reconciliations, this allows the controller to identify the
   521  functions within its control, remove them all, and re-add them based on its
   522  updated content. By including the PackageVariant name, we enable chains of
   523  PackageVariants to add functions, so long as the user is careful about their
   524  choice of resource names and avoids conflicts.
   525  
   526  If the controller is unable to modify the Pipeline for some reason, this is
   527  considered an error and should prevent generation of the Draft. This will result
   528  in the condition `Ready` being set to `False`.
   529  
   530  #### Configuration Injection Details
   531  
   532  As described [above](#configuration-injection), configuration injection is a
   533  process whereby in-package resources are matched to in-cluster resources, and
   534  the `spec` of the in-cluster resources is copied to the in-package resource.
   535  
   536  Configuration injection is controlled by a combination of in-package resources
   537  with annotations, and *injectors* (also known as *injection selectors*) defined
   538  on the PackageVariant resource. Package authors control the injection points
   539  they allow in their packages, by flagging specific resources as *injection
   540  points* with an annotation. Creators of the PackageVariant resource specify how
   541  to map in-cluster resources to those injection points using the injection
   542  selectors. Injection selectors are defined in the `spec.injectors` field of the
   543  PackageVariant. This field is an ordered array of structs containing a GVK
   544  (group, version, kind) tuple as separate fields, and name. Only the name is
   545  required. To identify a match, all fields present must match the in-cluster
   546  object, and all *GVK* fields present must match the in-package resource. In
   547  general the name will not match the in-package resource; this is discussed in
   548  more detail below.
   549  
   550  The annotations, along with the GVK of the annotated resource, allow a package
   551  to "advertise" the injections it can accept and understand. These injection
   552  points effectively form a configuration API for the package, and the injection
   553  selectors provide a way for the PackageVariant author to specify the inputs
   554  for those APIs from the possible values in the management cluster. If we define
   555  those APIs carefully, they can be used across many packages; since they are
   556  KRM resources, we can apply versioning and schema validation to them as well.
   557  This creates a more maintainable, automatable set of APIs for package
   558  customization than simple key/value pairs.
   559  
   560  As an example, we may define a GVK that contains service endpoints that many
   561  applications use. In each application package, we would then include an instance
   562  of that resource, say called "service-endpoints", and configure a function to
   563  propagate the values from that resource to others within our package. As those
   564  endpoints may vary by region, in our Porch cluster we can create an instance of
   565  this GVK for each region: "useast1-service-endpoints",
   566  "useast2-service-endpoints", "uswest1-service-endpoints", etc. When we
   567  instantiate the PackageVariant for a cluster, we want to inject the resource
   568  corresponding to the region in which the cluster exists. Thus, for each cluster
   569  we will create a PackageVariant resource pointing to the upstream package, but
   570  with injection selector name values that are specific to the region for that
   571  cluster.
   572  
   573  It is important to realize that the name of the in-package resource and the in-
   574  cluster resource need not match. In fact, it would be an unusual coincidence if
   575  they did match. The names in the package are the same across PackageVariants
   576  using that upstream, but we want to inject different resources for each one such
   577  PackageVariant. We also do not want to change the name in the package, because
   578  it likely has meaning within the package and will be used by functions in the
   579  package. Also, different owners control the names of the in-package and in-
   580  cluster resources. The names in the package are in the control of the package
   581  author. The names in the cluster are in the control of whoever populates the
   582  cluster (for example, some infrastructure team). The selector is the glue
   583  between them, and is in control of the PackageVariant resource creator.
   584  
   585  The GVK on the other hand, has to be the same for the in-package resource and
   586  the in-cluster resource, because it tells us the API schema for the resource.
   587  Also, the namespace of the in-cluster object needs to be the same as the
   588  PackageVariant resource, or we could leak resources from namespaces to which
   589  our PackageVariant user does not have access.
   590  
   591  With that understanding, the injection process works as follows:
   592  
   593  1. The controller will examine all in-package resources, looking for those with
   594     an annotation named `kpt.dev/config-injection`, with one of the following
   595     values: `required` or `optional`. We will call these "injection points". It
   596     is the responsibility of the package author to define these injection points,
   597     and to specify which are required and which are optional. Optional injection
   598     points are a way of specifying default values.
   599  1. For each injection point, a condition will be created *in the
   600     downstream PackageRevision*, with ConditionType set to the dot-delimited
   601     concatenation of `config.injection`, with the in-package resource kind and
   602     name, and the value set to `False`. Note that since the package author
   603     controls the name of the resource, kind and name are sufficient to
   604     disambiguate the injection point. We will call this ConditionType the
   605     "injection point ConditionType".
   606  1. For each required injection point, the injection point ConditionType will
   607     be added to the PackageRevision `readinessGates` by the package variant
   608     controller. Optional injection points' ConditionTypes must not be added to
   609     the `readinessGates` by the package variant controller, but humans or other
   610     actors may do so at a later date, and the package variant controller should
   611     not remove them on subsequent reconciliations. Also, this relies upon
   612     `readinessGates` gating publishing the package to a *deployment* repository,
   613     but not gating publishing to a blueprint repository.
   614  1. The injection processing will proceed as follows. For each injection point:
   615     - The controller will identify all in-cluster objects in the same
   616       namespace as the PackageVariant resource, with GVK matching the injection
   617       point (the in-package resource). If the controller is unable to load this
   618       objects (e.g., there are none and the CRD is not installed), the injection
   619       point ConditionType will be set to `False`, with a message indicating that
   620       the error, and processing proceeds to the next injection point. Note that
   621       for `optional` injection this may be an acceptable outcome, so it does not
   622       interfere with overall generation of the Draft.
   623     - The controller will look through the list of injection selectors in
   624       order and checking if any of the in-cluster objects match the selector. If
   625       so, that in-cluster object is selected, and processing of the list of
   626       injection selectors stops. Note that the namespace is set based upon the
   627       PackageVariant resource, the GVK is set based upon the in-package resource,
   628       and all selectors require name. Thus, at most one match is possible for any
   629       given selector. Also note that *all fields present in the selector* must
   630       match the in-cluster resource, and only the *GVK fields present in the
   631       selector* must match the in-package resource.
   632     - If no in-cluster object is selected, the injection point ConditionType will
   633       be set to `False` with a message that no matching in-cluster resource was
   634       found, and processing proceeds to the next injection point.
   635     - If a matching in-cluster object is selected, then it is injected as
   636       follows:
   637       - For ConfigMap resources, the `data` field from the in-cluster resource is
   638         copied to the `data` field of the in-package resource (the injection
   639         point), overwriting it.
   640       - For other resource types, the `spec` field from the in-cluster resource
   641         is copied to the `spec` field of the in-package resource (the injection
   642         point), overwriting it.
   643       - An annotation with name `kpt.dev/injected-resource-name` and value set to
   644         the name of the in-cluster resource is added (or overwritten) in the
   645         in-package resource.
   646  
   647  If the the overall injection cannot be completed for some reason, or if any of
   648  the below problems exist in the upstream package, it is considered an error and
   649  should prevent generation of the Draft:
   650     - There is a resource annotated as an injection point but having an invalid
   651       annotation value (i.e., other than `required` or `optional`).
   652     - There are ambiguous condition types due to conflicting GVK and name
   653       values. These must be disambiguated in the upstream package, if so.
   654  
   655  This will result in the condition `Ready` being set to `False`.
   656  
   657  Note that whether or not all `required` injection points are fulfilled does not
   658  affect the *PackageVariant* conditions, only the *PackageRevision* conditions.
   659  
   660  **A Further Note on Selectors**
   661  
   662  Note that by allowing the use of GVK, not just name, in the selector, more
   663  precision in selection is enabled. This is a way to constrain the injections
   664  that will be done. That is, if the package has 10 different objects with
   665  `config-injection` annotation, the PackageVariant could say it only wants to
   666  replace certain GVKs, allowing better control.
   667  
   668  Consider, for example, if the cluster contains these resources:
   669  
   670  - GVK1 foo
   671  - GVK1 bar
   672  - GVK2 foo
   673  - GVK2 bar
   674  
   675  If we could only define injection selectors based upon name, it would be
   676  impossible to ever inject one GVK with `foo` and another with `bar`. Instead,
   677  by using GVK, we can accomplish this with a list of selectors like:
   678  
   679   - GVK1 foo
   680   - GVK2 bar
   681  
   682  That said, often name will be sufficiently unique when combined with the
   683  in-package resource GVK, and so making the selector GVK optional is more
   684  convenient. This allows a single injector to apply to multiple injection points
   685  with different GVKs.
   686  
   687  #### Order of Mutations
   688  
   689  During creation, the first thing the controller does is clone the upstream
   690  package to create the downstream package.
   691  
   692  For update, first note that changes to the downstream PackageRevision can be
   693  triggered for several different reasons:
   694  
   695  1. The PackageVariant resource is updated, which could change any of the options
   696     for introducing variance, or could also change the upstream package revision
   697     referenced.
   698  1. A new revision of the upstream package has been selected due to a floating
   699     tag change, or due to a force retagging of the upstream.
   700  1. An injected in-cluster object is updated.
   701  
   702  The downstream PackageRevision may have been updated by humans or other
   703  automation actors since creation, so we cannot simply recreate the downstream
   704  PackageRevision from scratch when one of these changes happens. Instead, the
   705  controller must maintain the later edits by doing the equivalent of a `kpt pkg
   706  update`, in the case of changes to the upstream for any reason. Any other
   707  changes require reapplication of the PackageVariant functionality. With that
   708  understanding, we can see that the controller will perform mutations on the
   709  downstream package in this order, for both creation and update:
   710  
   711  1. Create (via Clone) or Update (via `kpt pkg update` equivalent)
   712     - This is done by the Porch server, not by the package variant controller
   713       directly.
   714     - This means that Porch will run the Kptfile pipeline after clone or
   715       update.
   716  1. Package variant controller applies configured mutations
   717     - Package Context Injections
   718     - Kptfile KRM Function Pipeline Additions/Changes
   719     - Config Injection
   720  1. Package variant controller saves the PackageRevision and
   721     PackageRevisionResources.
   722     - Porch server executes the Kptfile pipeline
   723  
   724  The package variant controller mutations edit resources (including the Kptfile),
   725  based on the contents of the PackageVariant and the injected in-cluster
   726  resources, but cannot affect one another. The results of those mutations
   727  throughout the rest of the package is materialized by the execution of the
   728  Kptfile pipeline during the save operation.
   729  
   730  #### PackageVariant Status
   731  
   732  PackageVariant sets the following status conditions:
   733   - `Stalled` is set to True if there has been a failure that most likely
   734     requires user intervention.
   735   - `Ready` is set to True if the last reconciliation successfully produced an
   736     up-to-date Draft.
   737  
   738  The PackageVariant resource will also contain a `DownstreamTargets` field,
   739  containing a list of downstream `Draft` and `Proposed` PackageRevisions owned by
   740  this PackageVariant resource, or the latest `Published` PackageRevision if there
   741  are none in `Draft` or `Proposed` state. Typically, there is only a single
   742  Draft, but use of the `adopt` value for `AdoptionPolicy` could result in
   743  multiple Drafts being owned by the same PackageVariant.
   744  
   745  ### PackageVariantSet API[^pvsimpl]
   746  
   747  The Go types below defines the `PackageVariantSetSpec`.
   748  
   749  ```go
   750  // PackageVariantSetSpec defines the desired state of PackageVariantSet
   751  type PackageVariantSetSpec struct {
   752          Upstream *pkgvarapi.Upstream `json:"upstream,omitempty"`
   753          Targets  []Target            `json:"targets,omitempty"`
   754  }
   755  
   756  type Target struct {
   757          // Exactly one of Repositories, RepositorySeletor, and ObjectSelector must be
   758          // populated
   759          // option 1: an explicit repositories and package names
   760          Repositories []RepositoryTarget `json:"repositories,omitempty"`
   761  
   762          // option 2: a label selector against a set of repositories
   763          RepositorySelector *metav1.LabelSelector `json:"repositorySelector,omitempty"`
   764  
   765          // option 3: a selector against a set of arbitrary objects
   766          ObjectSelector *ObjectSelector `json:"objectSelector,omitempty"`
   767  
   768          // Template specifies how to generate a PackageVariant from a target
   769          Template *PackageVariantTemplate `json:"template,omitempty"`
   770  }
   771  ```
   772  
   773  At the highest level, a PackageVariantSet is just an upstream, and a list of
   774  targets. For each target, there is a set of criteria for generating a list, and
   775  a set of rules (a template) for creating a PackageVariant from each list entry.
   776  
   777  Since `template` is optional, lets start with describing the different types of
   778  targets, and how the criteria in each is used to generate a list that seeds the
   779  PackageVariant resources.
   780  
   781  The `Target` structure must include exactly one of three different ways of
   782  generating the list. The first is a simple list of repositories and package
   783  names for each of those repositories[^repo-pkg-expr]. The package name list is
   784  needed for uses cases in which you want to repeatedly instantiate the same
   785  package in a single repository. For example, if a repository represents the
   786  contents of a cluster, you may want to instantiate a namespace package once for
   787  each namespace, with a name matching the namespace.
   788  
   789  This example shows using the `repositories` field:
   790  
   791  ```yaml
   792  apiVersion: config.porch.kpt.dev/v1alpha2
   793  kind: PackageVariantSet
   794  metadata:
   795    namespace: default
   796    name: example
   797  spec:
   798    upstream:
   799      repo: example-repo
   800      package: foo
   801      revision: v1
   802    targets:
   803    - repositories:
   804      - name: cluster-01
   805      - name: cluster-02
   806      - name: cluster-03
   807        packageNames:
   808        - foo-a
   809        - foo-b
   810        - foo-c
   811      - name: cluster-04
   812        packageNames:
   813        - foo-a
   814        - foo-b
   815  ```
   816  
   817  In this case, PackageVariant resources are created for each of these pairs of
   818  downstream repositories and packages names:
   819  
   820  | Repository | Package Name |
   821  | ---------- | ------------ |
   822  | cluster-01 | foo          |
   823  | cluster-02 | foo          |
   824  | cluster-03 | foo-a        |
   825  | cluster-03 | foo-b        |
   826  | cluster-03 | foo-c        |
   827  | cluster-04 | foo-a        |
   828  | cluster-04 | foo-b        |
   829  
   830  All of those PackageVariants have the same upstream.
   831  
   832  The second criteria targeting is via a label selector against Porch Repository
   833  objects, along with a list of package names. Those packages will be instantiated
   834  in each matching repository. Just like in the first example, not listing a
   835  package name defaults to one package, with the same name as the upstream
   836  package. Suppose, for example, we have these four repositories defined in our
   837  Porch cluster:
   838  
   839  | Repository | Labels                                |
   840  | ---------- | ------------------------------------- |
   841  | cluster-01 | region=useast1, env=prod, org=hr      |
   842  | cluster-02 | region=uswest1, env=prod, org=finance |
   843  | cluster-03 | region=useast2, env=prod, org=hr      |
   844  | cluster-04 | region=uswest1, env=prod, org=hr      |
   845  
   846  If we create a PackageVariantSet with the following `spec`:
   847  
   848  ```yaml
   849  spec:
   850    upstream:
   851      repo: example-repo
   852      package: foo
   853      revision: v1
   854    targets:
   855    - repositorySelector:
   856        matchLabels:
   857          env: prod
   858          org: hr
   859    - repositorySelector:
   860        matchLabels:
   861          region: uswest1
   862        packageNames:
   863        - foo-a
   864        - foo-b
   865        - foo-c
   866  ```
   867  
   868  then PackageVariant resources will be created with these repository and package
   869  names:
   870  
   871  | Repository | Package Name |
   872  | ---------- | ------------ |
   873  | cluster-01 | foo          |
   874  | cluster-03 | foo          |
   875  | cluster-04 | foo          |
   876  | cluster-02 | foo-a        |
   877  | cluster-02 | foo-b        |
   878  | cluster-02 | foo-c        |
   879  | cluster-04 | foo-a        |
   880  | cluster-04 | foo-b        |
   881  | cluster-04 | foo-c        |
   882  
   883  Finally, the third possibility allows the use of *arbitrary* resources in the
   884  Porch cluster as targeting criteria. The `objectSelector` looks like this:
   885  
   886  ```yaml
   887  spec:
   888    upstream:
   889      repo: example-repo
   890      package: foo
   891      revision: v1
   892    targets:
   893    - objectSelector:
   894        apiVersion: krm-platform.bigco.com/v1
   895        kind: Team
   896        matchLabels:
   897          org: hr
   898          role: dev
   899  ```
   900  
   901  It works exactly like the repository selector - in fact the repository selector
   902  is equivalent to the object selector with the `apiVersion` and `kind` values set
   903  to point to Porch Repository resources. That is, the repository name comes from
   904  the object name, and the package names come from the listed package names. In
   905  the description of the template, we will see how to derive different repository
   906  names from the objects.
   907  
   908  #### PackageVariant Template
   909  
   910  As previously discussed, the list entries generated by the target criteria
   911  result in PackageVariant entries. If no template is specified, then
   912  PackageVariant default are used, along with the downstream repository name and
   913  package name as described in the previous section. The template allows the user
   914  to have control over all of the values in the resulting PackageVariant. The
   915  template API is shown below.
   916  
   917  ```go
   918  type PackageVariantTemplate struct {
   919  	// Downstream allows overriding the default downstream package and repository name
   920  	// +optional
   921  	Downstream *DownstreamTemplate `json:"downstream,omitempty"`
   922  
   923  	// AdoptionPolicy allows overriding the PackageVariant adoption policy
   924  	// +optional
   925  	AdoptionPolicy *pkgvarapi.AdoptionPolicy `json:"adoptionPolicy,omitempty"`
   926  
   927  	// DeletionPolicy allows overriding the PackageVariant deletion policy
   928  	// +optional
   929  	DeletionPolicy *pkgvarapi.DeletionPolicy `json:"deletionPolicy,omitempty"`
   930  
   931  	// Labels allows specifying the spec.Labels field of the generated PackageVariant
   932  	// +optional
   933  	Labels map[string]string `json:"labels,omitempty"`
   934  
   935  	// LabelsExprs allows specifying the spec.Labels field of the generated PackageVariant
   936  	// using CEL to dynamically create the keys and values. Entries in this field take precedent over
   937  	// those with the same keys that are present in Labels.
   938  	// +optional
   939  	LabelExprs []MapExpr `json:"labelExprs,omitempty"`
   940  
   941  	// Annotations allows specifying the spec.Annotations field of the generated PackageVariant
   942  	// +optional
   943  	Annotations map[string]string `json:"annotations,omitempty"`
   944  
   945  	// AnnotationsExprs allows specifying the spec.Annotations field of the generated PackageVariant
   946  	// using CEL to dynamically create the keys and values. Entries in this field take precedent over
   947  	// those with the same keys that are present in Annotations.
   948  	// +optional
   949  	AnnotationExprs []MapExpr `json:"annotationExprs,omitempty"`
   950  
   951  	// PackageContext allows specifying the spec.PackageContext field of the generated PackageVariant
   952  	// +optional
   953  	PackageContext *PackageContextTemplate `json:"packageContext,omitempty"`
   954  
   955  	// Pipeline allows specifying the spec.Pipeline field of the generated PackageVariant
   956  	// +optional
   957  	Pipeline *PipelineTemplate `json:"pipeline,omitempty"`
   958  
   959  	// Injectors allows specifying the spec.Injectors field of the generated PackageVariant
   960  	// +optional
   961  	Injectors []InjectionSelectorTemplate `json:"injectors,omitempty"`
   962  }
   963  
   964  // DownstreamTemplate is used to calculate the downstream field of the resulting
   965  // package variants. Only one of Repo and RepoExpr may be specified;
   966  // similarly only one of Package and PackageExpr may be specified.
   967  type DownstreamTemplate struct {
   968  	Repo        *string `json:"repo,omitempty"`
   969  	Package     *string `json:"package,omitempty"`
   970  	RepoExpr    *string `json:"repoExpr,omitempty"`
   971  	PackageExpr *string `json:"packageExpr,omitempty"`
   972  }
   973  
   974  // PackageContextTemplate is used to calculate the packageContext field of the
   975  // resulting package variants. The plain fields and Exprs fields will be
   976  // merged, with the Exprs fields taking precedence.
   977  type PackageContextTemplate struct {
   978  	Data           map[string]string `json:"data,omitempty"`
   979  	RemoveKeys     []string          `json:"removeKeys,omitempty"`
   980  	DataExprs      []MapExpr         `json:"dataExprs,omitempty"`
   981  	RemoveKeyExprs []string          `json:"removeKeyExprs,omitempty"`
   982  }
   983  
   984  // InjectionSelectorTemplate is used to calculate the injectors field of the
   985  // resulting package variants. Exactly one of the Name and NameExpr fields must
   986  // be specified. The other fields are optional.
   987  type InjectionSelectorTemplate struct {
   988  	Group   *string `json:"group,omitempty"`
   989  	Version *string `json:"version,omitempty"`
   990  	Kind    *string `json:"kind,omitempty"`
   991  	Name    *string `json:"name,omitempty"`
   992  
   993  	NameExpr *string `json:"nameExpr,omitempty"`
   994  }
   995  
   996  // MapExpr is used for various fields to calculate map entries. Only one of
   997  // Key and KeyExpr may be specified; similarly only on of Value and ValueExpr
   998  // may be specified.
   999  type MapExpr struct {
  1000  	Key       *string `json:"key,omitempty"`
  1001  	Value     *string `json:"value,omitempty"`
  1002  	KeyExpr   *string `json:"keyExpr,omitempty"`
  1003  	ValueExpr *string `json:"valueExpr,omitempty"`
  1004  }
  1005  
  1006  // PipelineTemplate is used to calculate the pipeline field of the resulting
  1007  // package variants.
  1008  type PipelineTemplate struct {
  1009  	// Validators is used to caculate the pipeline.validators field of the
  1010  	// resulting package variants.
  1011  	// +optional
  1012  	Validators []FunctionTemplate `json:"validators,omitempty"`
  1013  
  1014  	// Mutators is used to caculate the pipeline.mutators field of the
  1015  	// resulting package variants.
  1016  	// +optional
  1017  	Mutators []FunctionTemplate `json:"mutators,omitempty"`
  1018  }
  1019  
  1020  // FunctionTemplate is used in generating KRM function pipeline entries; that
  1021  // is, it is used to generate Kptfile Function objects.
  1022  type FunctionTemplate struct {
  1023  	kptfilev1.Function `json:",inline"`
  1024  
  1025  	// ConfigMapExprs allows use of CEL to dynamically create the keys and values in the
  1026  	// function config ConfigMap. Entries in this field take precedent over those with
  1027  	// the same keys that are present in ConfigMap.
  1028  	// +optional
  1029  	ConfigMapExprs []MapExpr `json:"configMapExprs,omitempty"`
  1030  }
  1031  ```
  1032  
  1033  This is a pretty complicated structure. To make it more understandable, the
  1034  first thing to notice is that many fields have a plain version, and an `Expr`
  1035  version. The plain version is used when the value is static across all the
  1036  PackageVariants; the `Expr` version is used when the value needs to vary across
  1037  PackageVariants.
  1038  
  1039  Let's consider a simple example. Suppose we have a package for provisioning
  1040  namespaces called "base-ns". We want to instantiate this several times in the
  1041  `cluster-01` repository. We could do this with this PackageVariantSet:
  1042  
  1043  ```yaml
  1044  apiVersion: config.porch.kpt.dev/v1alpha2
  1045  kind: PackageVariantSet
  1046  metadata:
  1047    namespace: default
  1048    name: example
  1049  spec:
  1050    upstream:
  1051      repo: platform-catalog
  1052      package: base-ns
  1053      revision: v1
  1054    targets:
  1055    - repositories:
  1056      - name: cluster-01
  1057        packageNames:
  1058        - ns-1
  1059        - ns-2
  1060        - ns-3
  1061  ```
  1062  
  1063  That will produce three PackageVariant resources with the same upstream, all
  1064  with the same downstream repo, and each with a different downstream package
  1065  name. If we also want to set some labels identically across the packages, we can
  1066  do that with the `template.labels` field:
  1067  
  1068  ```yaml
  1069  apiVersion: config.porch.kpt.dev/v1alpha2
  1070  kind: PackageVariantSet
  1071  metadata:
  1072    namespace: default
  1073    name: example
  1074  spec:
  1075    upstream:
  1076      repo: platform-catalog
  1077      package: base-ns
  1078      revision: v1
  1079    targets:
  1080    - repositories:
  1081      - name: cluster-01
  1082        packageNames:
  1083        - ns-1
  1084        - ns-2
  1085        - ns-3
  1086      template:
  1087        labels:
  1088          package-type: namespace
  1089          org: hr
  1090  ```
  1091  
  1092  The resulting PackageVariant resources will include `labels` in their `spec`,
  1093  and will be identical other than their names and the `downstream.package`:
  1094  
  1095  ```yaml
  1096  apiVersion: config.porch.kpt.dev/v1alpha1
  1097  kind: PackageVariant
  1098  metadata:
  1099    namespace: default
  1100    name: example-aaaa
  1101  spec:
  1102    upstream:
  1103      repo: platform-catalog
  1104      package: base-ns
  1105      revision: v1
  1106    downstream:
  1107      repo: cluster-01
  1108      package: ns-1
  1109    labels:
  1110      package-type: namespace
  1111      org: hr
  1112  ---
  1113  apiVersion: config.porch.kpt.dev/v1alpha1
  1114  kind: PackageVariant
  1115  metadata:
  1116    namespace: default
  1117    name: example-aaab
  1118  spec:
  1119    upstream:
  1120      repo: platform-catalog
  1121      package: base-ns
  1122      revision: v1
  1123    downstream:
  1124      repo: cluster-01
  1125      package: ns-2
  1126    labels:
  1127      package-type: namespace
  1128      org: hr
  1129  ---
  1130  
  1131  apiVersion: config.porch.kpt.dev/v1alpha1
  1132  kind: PackageVariant
  1133  metadata:
  1134    namespace: default
  1135    name: example-aaac
  1136  spec:
  1137    upstream:
  1138      repo: platform-catalog
  1139      package: base-ns
  1140      revision: v1
  1141    downstream:
  1142      repo: cluster-01
  1143      package: ns-3
  1144    labels:
  1145      package-type: namespace
  1146      org: hr
  1147  ```
  1148  
  1149  When using other targeting means, the use of the `Expr` fields becomes more
  1150  likely, because we have more possible sources for different field values. The
  1151  `Expr` values are all [Common Expression Language (CEL)](https://github.com/google/cel-go)
  1152  expressions, rather than static values. This allows the user to construct values
  1153  based upon various fields of the targets. Consider again the
  1154  `repositorySelector` example, where we have these repositories in the cluster.
  1155  
  1156  | Repository | Labels                                |
  1157  | ---------- | ------------------------------------- |
  1158  | cluster-01 | region=useast1, env=prod, org=hr      |
  1159  | cluster-02 | region=uswest1, env=prod, org=finance |
  1160  | cluster-03 | region=useast2, env=prod, org=hr      |
  1161  | cluster-04 | region=uswest1, env=prod, org=hr      |
  1162  
  1163  If we create a PackageVariantSet with the following `spec`, we can use the
  1164  `Expr` fields to add labels to the PackageVariantSpecs (and thus to the
  1165  resulting PackageRevisions later) that vary based on cluster. We can also use
  1166  this to vary the `injectors` defined for each PackageVariant, resulting in each
  1167  PackageRevision having different resources injected. This `spec`:
  1168  
  1169  ```yaml
  1170  spec:
  1171    upstream:
  1172      repo: example-repo
  1173      package: foo
  1174      revision: v1
  1175    targets:
  1176    - repositorySelector:
  1177        matchLabels:
  1178          env: prod
  1179          org: hr
  1180      template:
  1181        labelExprs:
  1182          key: org
  1183          valueExpr: "repository.labels['org']"
  1184        injectorExprs:
  1185          - nameExpr: "repository.labels['region'] + '-endpoints'"
  1186  ```
  1187  
  1188  will result in three PackageVariant resources, one for each Repository with the
  1189  labels env=prod and org=hr. The `labels` and `injectors` fields of the
  1190  PackageVariantSpec will be different for each of these PackageVariants, as
  1191  determined by the use of the `Expr` fields in the template, as shown here:
  1192  
  1193  ```yaml
  1194  apiVersion: config.porch.kpt.dev/v1alpha1
  1195  kind: PackageVariant
  1196  metadata:
  1197    namespace: default
  1198    name: example-aaaa
  1199  spec:
  1200    upstream:
  1201      repo: example-repo
  1202      package: foo
  1203      revision: v1
  1204    downstream:
  1205      repo: cluster-01
  1206      package: foo
  1207    labels:
  1208      org: hr
  1209    injectors:
  1210      name: useast1-endpoints
  1211  ---
  1212  apiVersion: config.porch.kpt.dev/v1alpha1
  1213  kind: PackageVariant
  1214  metadata:
  1215    namespace: default
  1216    name: example-aaab
  1217  spec:
  1218    upstream:
  1219      repo: example-repo
  1220      package: foo
  1221      revision: v1
  1222    downstream:
  1223      repo: cluster-03
  1224      package: foo
  1225    labels:
  1226      org: hr
  1227    injectors:
  1228      name: useast2-endpoints
  1229  ---
  1230  apiVersion: config.porch.kpt.dev/v1alpha1
  1231  kind: PackageVariant
  1232  metadata:
  1233    namespace: default
  1234    name: example-aaac
  1235  spec:
  1236    upstream:
  1237      repo: example-repo
  1238      package: foo
  1239      revision: v1
  1240    downstream:
  1241      repo: cluster-04
  1242      package: foo
  1243    labels:
  1244      org: hr
  1245    injectors:
  1246      name: uswest1-endpoints
  1247  ```
  1248  
  1249  Since the injectors are different for each PackageVariant, the resulting
  1250  PackageRevisions will each have different resources injected.
  1251  
  1252  When CEL expressions are evaluated, they have an environment associated with
  1253  them. That is, there are certain objects that are accessible within the CEL
  1254  expression. For CEL expressions used in the PackageVariantSet `template` field,
  1255  the following variables are available:
  1256  
  1257  | CEL Variable   | Variable Contents                                            |
  1258  | -------------- | ------------------------------------------------------------ |
  1259  | repoDefault    | The default repository name based on the targeting criteria. |
  1260  | packageDefault | The default package name based on the targeting criteria.    |
  1261  | upstream       | The upstream PackageRevision.                                |
  1262  | repository     | The downstream Repository.                                   |
  1263  | target         | The target object (details vary; see below).                 |
  1264  
  1265  There is one expression that is an exception to the table above. Since the
  1266  `repository` value corresponds to the Repository of the downstream, we must
  1267  first evaluate the `downstream.repoExpr` expression to *find* that
  1268  repository.  Thus, for that expression only, `repository` is not a valid
  1269  variable.
  1270  
  1271  There is one more variable available across all CEL expressions: the `target`
  1272  variable. This variable has a meaning that varies depending on the type of
  1273  target, as follows:
  1274  
  1275  | Target Type         | `target` Variable Contents |
  1276  | ------------------- | -------------------------- |
  1277  | Repo/Package List   | A struct with two fields: `repo` and `package`, the same as the `repoDefault` and `packageDefault` values. |
  1278  | Repository Selector | The Repository selected by the selector. Although not recommended, this could be different than the `repository` value, which can be altered with `downstream.repo` or `downstream.repoExpr`. |
  1279  | Object Selector     | The Object selected by the selector. |
  1280  
  1281  For the various resource variables - `upstream`, `repository`, and `target` -
  1282  arbitrary access to all fields of the object could lead to security concerns.
  1283  Therefore, only a subset of the data is available for use in CEL expressions.
  1284  Specifically, the following fields: `name`, `namespace`, `labels`, and
  1285  `annotations`.
  1286  
  1287  Given the slight quirk with the `repoExpr`, it may be helpful to state the
  1288  processing flow for the template evaluation:
  1289  
  1290  1. The upstream PackageRevision is loaded. It must be in the same namespace as
  1291     the PackageVariantSet[^multi-ns-reg].
  1292  1. The targets are determined.
  1293  1. For each target:
  1294     1. The CEL environment is prepared with `repoDefault`, `packageDefault`,
  1295        `upstream`, and `target` variables.
  1296     1. The downstream repository is determined and loaded, as follows:
  1297        - If present, `downstream.repoExpr` is evaluated using the CEL
  1298          environment, and the result used as the downstream repository name.
  1299        - Otherwise, if `downstream.repo` is set, that is used as the downstream
  1300         repository name.
  1301        - If neither is present, the default repository name based on the target is
  1302          used (i.e., the same value as the `repoDefault` variable).
  1303        - The resulting downstream repository name is used to load the corresponding
  1304          Repository object in the same namespace as the PackageVariantSet.
  1305     1. The downstream Repository is added to the CEL environment.
  1306     1. All other CEL expressions are evaluated.
  1307  1. Note that if any of the resources (e.g., the upstream PackageRevision, or the
  1308     downstream Repository) are not found our otherwise fail to load, processing
  1309     stops and a failure condition is raised. Similarly, if a CEL expression
  1310     cannot be properly evaluated due to syntax or other reasons, processing stops
  1311     and a failure condition is raised.
  1312  
  1313  #### Other Considerations
  1314  It would appear convenient to automatically inject the PackageVariantSet
  1315  targeting resource. However, it is better to require the package advertise
  1316  the ways it accepts injections (i.e., the GVKs it understands), and only inject
  1317  those. This keeps the separation of concerns cleaner; the package does not
  1318  build in an awareness of the context in which it expects to be deployed. For
  1319  example, a package should not accept a Porch Repository resource just because
  1320  that happens to be the targeting mechanism. That would make the package unusable
  1321  in other contexts.
  1322  
  1323  #### PackageVariantSet Status
  1324  
  1325  The PackageVariantSet status uses these conditions:
  1326   - `Stalled` is set to True if there has been a failure that most likely
  1327     requires user intervention.
  1328   - `Ready` is set to True if the last reconciliation successfully reconciled
  1329     all targeted PackageVariant resources.
  1330  
  1331  ## Future Considerations
  1332  - As an alternative to the floating tag proposal, we may instead want to have
  1333    a separate tag tracking controller that can update PV and PVS resources to
  1334    tweak their upstream as the tag moves.
  1335  - Installing a collection of packages across a set of clusters, or performing
  1336    the same mutations to each package in a collection, is only supported by
  1337    creating multiple PackageVariant / PackageVariantSet resources. Options to
  1338    consider for these use cases:
  1339    - `upstreams` listing multiple packages.
  1340    - Label selector against PackageRevisions. This does not seem that useful, as
  1341      PackageRevisions are highly re-usable and would likely be composed in many
  1342      different ways.
  1343    - A PackageRevisionSet resource that simply contained a list of Upstream
  1344      structures and could be used as an Upstream. This is functionally equivalent
  1345      to the `upstreams` option, but that list is reusable across resources.
  1346    - Listing multiple PackageRevisionSets in the upstream would be nice as well.
  1347    - Any or all of these could be implemented in PackageVariant,
  1348      PackageVariantSet, or both.
  1349  
  1350  ## Footnotes
  1351  [^porch17]: Implemented in Porch v0.0.17.
  1352  [^porch18]: Coming in Porch v0.0.18.
  1353  [^notimplemented]: Proposed here but not yet implemented as of Porch v0.0.18.
  1354  [^setns]: As of this writing, the `set-namespace` function does not have a
  1355      `create` option. This should be added to avoid the user needing to also use
  1356      the `upsert-resource` function. Such common operation should be simple for
  1357      users.
  1358  [^pvsimpl]: This document describes PackageVariantSet `v1alpha2`, which will be
  1359      available starting with Porch v0.0.18. In Porch v0.0.16 and 17, the `v1alpha1`
  1360      implementation is available, but it is a somewhat different API, without
  1361      support for CEL or any injection. It is focused only on fan out targeting,
  1362      and uses a [slightly different targeting
  1363      API](https://github.com/GoogleContainerTools/kpt/blob/main/porch/controllers/packagevariantsets/api/v1alpha1/packagevariantset_types.go).
  1364  [^repo-pkg-expr]: This is not exactly correct. As we will see later in the
  1365      `template` discussion, this the repository and package names listed actually
  1366      are just defaults for the template; they can be further manipulated in the
  1367      template to reference different downstream repositories and package names.
  1368      The same is true for the repositories selected via the `repositorySelector`
  1369      option. However, this can be ignored for now.
  1370  [^multi-ns-reg]: Note that the same upstream repository can be registered in
  1371      multiple namespaces without a problem. This simplifies access controls,
  1372      avoiding the need for cross-namespace relationships between Repositories and
  1373      other Porch resources.