github.com/argoproj/argo-cd/v2@v2.10.5/docs/proposals/parameterized-config-management-plugins.md (about)

     1  ---
     2  title: Parameterized-Config-Management-Plugins
     3  
     4  authors:
     5  - "@alexmt"
     6  - "@crenshaw-dev"
     7  - "@leoluz"
     8  
     9  sponsors:
    10  - TBD
    11  
    12  reviewers:
    13  - TBD
    14  
    15  approvers:
    16  - TBD
    17  
    18  creation-date: 2022-01-05
    19  
    20  last-updated: 2022-01-05
    21  
    22  ---
    23  
    24  # Parameterized Config Management Plugins
    25  
    26  Config Management Plugin (CMP) parameterization defines a way for plugins to "announce" and then consume acceptable 
    27  parameters for an Application. Announcing parameters allows CMPs to provide a UI experience similar to native config 
    28  management tools (Helm, Kustomize, etc.).
    29  
    30  - [Parameterized Config Management Plugins](#parameterized-config-management-plugins)
    31      * [Open Questions](#open-questions)
    32      * [Summary](#summary)
    33      * [Motivation](#motivation)
    34          + [1. CMPs are under-utilized](#1-cmps-are-under-utilized)
    35          + [2. Decisions about config management tools are limited by the core code](#2-decisions-about-config-management-tools-are-limited-by-the-core-code)
    36          + [3. Ksonnet is deprecated, and CMPs are a good place to maintain support](#3-ksonnet-is-deprecated-and-cmps-are-a-good-place-to-maintain-support)
    37          + [Goals](#goals)
    38          + [Non-Goals](#non-goals)
    39      * [Proposal](#proposal)
    40          + [Use cases](#use-cases)
    41              - [Use case 1: building Argo CD without config management dependencies](#use-case-1-building-argo-cd-without-config-management-dependencies)
    42              - [Use case 2: writing CMPs with rich UI experiences](#use-case-2-writing-cmps-with-rich-ui-experiences)
    43          + [Implementation Details/Notes/Constraints](#implementation-detailsnotesconstraints)
    44              - [Prerequisites](#prerequisites)
    45              - [Terms](#terms)
    46              - [How will the ConfigManagementPlugin spec change?](#how-will-the-configmanagementplugin-spec-change)
    47              - [How will the CMP know what parameter values are set?](#how-will-the-cmp-know-what-parameter-values-are-set)
    48              - [How will the UI know what parameters may be set?](#how-will-the-ui-know-what-parameters-may-be-set)
    49              - [Implementation Q/A](#implementation-qa)
    50          + [Detailed examples](#detailed-examples)
    51              - [Example 1: trivial parameterized CMP](#example-1-trivial-parameterized-cmp)
    52              - [Example 2: Helm parameters from Kustomize dependency](#example-2-helm-parameters-from-kustomize-dependency)
    53              - [Example 3: simple Helm CMP](#example-3-simple-helm-cmp)
    54              - [Example 4: simple Kustomize CMP](#example-4-simple-kustomize-cmp)
    55          + [Security Considerations](#security-considerations)
    56              - [Increased scripting](#increased-scripting)
    57          + [Risks and Mitigations](#risks-and-mitigations)
    58          + [Upgrade / Downgrade Strategy](#upgrade--downgrade-strategy)
    59      * [Drawbacks](#drawbacks)
    60      * [Alternatives](#alternatives)
    61  
    62  ## Open Questions
    63  
    64  * Should we write examples in documentation in Python instead of shell scripts?
    65  
    66    It's very easy to write an insecure shell script. People copy/paste code from documentation to start their own work.
    67    Maybe by using a different language in examples, we can encourage more secure CMP development.
    68  
    69  ## Summary
    70  
    71  [Config Management Plugins](https://argo-cd.readthedocs.io/en/stable/user-guide/config-management-plugins/) 
    72  allow Argo CD administrators to define custom manifest generation tooling.
    73  
    74  The only existing way for users to parameterize manifest generation is with environment variables.
    75  
    76  This proposed feature will allow a plugin to "announce" acceptable parameters for an Application. It will also allow the
    77  plugin to consume parameters once the user has set them.
    78  
    79  Parameters definitions may be simple (advertising a simple key/value string pair) or more complex (accepting an array of
    80  strings or a map of string keys to string values). Parameter definitions can also specify a data type (string, number, 
    81  or boolean) to help the UI present the most relevant input field.
    82  
    83  ## Motivation
    84  
    85  ### 1. CMPs are under-utilized
    86  
    87  CMPs, especially the sidecar type, are under-utilized. Making them more robust will increase adoption. Increased
    88  adoption will help us find bugs and then make CMPs more robust. In other words, we need to reach a critical mass of 
    89  CMP users.
    90  
    91  More robust CMPs will make it easier to start supporting tools like [Tanka](https://tanka.dev/).
    92  
    93  ### 2. Decisions about config management tools are limited by the core code
    94  
    95  For example, there's a [Helm bug](https://github.com/argoproj/argo-cd/issues/7291) affecting Argo CD users. The fix 
    96  would involve importing the Helm SDK (a very large dependency) into Argo CD. Implementing Helm support as a CMP would
    97  allow us to use that SDK without embedding it in the core code.
    98  
    99  ### 3. Ksonnet is deprecated, and CMPs are a good place to maintain support
   100  
   101  Offloading Ksonnet to a plugin would allow us to support existing users without maintaining Ksonnet code in the more
   102  actively-developed base. But we need CMP parameters to provide Ksonnet support on-par with native support.
   103  
   104  ### Goals
   105  
   106  Parameterized CMPs must be:
   107  * Easy to write
   108    * An Argo CD admin should be able to write a simple parameterized CMP in just a few lines of code.
   109    * An Argo CD admin should be able to write an _advanced_ parameterized CMP server relying on thorough docs.
   110      
   111      Writing a custom CMP server might be preferable if the parameters announcement code gets too complex to be 
   112      an inline shell script.
   113  * Easy to install
   114    * Installing a simple CMP or even a CMP with a custom server should be intuitive and painless.
   115  * Easy to use
   116    * Argo CD end-users (for example, developers) should be able to
   117      1. View and set parameters in the Argo CD Application UI
   118      2. See the parameters reflected in the Application manifest
   119      3. Easily read/modify the generated parameters in the manifest (they should be structured in a way that's easy to read)
   120    * CMPs should be able to announce parameters with more helpful interfaces than a simple text field.
   121      * For example, numbers and booleans should be represented in the UI with the appropriate inputs.
   122  * Future-proof
   123    * Since the rich parameters UI is an important feature for config management tools, the parameter definition schema 
   124      should be flexible enough to announce new _types_ of parameters so the UI can customize its presentation.
   125  * Backwards-compatible
   126    * CMPs written before this enhancement should work fine after this enhancement is released.
   127  * Proven with a rich demonstration
   128    * The initial release of this feature should include a CMP implementation of the Helm config tool. This will
   129      1. Serve as a rich example for others CMP developers to mimic
   130      2. Allow us to decouple the Helm config management release cycle from the Argo release cycle
   131      3. Allow us to work around [this bug](https://github.com/argoproj/argo-cd/issues/7291) without including the Helm 
   132         SDK in the core Argo CD code
   133    * The Helm CMP must be on-par with the native implementation.
   134      1. It must present an equivalent parameters UI.
   135      2. It must communicate errors back to the repo-server (and then the UI) the same as the native implementation.
   136  
   137  ### Non-Goals
   138  
   139  We should not:
   140  * Re-implement config management tools as CMPs (besides Helm)
   141  
   142  ## Proposal
   143  
   144  ### Use cases
   145  
   146  #### Use case 1: building Argo CD without config management dependencies
   147  
   148  As an Argo CD developer, I would like to be able to build Argo CD without including the Helm SDK as a dependency.
   149  
   150  The Helm SDK includes the Kubernetes code base. That's a lot of code, and it will make builds unacceptably slow.
   151  
   152  #### Use case 2: writing CMPs with rich UI experiences
   153  
   154  As an Argo CD user, I would like to be able to parameterize manifests built by a CMP.
   155  
   156  For example, if the Argo CD administrator has installed a CMP which applies a last-mile kustomize overlay to a Helm
   157  repo, I would like to be able to pass values to the Helm chart without having to manually discover those parameter names
   158  (in other words, they should show up in the Application UI just like with a native Helm Application). I also shouldn't 
   159  have to ask my Argo CD admin to modify the CMP to accommodate the values as environment variables.
   160  
   161  ### Implementation Details/Notes/Constraints
   162  
   163  #### Prerequisites
   164  
   165  Since this proposal is designed to increase CMP adoption, we need to make sure there aren't any bugs that make CMPs
   166  less robust than native tools.
   167  
   168  Bugs to fix:
   169  1. [#8145](https://github.com/argoproj/argo-cd/issues/8145) - `argocd app sync/diff --local` doesn't account for sidecar CMPs
   170  2. [#8243](https://github.com/argoproj/argo-cd/issues/8243) - "Configure plugin via sidecar" ⇒ child resources not pruned on deletion
   171  
   172  #### Terms
   173  
   174  * **Parameter announcement**: an instance of a data structure which describes an individual parameter that may be applied
   175    to a specific Application. (See the [schema](#how-will-the-ui-know-what-parameters-may-be-set) below.)
   176  * **Parameters announcement**: a list of parameter announcements. (See the [schema](#how-will-the-ui-know-what-parameters-may-be-set) below.)
   177  
   178    "Parameters" is plural because each "announcement" will be a list of multiple parameter announcements.
   179  * **Parameterized CMP**: a CMP which supports rich parameters (i.e. more than environment variables). A CMP is
   180    parameterized if either of these is true:
   181    1. its configuration includes the sections consumed by the default CMP server to generate parameters announcements
   182    2. it is a fully customized CMP server which implements an endpoint to generate parameters announcements
   183  
   184  #### How will the ConfigManagementPlugin spec change?
   185  
   186  This proposal adds a new `parameters` key to the ConfigManagementPlugin config spec.
   187  
   188  ```yaml
   189  apiVersion: argoproj.io/v1alpha1
   190  kind: ConfigManagementPlugin
   191  metadata:
   192    name: cmp-plugin
   193  spec:
   194    version: v1.0
   195    generate:
   196      command: ["example.sh"]
   197    discover:
   198      fileName: "./subdir/s*.yaml"
   199    # NEW KEY
   200    parameters:
   201      static:
   202      # The static announcement follows the parameters announcement schema. This is where a parameter description
   203      # should go if it applies to all apps for this CMP.
   204      - name: values-file
   205        title: Values File
   206        tooltip: Path of a Helm values file to apply to the chart.
   207      dynamic:
   208        # The (optional) generated announcement is combined with the declarative announcement (if present). This is where
   209        # a parameter description should be generated if it applies only to a specific app which the CMP handles.
   210        command: ["example-params.sh"]
   211  ```
   212  
   213  The currently-configured parameters (if there are any) will be communicated to both `generate.command` and 
   214  `parameters.dynamic.command` via an `ARGOCD_APP_PARAMETERS` environment variable. The parameters will be encoded 
   215  according to the [parameters serialization format](#how-will-the-cmp-know-what-parameter-values-are-set) defined below.
   216  
   217  Passing the parameters to the `parameters.dynamic.command` will allow configuration of parameter discovery. For example,
   218  if my CMP is designed to handle Kustomize projects which contain Helm charts, I might have the CMP accept an
   219  `ignore-helm-charts` parameter to avoid announcing parameters for those charts.
   220  
   221  ```yaml
   222  apiVersion: argoproj.io/v1alpha1
   223  kind: Application
   224  spec:
   225    source:
   226      plugin:
   227        parameters:
   228        - name: ignore-helm-charts
   229          array: [chart-a, chart-b]
   230  ```
   231  
   232  #### How will the CMP know what parameter values are set?
   233  
   234  Users persist parameter values in an Application's `spec.source.plugin.parameters` list.
   235  
   236  Each parameter has a `name` and a value stored in the `string`, `array`, or `map` field, according to the parameter's 
   237  collectionType. The name should match the name of some parameter announced by the CMP. (But 
   238  the user can set any parameter name, so it's the CMP's job to ignore invalid parameters.)
   239  
   240  This example is for a hypothetical Helm CMP. This CMP accepts a `values` and a `values-files` parameter.
   241  
   242  ```yaml
   243  apiVersion: argoproj.io/v1alpha1
   244  kind: Application
   245  spec:
   246    source:
   247      repoURL: https://github.com/argoproj/argocd-example-apps.git
   248      plugin:
   249        parameters:
   250          - name: values
   251            string: >-
   252              resources:
   253                cpu: 100m
   254                memory: 128Mi
   255          - name: values-files
   256            array: [values.yaml]
   257          - name: helm-parameters
   258            map: 
   259              image.repository: my.example.com/gcr-proxy/heptio-images/ks-guestbook-demo
   260              image.tag: "0.1"
   261  ```
   262  
   263  When Argo CD generates manifests (for example, when the user clicks "Hard Refresh" in the UI), Argo CD will send these
   264  parameters to the CMP as JSON (using the equivalent structure to what's shown above) on an environment variable called
   265  `ARGOCD_APP_PARAMETERS`.
   266  
   267  ```shell
   268  echo "$ARGOCD_APP_PARAMETERS" | jq
   269  ```
   270  
   271  That command, when run by a CMP with the above Application manifest, will print the following:
   272  
   273  ```json
   274  [
   275    {
   276      "name": "values",
   277      "string": "resources:\n  cpu: 100m\n  memory: 128Mi"
   278    },
   279    {
   280      "name": "values-files",
   281      "array": ["values.yaml"]
   282    },
   283    {
   284      "name": "helm-parameters",
   285      "map": {
   286        "image.repository": "my.example.com/gcr-proxy/heptio-images/ks-guestbook-demo",
   287        "image.tag": "0.1"
   288      }
   289    }
   290  ]
   291  ```
   292  
   293  Another way the CMP can access parameters is via environment variables. For example:
   294  
   295  ```shell
   296  echo "$VALUES" > /tmp/values.yaml
   297  helm template --values /tmp/values.yaml .
   298  ```
   299  
   300  Environment variable names are set according to these rules:
   301  
   302  1. If a parameter is a `string`, the format is `PARAM_{escaped(name)}` (`escaped` is defined below).
   303  2. If a parameter is an `array`, the format is `PARAM_{escaped(name_{index})}` (where the first index is 0).
   304  3. If a parameter is a `map`, the format is `PARAM_{escaped(name_key)}`.
   305  4. If an escaped env var name matches one in the [build environment](https://argo-cd-docs.readthedocs.io/en/latest/user-guide/build-environment/),
   306     the build environment variable wins.
   307  5. If more than one parameter name produces the same env var name, the env var later in the list wins.
   308  
   309  The `escaped` function will perform the following tasks:
   310  1. It will uppercase the input.
   311  2. It will replace any characters matching this regex with an underscore: `[^A-Z0-9_]`.
   312  
   313  The above example will produce the following env vars:
   314  
   315  ```shell
   316  echo "$PARAM_VALUES"
   317  echo "$PARAM_VALUES_FILES_0"
   318  echo "$PARAM_HELM_PARAMETERS_IMAGE_REPOSITORY"
   319  echo "$PARAM_HELM_PARAMETERS_IMAGE_TAG"
   320  ```
   321  
   322  The parameters in the Application manifest are represented behind the scenes with the following Go types:
   323  
   324  ```go
   325  package cmp
   326  
   327  // Parameter represents a single parameter name and its value. One of Value, Map, or Array must be set.
   328  type Parameter struct {
   329  	// Name is the name identifying a parameter. (required)
   330  	Name  string                     `json:"name,omitempty"`
   331  	String         string            `json:"string,omitempty"`
   332  	Map            map[string]string `json:"map,omitempty"`
   333  	Array          []string          `json:"array,omitempty"`
   334  }
   335  
   336  // Parameters is a list of parameters to be sent to a CMP for manifest generation.
   337  type Parameters []Parameter
   338  ```
   339  
   340  #### How will the UI know what parameters may be set?
   341  
   342  The CMP developer will have two ways to announce acceptable parameters: statically (declaratively) and dynamically.
   343  
   344  Static parameter announcements are written directly into the CMP config file:
   345  
   346  ```yaml
   347  apiVersion: argoproj.io/v1alpha1
   348  kind: ConfigManagementPlugin
   349  metadata:
   350    name: helm
   351  spec:
   352    parameters:
   353      static:
   354      - name: values-files
   355        title: Values Files
   356        collectionType: array
   357  ```
   358  
   359  Since this hypothetical Helm CMP will accept an array of values.yaml files for every app it handles, the CMP developer
   360  can add that parameter as a static parameter announcement in the CMP config.
   361  
   362  Dynamic parameters are generated by a CMP developer-defined command.
   363  
   364  A parameter definition is an object with following schema:
   365  
   366  ```yaml
   367  apiVersion: argoproj.io/v1alpha1
   368  kind: ConfigManagementPlugin
   369  metadata:
   370    name: helm
   371  spec:
   372    parameters:
   373      dynamic:
   374        command: 
   375        - sh
   376        - -c
   377        - |
   378          # Use yq to generate a list of parameters. Then use jq to convert that list of parameters to a parameters
   379          # announcement list.
   380          yq e -o=p values.yaml | jq -nR '
   381            [{
   382              name: "helm-parameters",
   383              title: "Helm Parameters",
   384              tooltip: "Parameters to override when generating manifests with Helm",
   385              collectionType: "map",
   386              map: (inputs | capture("(?<key>.*) = (?<value>.*)") | from_entries)
   387            }]'
   388  ```
   389  
   390  For a Helm chart with only an `image.repository` and `image.tag` in values.yaml, the parameter announcement would look
   391  like this:
   392  
   393  ```json
   394  [
   395    {
   396      "name": "helm-parameters",
   397      "collectionType": "map",
   398      "title": "Helm Parameters",
   399      "tooltip": "Parameters to override when generating manifests with Helm",
   400      "map": {
   401        "image.repository": "my.example.com/gcr-proxy/heptio-images/ks-guestbook-demo",
   402        "image.tag": "0.1"
   403      }
   404    }
   405  ]
   406  ```
   407  
   408  Before sending a parameters announcement to the UI, the CMP server will combine the static and dynamic parameters.
   409  (Behind the scenes, the list is actually communicated to the UI via gRPC, but they're presented here as JSON for 
   410  readability.)
   411  
   412  ```json
   413  
   414  [
   415    {
   416      "name": "values-files",
   417      "title": "Values Files",
   418      "collectionType": "array"
   419    },
   420    {
   421      "name": "helm-parameters",
   422      "collectionType": "map",
   423      "title": "Helm Parameters",
   424      "tooltip": "Parameters to override when generating manifests with Helm",
   425      "map": {
   426        "image.repository": "my.example.com/gcr-proxy/heptio-images/ks-guestbook-demo",
   427        "image.tag": "0.1"
   428      }
   429    }
   430  ]
   431  ```
   432  
   433  This is the full parameters announcement schema as Go types.
   434  
   435  ```go
   436  package cmp
   437  
   438  // ParameterItemType is the primitive data type of each of the parameter's value (or each of its values, if it's an array or
   439  // a map).
   440  type ParameterItemType string
   441  
   442  // Anything besides "number" and "boolean" is treated as string.
   443  const (
   444  	ParameterItemTypeNumber  ParameterItemType = "number"
   445  	ParameterItemTypeBoolean ParameterItemType = "boolean"
   446  )
   447  
   448  // ParameterCollectionType is a parameter's value's type - a single value (like a string) or a collection (like an array or a
   449  // map).
   450  type ParameterCollectionType string
   451  
   452  // Anything besides "number" and "boolean" is treated as string.
   453  const (
   454  	ParameterCollectionTypeMap    ParameterCollectionType = "map"
   455  	ParameterCollectionTypeArray  ParameterCollectionType = "array"
   456  )
   457  
   458  // ParameterAnnouncement represents a CMP's announcement of one acceptable parameter (though that parameter may contain
   459  // multiple elements, if the value holds an array or a map).
   460  type ParameterAnnouncement struct {
   461  	// Name is the name identifying a parameter. (required)
   462  	Name string                            `json:"name,omitempty"`
   463  	// Title is a human-readable text of the parameter name. (optional)
   464  	Title    string                        `json:"title,omitempty"`
   465  	// Tooltip is a human-readable description of the parameter. (optional)
   466  	Tooltip  string                        `json:"tooltip,omitempty"`
   467  	// Required defines if this given parameter is mandatory. (optional: default false)
   468  	Required bool                          `json:"required,omitempty"`
   469  	// ItemType determines the primitive data type represented by the parameter. Parameters are always encoded as
   470  	// strings, but ParameterTypes lets them be interpreted as other primitive types.
   471  	ItemType ParameterItemType             `json:"itemType,omitempty"`
   472  	// CollectionType is the type of value this parameter holds - either a single value (a string) or a collection (array or map).
   473  	// If Type is set, only the field with that type will be used. If Type is not set, `string` is the default. If Type
   474  	// is set to an invalid value, a validation error is thrown.
   475  	CollectionType ParameterCollectionType `json:"collectionType,omitempty"`
   476  	String         string                  `json:"string,omitempty"`
   477  	Map            map[string]string       `json:"map,omitempty"`
   478  	Array          []string                `json:"array,omitempty"`
   479  }
   480  
   481  // ParametersAnnouncement is a list of announcements. This list represents all the parameters which a CMP is able to 
   482  // accept.
   483  type ParametersAnnouncement []ParameterAnnouncement
   484  ```
   485  
   486  #### Implementation Q/A
   487  
   488  1. **Question**: What do we do if the CMP announcement sets more than one `value.{collection}`?
   489  
   490     **Answer**: We ignore all but the configured `collectionType`.
   491  
   492     ```yaml
   493     - name: images
   494       collectionType: map
   495       array:  # this gets ignored because collectionType is 'map'
   496       - ubuntu:latest=docker.example.com/proxy/ubuntu:latest
   497       - guestbook:v0.1=docker.example.com/proxy/guestbook:v0.1
   498       map:
   499         ubuntu:latest: docker.example.com/proxy/ubuntu:latest
   500         guestbook:v0.1: docker.example.com/proxy/guestbook:v0.1
   501     ```
   502  
   503  2. **Question**: What do we do if the CMP user sets more than one of `value`/`array`/`map` in the Application spec?
   504  
   505     **Answer**: We send all given information to the CMP and allow it to select the relevant field.
   506  
   507     ```yaml
   508     apiVersion: argoproj.io/v1alpha1
   509     kind: Application
   510     spec:
   511       source:
   512         plugin:
   513           parameters:
   514           - name: images
   515             array:  # this gets sent to the CMP, but the CMP should ignore it
   516             - ubuntu:latest=docker.example.com/proxy/ubuntu:latest
   517             - guestbook:v0.1=docker.example.com/proxy/guestbook:v0.1
   518             map:
   519               ubuntu:latest: docker.example.com/proxy/ubuntu:latest
   520               guestbook:v0.1: docker.example.com/proxy/guestbook:v0.1
   521     ```
   522  
   523  3. **Question**: How will the UI know that adding more items to an array or a map is allowed?
   524  
   525     **Answer**: Always assume it's allowed to add to a map or array.
   526  
   527     ```yaml
   528     - name: images
   529       collectionType: map  # users will be allowed to add new items, because this is a map
   530       map:
   531         ubuntu:latest: docker.example.com/proxy/ubuntu:latest
   532         guestbook:v0.1: docker.example.com/proxy/guestbook:v0.1
   533     ```
   534  
   535     If the CMP author wants an immutable array or map, they should just break it into individual parameters.
   536  
   537     ```yaml
   538     - name: ubuntu:latest
   539       string: docker.example.com/proxy/ubuntu:latest
   540     - name: guestbook:v0.1
   541       string: docker.example.com/proxy/guestbook:v0.1
   542     ```
   543  
   544  4. **Question**: What do we do if a CMP announcement doesn't include a `collectionType`?
   545  
   546     **Answer**: Default to `string`.
   547  
   548     ```yaml
   549     - name: name-prefix  # expects a string
   550     - name: helm-parameters-incorrect  # expects a string, the map is ignored
   551       map:
   552         global.image.repository: quay.io/argoproj/argocd
   553     - name: helm-parameters  # expects a map
   554       collectionType: map
   555       map:
   556         global.image.repository: quay.io/argoproj/argocd
   557     ```
   558  
   559  5. **Question**: What do we do if a parameter has a missing or absent top-level `name` field?
   560  
   561     **Answer**: Throw a validation error in the CMP server when handling an announcement. Throw a validation error
   562     in the controller and mark the Application as unhealthy if the invalid spec is in the Application. Throw an error
   563     in the CMP server and refuse to generate manifests in the CMP server if given invalid parameters.
   564  
   565     ```yaml
   566     # needs a `name` field
   567     - title: Parameter Overrides
   568       collectionType: map
   569       map:
   570         global.image.repository: quay.io/argoproj/argocd
   571     ```
   572  
   573  ### Detailed examples
   574  
   575  #### Example 1: trivial parameterized CMP
   576  
   577  ```yaml
   578  apiVersion: argoproj.io/v1alpha1
   579  kind: ConfigManagementPlugin
   580  metadata:
   581    name: trivial-cmp
   582  spec:
   583    version: v1.0
   584    generate:
   585      command: 
   586        - sh
   587        - -c
   588        - |
   589          # Pull one parameter value from the "main" section of the given parameters.
   590          CM_NAME_SUFFIX=$(echo "$ARGOCD_APP_PARAMETERS" | jq -r '.["main"][] | select(.name == "cm-name-suffix").value')
   591          cat << EOM
   592          {
   593            "kind": "ConfigMap",
   594            "apiVersion": "v1",
   595            "metadata": {
   596              "name": "$ARGOCD_APP_NAME-$CM_NAME_SUFFIX",
   597              "namespace": "$ARGOCD_APP_NAMESPACE"
   598            }
   599          }
   600          EOM
   601    discover:
   602      fileName: "./trivial-cmp"
   603    parameters:
   604      command:
   605        - sh
   606        - -c
   607        - |
   608          echo '[{"name": "cm-name-suffix"}]'
   609  ```
   610  
   611  #### Example 2: Helm parameters from Kustomize dependency
   612  
   613  **Plugin config**
   614  
   615  ```yaml
   616  apiVersion: argoproj.io/v1alpha1
   617  kind: ConfigManagementPlugin
   618  metadata:
   619    name: kustomize-helm-proxy-cmp
   620  spec:
   621    version: v1.0
   622    generate:
   623      command: [/home/argocd/generate.sh]
   624    discover:
   625      fileName: "./kustomization.yaml"
   626    parameters:
   627      static:
   628        - name: version
   629          title: VERSION
   630          string: v4.3.0
   631        - name: name-prefix
   632          title: NAME PREFIX
   633        - name: name-suffix
   634          title: NAME SUFFIX
   635      dynamic:
   636        command: [/home/argocd/get-parameters.sh]
   637  ```
   638  
   639  **generate.sh**
   640  
   641  This script would be non-trivial. Kustomize only accepts YAML-formatted values for Helm charts. The script would have to
   642  convert the dot-notated parameters to a YAML file.
   643  
   644  **get-parameters.sh**
   645  
   646  ```shell
   647  kustomize build . --enable-helm > /dev/null
   648  
   649  get_parameters() {
   650  while read -r chart; do  
   651    yq e -o=p "charts/$chart/values.yaml" | jq --arg chart "$chart" --slurp --raw-input '
   652      {
   653        name: "\($chart)-helm-parameters",
   654        title: "\($chart) Helm parameters",
   655        tooltip: "Parameter overrides for the \($chart) Helm chart.",
   656        collectionType: "map",
   657        map: split("\\n") | map(capture("(?<key>.*) = (?<value>.*)")) | from_entries
   658      }'
   659  done << EOF
   660  $(yq e '.helmCharts[].name' kustomization.yaml)
   661  EOF
   662  }
   663  
   664  # Collect the parameters generated for each chart into one array.
   665  get_parameters | jq --slurp
   666  ```
   667  
   668  **Dockerfile**
   669  
   670  ```dockerfile
   671  FROM ubuntu:20.04
   672  
   673  RUN apt install jq yq helm kustomize -y
   674  
   675  ADD get-parameters.sh /home/argocd/get-parameters.sh
   676  ```
   677  
   678  #### Example 3: simple Helm CMP
   679  
   680  This example demonstrates how the Helm parameters interface could be achieved with a parameterized CMP.
   681  
   682  ![Helm parameters interface](images/helm-parameters.png)
   683  
   684  ```yaml
   685  apiVersion: argoproj.io/v1alpha1
   686  kind: ConfigManagementPlugin
   687  metadata:
   688    name: simple-helm-cmp
   689  spec:
   690    version: v1.0
   691    generate:
   692      command: [/home/argocd/generate.sh]
   693    discover:
   694      fileName: "./values.yaml"
   695    parameters:
   696      static:
   697      - name: values-files
   698        title: VALUES FILES
   699        collectionType: array
   700      dynamic:
   701        command: [/home/argocd/get-parameters.sh]
   702  ```
   703  
   704  **generate.sh**
   705  
   706  ```shell
   707  # Convert the values-files parameter value to a newline-delimited list of Helm CLI arguments.
   708  ARGUMENTS=$(echo "$ARGOCD_APP_PARAMETERS" | jq -r '.[] | select(.name == "values-files").array | .[] | "--values=" + .')
   709  # Convert JSON parameters to comma-delimited k=v pairs.
   710  PARAMETERS=$(echo "$ARGOCD_APP_PARAMETERS" | jq -r '.[] | select(.name == "helm-parameters").map | to_entries | map("\(.key)=\(.value)") | .[] | "--set=" + .')
   711  # Add parameters to the arguments variable.
   712  ARGUMENTS="$ARGUMENTS\n$PARAMETERS"
   713  echo "$ARGUMENTS" | xargs helm template .
   714  ```
   715  
   716  The manifest generation command will be 
   717  `helm template . --values=a.yaml --values=b.yaml --set=image.repo=alpine --set=image.tag=latest`
   718  for the following value of `$ARGOCD_APP_PARAMETERS`:
   719  
   720  ```json
   721  [
   722    {
   723      "name": "values-files",
   724      "array": ["a.yaml", "b.yaml"]
   725    },
   726    {
   727      "name": "helm-parameters",
   728      "map": {
   729        "image.repo": "alpine",
   730        "image.tag": "latest"
   731      }
   732    }
   733  ]
   734  ```
   735  
   736  **get-parameters.sh**
   737  
   738  ```shell
   739  yq e -o=p values.yaml | jq --slurp --raw-input '
   740    [{
   741      name: "helm-parameters", 
   742      title: "Helm Parameters",
   743      collectionType: "map",
   744      map: split("\\n") | map(capture("(?<key>.*) = (?<value>.*)")) | from_entries
   745    }]'
   746  ```
   747  
   748  Consider a very simple values.yaml:
   749  
   750  ```yaml
   751  image:
   752    repo: quay.io/argoproj/argocd
   753    tag: latest
   754  ```
   755  
   756  The script above will produce the following parameters announcement:
   757  
   758  ```json
   759  [
   760    {
   761      "name": "helm-parameters",
   762      "title": "Helm Parameters",
   763      "collectionType": "map",
   764      "map": {
   765        "image.repo": "quay.io/argoproj/argocd",
   766        "image.tag": "latest"
   767      }
   768    }
   769  ]
   770  ```
   771  
   772  #### Example 4: simple Kustomize CMP
   773  
   774  ```yaml
   775  apiVersion: argoproj.io/v1alpha1
   776  kind: ConfigManagementPlugin
   777  metadata:
   778    name: kustomize
   779  spec:
   780    parameters:
   781      static:
   782      - name: version
   783        title: VERSION
   784        string: v4.3.0
   785      - name: name-prefix
   786        title: NAME PREFIX
   787      - name: name-suffix
   788        title: NAME SUFFIX
   789      dynamic:
   790        command: ["generate-params.sh"]
   791  ```
   792  
   793  `parameters.dynamic.command` will produce something like this:
   794  
   795  ```yaml
   796  [
   797    {
   798      "name": "images",
   799      "title": "Image Overrides",
   800      "collectionType": "map",
   801      "map": {
   802        "quay.io/argoproj/argocd": "docker.example.com/proxy/argoproj/argocd",
   803        "ubuntu:latest": "docker.example.com/proxy/argoproj/argocd"
   804      }
   805    }
   806  ]
   807  ```
   808  
   809  ### Security Considerations
   810  
   811  #### Increased scripting
   812  
   813  Our examples will have shell scripts, and users will write shell scripts. Scripts are difficult
   814  to write securely - this is especially true when the scripts are embedded in YAML, and developers don't get helpful 
   815  warnings from the IDE.
   816  
   817  Our docs should emphasize the importance of handling input carefully in any scripts (or other programs) which will be
   818  executed as part of CMPs.
   819  
   820  The docs should also warn against embedding large scripts in YAML and recommend plugin authors instead build custom
   821  images with the script invoked as its own file. The docs should also recommend taking advantage of IDE plugins as
   822  well as image and source code scanning tools in CI/CD.
   823  
   824  ### Risks and Mitigations
   825  
   826  1. Risk: encouraging CMP adoption while missing critical features from native tools.
   827  
   828     Mitigation: rewrite the Helm config management tool as a CMP and test as many common use cases as possible. Write a
   829     document before starting on the Helm CMP documenting all major features which must be tested.
   830  
   831  ### Upgrade / Downgrade Strategy
   832  
   833  Upgrading will only require using a new version of Argo CD and adding the `parameters` settings to the plugin config.
   834  
   835  Downgrading will only require using an older version of Argo CD. The `parameters` section of the plugin config will 
   836  simply be ignored.
   837  
   838  ## Drawbacks
   839  
   840  Sidecar CMPs aren't really battle-tested. If there are major issues we've missed, then moving more users towards CMPs
   841  could involve a lot of growing pains.
   842  
   843  ## Alternatives