Matrix Generator

The Matrix generator combines the parameters generated by two child generators, iterating through every combination of each generator’s generated parameters.

By combining both generators parameters, to produce every possible combination, this allows you to gain the intrinsic properties of both generators. For example, a small subset of the many possible use cases include:

  • SCM Provider Generator + Cluster Generator: Scanning the repositories of a GitHub organization for application resources, and targeting those resources to all available clusters.
  • Git File Generator + List Generator: Providing a list of applications to deploy via configuration files, with optional configuration options, and deploying them to a fixed list of clusters.
  • Git Directory Generator + Cluster Decision Resource Generator: Locate application resources contained within folders of a Git repository, and deploy them to a list of clusters provided via an external custom resource.
  • And so on…

Any set of generators may be used, with the combined values of those generators inserted into the template parameters, as usual.

Note: If both child generators are Git generators, one or both of them must use the pathParamPrefix option to avoid conflicts when merging the child generators’ items.

Example: Git Directory generator + Cluster generator

As an example, imagine that we have two clusters:

  • A staging cluster (at https://1.2.3.4)
  • A production cluster (at https://2.4.6.8)

And our application YAMLs are defined in a Git repository:

  • Argo Workflows controller (examples/git-generator-directory/cluster-addons/argo-workflows)
  • Prometheus operator (/examples/git-generator-directory/cluster-addons/prometheus-operator)

Our goal is to deploy both applications onto both clusters, and, more generally, in the future to automatically deploy new applications in the Git repository, and to new clusters defined within Argo CD, as well.

For this we will use the Matrix generator, with the Git and the Cluster as child generators:

  1. apiVersion: argoproj.io/v1alpha1
  2. kind: ApplicationSet
  3. metadata:
  4. name: cluster-git
  5. spec:
  6. generators:
  7. # matrix 'parent' generator
  8. - matrix:
  9. generators:
  10. # git generator, 'child' #1
  11. - git:
  12. repoURL: https://github.com/argoproj/argo-cd.git
  13. revision: HEAD
  14. directories:
  15. - path: applicationset/examples/matrix/cluster-addons/*
  16. # cluster generator, 'child' #2
  17. - clusters:
  18. selector:
  19. matchLabels:
  20. argocd.argoproj.io/secret-type: cluster
  21. template:
  22. metadata:
  23. name: '{{path.basename}}-{{name}}'
  24. spec:
  25. project: '{{metadata.labels.environment}}'
  26. source:
  27. repoURL: https://github.com/argoproj/argo-cd.git
  28. targetRevision: HEAD
  29. path: '{{path}}'
  30. destination:
  31. server: '{{server}}'
  32. namespace: '{{path.basename}}'

First, the Git directory generator will scan the Git repository, discovering directories under the specified path. It discovers the argo-workflows and prometheus-operator applications, and produces two corresponding sets of parameters:

  1. - path: /examples/git-generator-directory/cluster-addons/argo-workflows
  2. path.basename: argo-workflows
  3. - path: /examples/git-generator-directory/cluster-addons/prometheus-operator
  4. path.basename: prometheus-operator

Next, the Cluster generator scans the set of clusters defined in Argo CD, finds the staging and production cluster secrets, and produce two corresponding sets of parameters:

  1. - name: staging
  2. server: https://1.2.3.4
  3. - name: production
  4. server: https://2.4.6.8

Finally, the Matrix generator will combine both sets of outputs, and produce:

  1. - name: staging
  2. server: https://1.2.3.4
  3. path: /examples/git-generator-directory/cluster-addons/argo-workflows
  4. path.basename: argo-workflows
  5. - name: staging
  6. server: https://1.2.3.4
  7. path: /examples/git-generator-directory/cluster-addons/prometheus-operator
  8. path.basename: prometheus-operator
  9. - name: production
  10. server: https://2.4.6.8
  11. path: /examples/git-generator-directory/cluster-addons/argo-workflows
  12. path.basename: argo-workflows
  13. - name: production
  14. server: https://2.4.6.8
  15. path: /examples/git-generator-directory/cluster-addons/prometheus-operator
  16. path.basename: prometheus-operator

(The full example can be found here.)

Using Parameters from one child generator in another child generator

The Matrix generator allows using the parameters generated by one child generator inside another child generator. Below is an example that uses a git-files generator in conjunction with a cluster generator.

  1. apiVersion: argoproj.io/v1alpha1
  2. kind: ApplicationSet
  3. metadata:
  4. name: cluster-git
  5. spec:
  6. generators:
  7. # matrix 'parent' generator
  8. - matrix:
  9. generators:
  10. # git generator, 'child' #1
  11. - git:
  12. repoURL: https://github.com/argoproj/applicationset.git
  13. revision: HEAD
  14. files:
  15. - path: "examples/git-generator-files-discovery/cluster-config/**/config.json"
  16. # cluster generator, 'child' #2
  17. - clusters:
  18. selector:
  19. matchLabels:
  20. argocd.argoproj.io/secret-type: cluster
  21. kubernetes.io/environment: '{{path.basename}}'
  22. template:
  23. metadata:
  24. name: '{{name}}-guestbook'
  25. spec:
  26. project: default
  27. source:
  28. repoURL: https://github.com/argoproj/applicationset.git
  29. targetRevision: HEAD
  30. path: "examples/git-generator-files-discovery/apps/guestbook"
  31. destination:
  32. server: '{{server}}'
  33. namespace: guestbook

Here is the corresponding folder structure for the git repository used by the git-files generator:

  1. ├── apps
  2. └── guestbook
  3. ├── guestbook-ui-deployment.yaml
  4. ├── guestbook-ui-svc.yaml
  5. └── kustomization.yaml
  6. ├── cluster-config
  7. └── engineering
  8. ├── dev
  9. └── config.json
  10. └── prod
  11. └── config.json
  12. └── git-generator-files.yaml

In the above example, the {{path.basename}} parameters produced by the git-files generator will resolve to dev and prod. In the 2nd child generator, the label selector with label kubernetes.io/environment: {{path.basename}} will resolve with the values produced by the first child generator’s parameters (kubernetes.io/environment: prod and kubernetes.io/environment: dev).

So in the above example, clusters with the label kubernetes.io/environment: prod will have only prod-specific configuration (ie. prod/config.json) applied to it, wheres clusters with the label kubernetes.io/environment: dev will have only dev-specific configuration (ie. dev/config.json)

Overriding parameters from one child generator in another child generator

The Matrix Generator allows parameters with the same name to be defined in multiple child generators. This is useful, for example, to define default values for all stages in one generator and override them with stage-specific values in another generator. The example below generates a Helm-based application using a matrix generator with two git generators: the first provides stage-specific values (one directory per stage) and the second provides global values for all stages.

  1. apiVersion: argoproj.io/v1alpha1
  2. kind: ApplicationSet
  3. metadata:
  4. name: parameter-override-example
  5. spec:
  6. generators:
  7. - matrix:
  8. generators:
  9. - git:
  10. repoURL: https://github.com/example/values.git
  11. revision: HEAD
  12. files:
  13. - path: "**/stage.values.yaml"
  14. - git:
  15. repoURL: https://github.com/example/values.git
  16. revision: HEAD
  17. files:
  18. - path: "global.values.yaml"
  19. goTemplate: true
  20. template:
  21. metadata:
  22. name: example
  23. spec:
  24. project: default
  25. source:
  26. repoURL: https://github.com/example/example-app.git
  27. targetRevision: HEAD
  28. path: .
  29. helm:
  30. values: |
  31. {{ `{{ . | mustToPrettyJson }}` }}
  32. destination:
  33. server: in-cluster
  34. namespace: default

Given the following structure/content of the example/values repository:

  1. ├── test
  2. └── stage.values.yaml
  3. stageName: test
  4. cpuRequest: 100m
  5. debugEnabled: true
  6. ├── staging
  7. └── stage.values.yaml
  8. stageName: staging
  9. ├── production
  10. └── stage.values.yaml
  11. stageName: production
  12. memoryLimit: 512Mi
  13. debugEnabled: false
  14. └── global.values.yaml
  15. cpuRequest: 200m
  16. memoryLimit: 256Mi
  17. debugEnabled: true

The matrix generator above would yield the following results:

  1. - stageName: test
  2. cpuRequest: 100m
  3. memoryLimit: 256Mi
  4. debugEnabled: true
  5. - stageName: staging
  6. cpuRequest: 200m
  7. memoryLimit: 256Mi
  8. debugEnabled: true
  9. - stageName: production
  10. cpuRequest: 200m
  11. memoryLimit: 512Mi
  12. debugEnabled: false

Example: Two Git Generators Using pathParamPrefix

The matrix generator will fail if its children produce results containing identical keys with differing values. This poses a problem for matrix generators where both children are Git generators since they auto-populate path-related parameters in their outputs. To avoid this problem, specify a pathParamPrefix on one or both of the child generators to avoid conflicting parameter keys in the output.

  1. apiVersion: argoproj.io/v1alpha1
  2. kind: ApplicationSet
  3. metadata:
  4. name: two-gits-with-path-param-prefix
  5. spec:
  6. generators:
  7. - matrix:
  8. generators:
  9. # git file generator referencing files containing details about each
  10. # app to be deployed (e.g., `appName`).
  11. - git:
  12. repoURL: https://github.com/some-org/some-repo.git
  13. revision: HEAD
  14. files:
  15. - path: "apps/*.json"
  16. pathParamPrefix: app
  17. # git file generator referencing files containing details about
  18. # locations to which each app should deploy (e.g., `region` and
  19. # `clusterName`).
  20. - git:
  21. repoURL: https://github.com/some-org/some-repo.git
  22. revision: HEAD
  23. files:
  24. - path: "targets/{{appName}}/*.json"
  25. pathParamPrefix: target
  26. template: {} # ...

Then, given the following file structure/content:

  1. ├── apps
  2. ├── app-one.json
  3. { "appName": "app-one" }
  4. └── app-two.json
  5. { "appName": "app-two" }
  6. └── targets
  7. ├── app-one
  8. ├── east-cluster-one.json
  9. { "region": "east", "clusterName": "cluster-one" }
  10. └── east-cluster-two.json
  11. { "region": "east", "clusterName": "cluster-two" }
  12. └── app-two
  13. ├── east-cluster-one.json
  14. { "region": "east", "clusterName": "cluster-one" }
  15. └── west-cluster-three.json
  16. { "region": "west", "clusterName": "cluster-three" }

…the matrix generator above would yield the following results:

  1. - appName: app-one
  2. app.path: /apps
  3. app.path.filename: app-one.json
  4. # plus additional path-related parameters from the first child generator, all
  5. # prefixed with "app".
  6. region: east
  7. clusterName: cluster-one
  8. target.path: /targets/app-one
  9. target.path.filename: east-cluster-one.json
  10. # plus additional path-related parameters from the second child generator, all
  11. # prefixed with "target".
  12. - appName: app-one
  13. app.path: /apps
  14. app.path.filename: app-one.json
  15. region: east
  16. clusterName: cluster-two
  17. target.path: /targets/app-one
  18. target.path.filename: east-cluster-two.json
  19. - appName: app-two
  20. app.path: /apps
  21. app.path.filename: app-two.json
  22. region: east
  23. clusterName: cluster-one
  24. target.path: /targets/app-two
  25. target.path.filename: east-cluster-one.json
  26. - appName: app-two
  27. app.path: /apps
  28. app.path.filename: app-two.json
  29. region: west
  30. clusterName: cluster-three
  31. target.path: /targets/app-two
  32. target.path.filename: west-cluster-three.json

Restrictions

  1. The Matrix generator currently only supports combining the outputs of only two child generators (eg does not support generating combinations for 3 or more).

  2. You should specify only a single generator per array entry, eg this is not valid:

    1. - matrix:
    2. generators:
    3. - list: # (...)
    4. git: # (...)
    • While this will be accepted by Kubernetes API validation, the controller will report an error on generation. Each generator should be specified in a separate array element, as in the examples above.
  3. The Matrix generator does not currently support template overrides specified on child generators, eg this template will not be processed:

    1. - matrix:
    2. generators:
    3. - list:
    4. elements:
    5. - # (...)
    6. template: { } # Not processed
  4. Combination-type generators (matrix or merge) can only be nested once. For example, this will not work:

    1. - matrix:
    2. generators:
    3. - matrix:
    4. generators:
    5. - matrix: # This third level is invalid.
    6. generators:
    7. - list:
    8. elements:
    9. - # (...)
  5. When using parameters from one child generator inside another child generator, the child generator that consumes the parameters must come after the child generator that produces the parameters. For example, the below example would be invalid (cluster-generator must come after the git-files generator):

    1. - matrix:
    2. generators:
    3. # cluster generator, 'child' #1
    4. - clusters:
    5. selector:
    6. matchLabels:
    7. argocd.argoproj.io/secret-type: cluster
    8. kubernetes.io/environment: '{{path.basename}}' # {{path.basename}} is produced by git-files generator
    9. # git generator, 'child' #2
    10. - git:
    11. repoURL: https://github.com/argoproj/applicationset.git
    12. revision: HEAD
    13. files:
    14. - path: "examples/git-generator-files-discovery/cluster-config/**/config.json"
  6. You cannot have both child generators consuming parameters from each another. In the example below, the cluster generator is consuming the {{path.basename}} parameter produced by the git-files generator, whereas the git-files generator is consuming the {{name}} parameter produced by the cluster generator. This will result in a circular dependency, which is invalid.

    1. - matrix:
    2. generators:
    3. # cluster generator, 'child' #1
    4. - clusters:
    5. selector:
    6. matchLabels:
    7. argocd.argoproj.io/secret-type: cluster
    8. kubernetes.io/environment: '{{path.basename}}' # {{path.basename}} is produced by git-files generator
    9. # git generator, 'child' #2
    10. - git:
    11. repoURL: https://github.com/argoproj/applicationset.git
    12. revision: HEAD
    13. files:
    14. - path: "examples/git-generator-files-discovery/cluster-config/engineering/{{name}}**/config.json" # {{name}} is produced by cluster generator
  7. When using a Matrix generator nested inside another Matrix or Merge generator, Post Selectors for this nested generator’s generators will only be applied when enabled via spec.applyNestedSelectors. You may also need to enable this even if your Post Selectors are not within the nested matrix or Merge generator, but are instead a sibling of a nested Matrix or Merge generator.

    1. - matrix:
    2. generators:
    3. - matrix:
    4. generators:
    5. - list
    6. elements:
    7. - # (...)
    8. selector: { } # Only applied when applyNestedSelectors is true