Repository: argoproj-labs/applicationset
Pull request #439 (2021-12-17 21:06:44)

fix: log all validation errors
Logs are cheap. Logging all validation errors will make it easier to resolve them all.

Still only putting one error on the status to avoid using too much space.


Patch:
diff --git a/.github/workflows/ci-build.yaml b/.github/workflows/ci-build.yaml
index b4722ec..d4cfe1a 100644
--- a/.github/workflows/ci-build.yaml
+++ b/.github/workflows/ci-build.yaml
@@ -178,7 +178,7 @@ jobs:
           # This version is verified to match a version consistent with the kustomize install yaml,
           # by 'hack/verify-argo-cd-versions.sh'.
           # BEGIN-ARGO-CD-VERSION
-          ref: v2.1.0
+          ref: v2.2.0
           # END-ARGO-CD-VERSION
       - name: Setup Golang
         uses: actions/setup-go@v2
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a7286be..44d1e4f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,236 @@
 # Changelog
 
+# v0.3.0
+
+I am happy to announce the latest release of the Argo CD ApplicationSet controller, v0.3.0. Many new features were contributed as part of this release, including two new generators, improved error reporting and handling, support for webhook-based refresh trigger, plus doc updates, usability improvements, stability fixes, and more. 
+
+You can learn more about this release from the [ApplicationSet documentation](https://argocd-applicationset.readthedocs.io), or check out the project repository [and learn how you can contribute](https://github.com/argoproj-labs/applicationset/).
+
+## Contributors
+
+Thanks to all the folks who have contributed to the ApplicationSet controller since our last release. 
+- Shunya Murata (@shmurata)
+- Michael Crenshaw (@crenshaw-dev)
+- Jonathan West (@jgwest)
+- Ishita Sequeira (@ishitasequeira)
+- Chetan Banavikalmutt (@chetan-rns)
+- Alexander Matyushentsev (@alexmt)
+- Shiv Jha-Mathur (@shivjm)
+- Subhash Chandra (@TMaYaD)
+- William Tam (@wtam2018)
+- Benoit Gaillard (@benoitg31) 
+- Michal Barecki (@mbarecki)
+- Guillaume Dupin (@yogeek)
+- Krzysztof Dąbrowski (@krzysdabro)
+- Olve S. Hansen (@olvesh)
+- Dewan Ishtiaque Ahmed (@dewan-ahmed)
+- Diego Pomares (@DiegoPomares)
+
+Want to join us for our next release? Check out the project repository (https://github.com/argoproj-labs/applicationset) or visit us on #argo-cd-appset on Slack (https://argoproj.github.io/community/join-slack/).
+
+## New in this release
+
+### New generator: Pull Request generator
+
+With ApplicationSet v0.3.0, a new Pull Request generator has been contributed which uses the API of an SCMaaS provider (e.g. GitHub) to automatically discover open pull requests within an repository. This fits well with users that wish to construct a test environment based on an open pull request.
+
+In this example, we will create an Argo CD `Application` resource for each open pull request:
+```yaml
+apiVersion: argoproj.io/v1alpha1
+kind: ApplicationSet
+metadata:
+  name: myapps
+spec:
+  generators:
+  - pullRequest:
+      github:
+        # The GitHub organization or user.
+        owner: myorg
+        # The Github repository
+        repo: myrepository
+        # For GitHub Enterprise (optional)
+        api: https://git.example.com/
+        # Reference to a Secret containing an access token. (optional)
+        tokenRef:
+          secretName: github-token
+          key: token
+        # Labels is used to filter the PRs that you want to target. (optional)
+        labels:
+        - preview
+  template:
+  # (template the Application using PR generator params)...
+```  
+
+To learn more, check out the [Pull Request generator documentation](https://argocd-applicationset.readthedocs.io/en/master/Generators-Pull-Request/) for details. Contributed by [@shmurata](https://github.com/argoproj-labs/applicationset/pull/366/).
+
+### New generator: Merge generator
+
+Also new in this release is the Merge generator, which is useful when you want to selectively override the parameters generated by one generator, with those generated by another.
+
+In this example, we first gather the list of clusters from Argo CD, then we 'patch' only those clusters with label `use-kakfa: false`, and finally we enable redis on a specfic cluster:
+```yaml
+apiVersion: argoproj.io/v1alpha1
+kind: ApplicationSet
+metadata:
+  name: cluster-git
+spec:
+  generators:
+    # merge 'parent' generator
+    - merge:
+        mergeKeys:
+          - server
+        generators:
+          # Generate parameters for all Argo CD clusters
+          - clusters:
+              values:
+                kafka: 'true'
+                redis: 'false'
+          # For clusters with a specific label, enable Kafka.
+          - clusters:
+              selector:
+                matchLabels:
+                  use-kafka: 'false'
+              values:
+                kafka: 'false'
+          # For a specific cluster, enable Redis.
+          - list:
+              elements: 
+                - server: https://2.4.6.8
+                  values.redis: 'true'
+```
+
+See the [Merge generator documentation](https://argocd-applicationset.readthedocs.io/en/master/Generators-Merge/) for a full example, and for details on generator behaviour. Contributed by [@crenshaw-dev](https://github.com/argoproj-labs/applicationset/pull/404).
+
+### Report error conditions/status for ApplicationSet CR
+   
+When the user-provided generator/template produce invalid Argo CD Applications, the `ApplicationSet` resource's status field will now report errors (or the lack thereof). Here is an example of the new status conditions:
+```yaml
+apiVersion: argoproj.io/v1alpha1
+kind: ApplicationSet
+metadata:
+  name: myapps
+spec:
+  generators: # (...)
+  template: # (...)
+status:
+  conditions:
+  - lastTransitionTime: "2021-11-23T05:47:08Z"
+    type: ErrorOccurred
+    status: "False"
+    reason: ApplicationSetUpToDate
+    message: Successfully generated parameters for all Applications
+  - lastTransitionTime: "2021-11-23T05:47:08Z"
+    type: ParametersGenerated
+    message: Successfully generated parameters for all Applications
+    reason: ParametersGenerated
+    status: "True"
+  - lastTransitionTime: "2021-11-23T05:47:08Z"
+    type: ResourcesUpToDate
+    status: "True"
+    reason: ApplicationSetUpToDate
+    message: ApplicationSet up to date
+```
+
+On parameter generation failure or templating failure, those errors will be reported under the appropriate conditions. Contributed by [@ishitasequeira](https://github.com/argoproj-labs/applicationset/pull/370).
+
+
+### Git Generator: Refresh ApplicationSet resource with Git generator using webhook
+
+This feature adds support for refreshing ApplicationSets via a GitHub webhook trigger. It exposes a service which listens for incoming webhook payloads, and once received triggers the ApplicationSet controller to regenerate resources. In contrast, with the previous release, the ApplicationSet controller only supported polling the Git repository used by the Git generator every 3 mins (but this is at least customizable).
+
+See the [webhook documentation](https://argocd-applicationset.readthedocs.io/en/master/Generators-Git/#webhook-configuration) for details. Contributed by [@chetan-rns](https://github.com/argoproj-labs/applicationset/pull/341).
+
+This contribution also adds general support for webhooks, which is used by the Pull Request generator webhook code, below.
+
+### Gracefully handle application validation errors
+
+This feature changes how the ApplicationSet controller handles ApplicationSets that generate invalid `Application`s. Previously, if at least one Application in the ApplicationSet was invalid, the controller would refuse to proceed further and would skip _all_ Application processing (i.e. it would 'fail fast'). Now, the controller will process *valid* Applications, and only skip *invalid* Applications (logging information about them to the console).
+
+Contributed by [@alexmt](https://github.com/argoproj-labs/applicationset/pull/372).
+
+### Pull Request generator: Support for webhooks
+
+When using a Pull Request generator, the ApplicationSet controller polls every `requeueAfterSeconds` interval (defaulting to every 30 minutes) to detect changes. To eliminate this delay from polling, the ApplicationSet webhook server can be configured to receive webhook events, which will refresh the parameters generated by the Pull Request generator, and thus the corresponding `Application` resources.
+
+More information on configuring webhooks with the Pull Request generator is available from the [Pull Request generator documentation](https://argocd-applicationset.readthedocs.io/en/master/Generators-Pull-Request/#webhook-configuration). Contributed by [@shmurata](https://github.com/argoproj-labs/applicationset/pull/417).
+
+### Support `-logformat=json` as parameter to applicationset-controller
+
+This feature adds a new `--logformat=json` parameter to the applicationset-controller, which switches the logging output of the ApplicationSet controller to JSON. Contributed by [@shivjm](https://github.com/argoproj-labs/applicationset/pull/373).
+
+### SCM Generator: Provide SHA for latest commit on a branch in variables (#307)
+
+This feature adds SHA to the list of parameters exposed by the SCM Generator, with the SHA parameter representing the latest commit. Contributed by [@TMaYaD](https://github.com/argoproj-labs/applicationset/pull/307).
+
+### Improve Git files generator performance (#355)
+
+The Git files generator was consuming too much time (and driving up Git requests) due to inadvertently executing 'git fetch/git checkout' for each discovered file within the repository. With ApplicationSet v0.3.0, that has improved such that we will now issue a Git checkout/fetch repo once per refresh. Contributed by [@alexmt](https://github.com/argoproj-labs/applicationset/pull/355).
+
+### Fixes, test fixes, infrastructure improvements, and documentation updates
+
+#### Fixes
+- Fix: new variable for the normalized version of name field ([#390](https://github.com/argoproj-labs/applicationset/pull/390), contributed by @chetan-rns)
+- Fixes GitLab RepoHasPath error handling ([#423](https://github.com/argoproj-labs/applicationset/pull/423), contributed by @benoitg31)
+
+
+#### Test/infrastructure improvements:
+- Investigate Argo CD deletion failure messages when running ApplicationSet E2E tests in on-cluster configuration ([#392](https://github.com/argoproj-labs/applicationset/pull/392), contributed by @jgwest)
+- Update master branch VERSION file and metadata, and pull up release changes from 0.2.0 ([#343](https://github.com/argoproj-labs/applicationset/pull/343), contributed by @jgwest)
+- Skip E2E tests that require GitHub token, if not specified ([#380](https://github.com/argoproj-labs/applicationset/pull/380), contributed by @jgwest)
+- API rate limit error in image publish action ([#368](https://github.com/argoproj-labs/applicationset/pull/368), contributed by @jgwest)
+- Disable SCM Provider Unit tests on PRs ([#337](https://github.com/argoproj-labs/applicationset/pull/337), contributed by @jgwest)
+- Fix lint-docs ([#411](https://github.com/argoproj-labs/applicationset/pull/411), contributed by @crenshaw-dev)
+- Fix indentation in example ([#360](https://github.com/argoproj-labs/applicationset/pull/360), contributed by @DiegoPomares)
+- Adding required 'version' field for Helm Charts ([#332](https://github.com/argoproj-labs/applicationset/pull/332), contributed by @dewan-ahmed)
+- Adopt latest Argo CD dependencies, in preparation for next release ([#410](https://github.com/argoproj-labs/applicationset/pull/410), contributed by @jgwest)
+
+
+#### Doc updates
+- Update release process docs and include release checklist in docs ([#365](https://github.com/argoproj-labs/applicationset/pull/365), contributed by @jgwest)
+- Fix includeSubgroups reference name ([#357](https://github.com/argoproj-labs/applicationset/pull/357), contributed by @yogeek)
+- Add missing brace ([#349](https://github.com/argoproj-labs/applicationset/pull/349), contributed by @krzysdabro)
+- Fix Git Generator Files path example in docs ([#408](https://github.com/argoproj-labs/applicationset/pull/408), contributed by @mbarecki)
+- Corrected wrong info about path and path.basename ([#412](https://github.com/argoproj-labs/applicationset/pull/412), contributed by @olvesh)
+
+
+
+## Upgrade Notes
+
+When moving from ApplicationSet v0.1.0/v0.2.0, to v0.3.0, there are two behaviour changes to be aware of.
+
+#### Cluster generator: `{{name}}` parameter value will no longer be normalized, but existing normalization behaviour is preserved in a new `{{nameNormalized}}` parameter
+
+The Cluster generator `{{name}}` parameter has now reverted to its original behaviour: the cluster name within Argo CD will no longer be [normalized](https://github.com/argoproj-labs/applicationset/blob/11f1fe893b019c9a530865fa83ee78b16af2c090/pkg/generators/cluster.go#L168). The `{{name}}` parameter generated by the Cluster generator within the ApplicationSet will now be passed unmodified to the ApplicationSet template. 
+
+A new parameter, `{{nameNormalized}}` has been introduced which preserves the 0.2.0 behaviour. This allows you to choose which behaviour you wish to use in your ApplicationSet, based on the context in which it is used: either using the parameter as defined, or in a normalized form (which allows it to be used in the `name` field of an `Application` resource.)
+
+If your Argo CD cluster names are already valid, no change is required. Otherwise, to preserve the v0.2.0 behaviour of your ApplicationSet, replace `{{name}}` with `{{nameNormalized}}` within your ApplicationSet template. 
+
+More information about this change is [available from the issue](https://github.com/argoproj-labs/applicationset/pull/390).
+
+
+#### If an ApplicationSet contains an invalid generated Application, the valid generated Applications will still be processed
+
+The responsibility of the ApplicationSet controller is to convert an `ApplicationSet` resource into one or more `Application` resources. However, with the previous releases, if at least one of the generated `Application` resources was invalid (e.g. it failed the internal validation logic), none of the generated Applications would be processed (they would not be neither created nor modified).
+
+With the latest ApplicationSet release, if a generator generates invalid Applications, those invalid generated Applications will still be skipped, **but** the valid generated Applications will now be processed (created/modified).
+
+Thus no `ApplicationSet` resource changes are required by this new behaviour, but it is worth keeping in mind that your ApplicationSets which were previously blocked by a failing Application may no longer be blocked. This change might cause valid Applications to now be created/modified, whereas previously they were prevented from being processed.
+
+More information about this change is [available from the issue](https://github.com/argoproj-labs/applicationset/pull/372).
+
+
+## Installation
+
+The ApplicationSet controller must be installed into the same namespace as the Argo CD it is targeting:
+```
+kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/applicationset/v0.3.0/manifests/install.yaml
+```
+
+Once installed, the ApplicationSet controller requires no additional setup. You can learn more about ApplicationSet controller installation from the [Getting Started](https://argocd-applicationset.readthedocs.io/en/v0.3.0/Getting-Started/) page.
+
+
+
 # v0.2.0
 
 I am happy to announce the second release of the Argo CD ApplicationSet controller, v0.2.0. Many new features were contributed as part of this release, including support for combining generator parameters, support for building Argo CD Applications based on GitHub/GitLab organizations, and support for using custom resources to select clusters, plus oft requested improvements to existing generators, and of course doc updates, usability improvements, stability fixes, and more. 
diff --git a/VERSION b/VERSION
index 0d91a54..1d0ba9e 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-0.3.0
+0.4.0
diff --git a/docs/Generators-Cluster.md b/docs/Generators-Cluster.md
index da89e68..98bb828 100644
--- a/docs/Generators-Cluster.md
+++ b/docs/Generators-Cluster.md
@@ -2,20 +2,25 @@
 
 In Argo CD, managed clusters [are stored within Secrets](https://argo-cd.readthedocs.io/en/stable/operator-manual/declarative-setup/#clusters) in the Argo CD namespace. The ApplicationSet controller uses those same Secrets to generate parameters to identify and target available clusters.
 
-For each cluster registered with Argo CD, the Cluster generator produces parameters based on the list of items found within the cluster secret. 
+For each cluster registered with Argo CD, the Cluster generator produces parameters based on the list of items found within the cluster secret.
 
 It automatically provides the following parameter values to the Application template for each cluster:
 
 - `name`
+- `nameNormalized` *('name' but normalized to contain only lowercase alphanumeric characters, '-' or '.')*
 - `server`
 - `metadata.labels.<key>` *(for each label in the Secret)*
 - `metadata.annotations.<key>` *(for each annotation in the Secret)*
 
+!!! note
+    Use the `nameNormalized` parameter if your cluster name contains characters (such as underscores) that are not valid for Kubernetes resource names. This prevents rendering invalid Kubernetes resources with names like `my_cluster-app1`, and instead would convert them to `my-cluster-app1`.
+
+
 Within [Argo CD cluster Secrets](https://argo-cd.readthedocs.io/en/stable/operator-manual/declarative-setup/#clusters) are data fields describing the cluster:
 ```yaml
 kind: Secret
 data:
-  # Within Kubernetes these fields are actually encoded in Base64; they are decoded here for convenience. 
+  # Within Kubernetes these fields are actually encoded in Base64; they are decoded here for convenience.
   # (They are likewise decoded when passed as parameters by the Cluster generator)
   config: "{'tlsClientConfig':{'insecure':false}}"
   name: "in-cluster2"
@@ -105,7 +110,7 @@ However, if you do wish to target both local and non-local clusters, while also
 
 1. Within the Argo CD web UI, select *Settings*, then *Clusters*.
 2. Select your local cluster, usually named `in-cluster`.
-3. Click the *Edit* button, and change the the *NAME* of the cluster to another value, for example `in-cluster-local`. Any other value here is fine. 
+3. Click the *Edit* button, and change the the *NAME* of the cluster to another value, for example `in-cluster-local`. Any other value here is fine.
 4. Leave all other fields unchanged.
 5. Click *Save*.
 
@@ -124,13 +129,13 @@ spec:
         matchLabels:
           type: 'staging'
       # A key-value map for arbitrary parameters
-      values: 
+      values:
         revision: HEAD # staging clusters use HEAD branch
   - clusters:
       selector:
         matchLabels:
           type: 'production'
-      values: 
+      values:
         # production uses a different revision value, for 'stable' branch
         revision: stable
   template:
@@ -145,10 +150,10 @@ spec:
         path: guestbook
       destination:
         server: '{{server}}'
-        namespace: guestbook        
+        namespace: guestbook
 ```
 
-In this example the `revision` value from the `generators.clusters` fields is passed into the template as `values.revision`, containing either `HEAD` or `stable` (based on which generator generated the set of parameters). 
+In this example the `revision` value from the `generators.clusters` fields is passed into the template as `values.revision`, containing either `HEAD` or `stable` (based on which generator generated the set of parameters).
 
 !!! note
-    The `values.` prefix is always prepended to values provided via `generators.clusters.values` field. Ensure you include this prefix in the parameter name within the `template` when using it.
\ No newline at end of file
+    The `values.` prefix is always prepended to values provided via `generators.clusters.values` field. Ensure you include this prefix in the parameter name within the `template` when using it.
diff --git a/docs/Generators-Git.md b/docs/Generators-Git.md
index 0a3244c..0a929d3 100644
--- a/docs/Generators-Git.md
+++ b/docs/Generators-Git.md
@@ -38,7 +38,7 @@ spec:
       - path: examples/git-generator-directory/cluster-addons/*
   template:
     metadata:
-      name: '{{path.basename}}'
+      name: '{{path[0]}}'
     spec:
       project: default
       source:
@@ -54,7 +54,9 @@ spec:
 The generator parameters are:
 
 - `{{path}}`: The directory paths within the Git repository that match the `path` wildcard.
+- `{{path[n]}}`: The directory paths within the Git repository that match the `path` wildcard, split into array elements (`n` - array index)
 - `{{path.basename}}`: For any directory path within the Git repository that matches the `path` wildcard, the right-most path name is extracted (e.g. `/directory/directory2` would produce `directory2`).
+- `{{path.basenamenameNormalized}}`: This field is the same as `path.basename` with unsupported characters replaced with `-` (e.g. a `path` of `/directory/directory_2`, and `path.basename` of `directory_2` would produce `directory-2` here).
 
 Whenever a new Helm chart/Kustomize YAML/Application/plain subfolder is added to the Git repository, the ApplicationSet controller will detect this change and automatically deploy the resulting manifests within new `Application` resources.
 
@@ -224,7 +226,9 @@ As with other generators, clusters *must* already be defined within Argo CD, in
 In addition to the flattened key/value pairs from the configuration file, the following generator parameters are provided:
 
 - `{{path}}`: The path to the folder containing matching configuration file within the Git repository. Example: `/clusters/clusterA`, if the config file was `/clusters/clusterA/config.json`
+- `{{path[n]}}`: The path to the matching configuration file within the Git repository, split into array elements (`n` - array index). Example: `path[0]: clusters`, `path[1]: clusterA`
 - `{{path.basename}}`: Basename of the path to the folder containing the configuration file (e.g. `clusterA`, with the above example.)
+- `{{path.basenamenameNormalized}}`: This field is the same as `path.basename` with unsupported characters replaced with `-` (e.g. a `path` of `/directory/directory_2`, and `path.basename` of `directory_2` would produce `directory-2` here).
 
 ## Webhook Configuration
 
@@ -235,7 +239,7 @@ Git webhook notifications from GitHub and GitLab. The following explains how to
 !!! note
     ApplicationSet exposes the webhook server as a service of type ClusterIP. An Ingress resource needs to be created to expose this service to the webhook source.
 
-### 1. Create The WebHook In The Git Provider
+### 1. Create the webhook in the Git provider
 
 In your Git provider, navigate to the settings page where webhooks can be configured. The payload
 URL configured in the Git provider should use the `/api/webhook` endpoint of your ApplicationSet instance
@@ -247,7 +251,7 @@ arbitrary value in the secret. This value will be used when configuring the webh
 !!! note
     When creating the webhook in GitHub, the "Content type" needs to be set to "application/json". The default value "application/x-www-form-urlencoded" is not supported by the library used to handle the hooks
 
-### 2. Configure ApplicationSet With The WebHook Secret (Optional)
+### 2. Configure ApplicationSet with the webhook secret (Optional)
 
 Configuring a webhook shared secret is optional, since ApplicationSet will still refresh applications
 generated by Git generators, even with unauthenticated webhook events. This is safe to do since
diff --git a/docs/Generators-Merge.md b/docs/Generators-Merge.md
index 44edd75..cde5eaf 100644
--- a/docs/Generators-Merge.md
+++ b/docs/Generators-Merge.md
@@ -1,10 +1,10 @@
 # Merge Generator
 
-The merge generator combines parameters produced by the base (first) generator with matching parameter sets produced by subsequent generators. A _matching_ parameter set has the same values for the configured _merge keys_. _Non-matching_ parameter sets are discarded. Override precedence is bottom-to-top. The values from a matching parameter set produced by generator 3 will take precedence over the values from the corresponding parameter set produced by generator 2.
+The Merge generator combines parameters produced by the base (first) generator with matching parameter sets produced by subsequent generators. A _matching_ parameter set has the same values for the configured _merge keys_. _Non-matching_ parameter sets are discarded. Override precedence is bottom-to-top: the values from a matching parameter set produced by generator 3 will take precedence over the values from the corresponding parameter set produced by generator 2.
 
-Using a merge generator is appropriate when a subset of parameter sets require overriding.
+Using a Merge generator is appropriate when a subset of parameter sets require overriding.
 
-## Example: base cluster generator + override cluster generator + list generator 
+## Example: Base Cluster generator + override Cluster generator + List generator 
 
 As an example, imagine that we have two clusters:
 
@@ -130,7 +130,7 @@ When merged with the updated base parameters, the `values.redis` value for the p
             - # (...)
           template: { } # Not processed
 ```
-1. Combination-type generators (matrix or merge) can only be nested once. For example, this will not work:
+1. Combination-type generators (Matrix or Merge) can only be nested once. For example, this will not work:
 ```yaml
 - merge:
     generators:
diff --git a/docs/Generators-Pull-Request.md b/docs/Generators-Pull-Request.md
index a8db501..6cad00f 100644
--- a/docs/Generators-Pull-Request.md
+++ b/docs/Generators-Pull-Request.md
@@ -68,16 +68,16 @@ spec:
     # ...
   template:
     metadata:
-      name: 'myapp-{{ branch }}-{{ number }}'
+      name: 'myapp-{{branch}}-{{number}}'
     spec:
       source:
         repoURL: 'https://github.com/myorg/myrepo.git'
-        targetRevision: '{{ head_sha }}'
+        targetRevision: '{{head_sha}}'
         path: kubernetes/
         helm:
           parameters:
           - name: "image.tag"
-            value: "pull-{{ head_sha }}"
+            value: "pull-{{head_sha}}"
       project: default
       destination:
         server: https://kubernetes.default.svc
@@ -90,12 +90,12 @@ spec:
 
 ## Webhook Configuration
 
-When using a Pull Request generator, ApplicationSet polls every requeueAfterSeconds(default: 30 minutes) interval to detect changes. To eliminate this delay from polling, the ApplicationSet webhook server can be configured to receive webhook events. ApplicationSet supports PullRequest webhook notifications from GitHub.
+When using a Pull Request generator, the ApplicationSet controller polls every `requeueAfterSeconds` interval (defaulting to every 30 minutes) to detect changes. To eliminate this delay from polling, the ApplicationSet webhook server can be configured to receive webhook events, which will trigger Application generation by the Pull Request generator.
 
-The configuration is almost the same as the one described in the Git Generator, but there is one difference if you want to use the Pull Request Generator as well, so if you want to use it, additionally configure the following settings.
+The configuration is almost the same as the one described [in the Git generator](Generators-Git.md), but there is one difference: if you want to use the Pull Request Generator as well, additionally configure the following settings.
 
-In section 1, add an event so that a webhook request will be sent when a pull request is created, closed, or label changed.
-Select `Let me select individual events.` and enable the checkbox for `Pull requests`.
+In section 1, _"Create the webhook in the Git provider"_, add an event so that a webhook request will be sent when a pull request is created, closed, or label changed.
+Select `Let me select individual events` and enable the checkbox for `Pull requests`.
 
 ![Add Webhook](./assets/webhook-config-pull-request.png "Add Webhook Pull Request")
 
@@ -108,4 +108,4 @@ The Pull Request Generator will requeue when the next action occurs.
 - `unlabeled`
 - `synchronized`
 
-For more information about each event, please refer to the official documentation at github.com.
+For more information about each event, please refer to the [official documentation](https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads).
diff --git a/docs/Generators-SCM-Provider.md b/docs/Generators-SCM-Provider.md
index 45899a0..372db1c 100644
--- a/docs/Generators-SCM-Provider.md
+++ b/docs/Generators-SCM-Provider.md
@@ -2,7 +2,6 @@
 
 The SCM Provider generator uses the API of an SCMaaS provider (eg GitHub) to automatically discover repositories within an organization. This fits well with GitOps layout patterns that split microservices across many repositories.
 
-
 ```yaml
 apiVersion: argoproj.io/v1alpha1
 kind: ApplicationSet
@@ -126,7 +125,7 @@ spec:
 
 ## Template
 
-As with all generators, several keys are available for replacement in the generated application.
+As with all generators, several parameters are generated for use within the `ApplicationSet` resource template.
 
 ```yaml
 apiVersion: argoproj.io/v1alpha1
@@ -155,3 +154,5 @@ spec:
 * `repository`: The name of the repository.
 * `url`: The clone URL for the repository.
 * `branch`: The default branch of the repository.
+* `sha`: The Git commit SHA for the branch
+* `labels`: A comma-separated list of repository labels
\ No newline at end of file
diff --git a/docs/Generators.md b/docs/Generators.md
index 95a5979..12e6440 100644
--- a/docs/Generators.md
+++ b/docs/Generators.md
@@ -12,7 +12,7 @@ As of this writing there are seven generators:
 - [Matrix generator](Generators-Matrix.md): The Matrix generator may be used to combine the generated parameters of two separate generators.
 - [Merge generator](Generators-Merge.md): The Merge generator may be used to merge the generated parameters of two or more generators. Additional generators can override the values of the base generator.
 - [SCM Provider generator](Generators-SCM-Provider.md): The SCM Provider generator uses the API of an SCM provider (eg GitHub) to automatically discover repositories within an organization.
-- [Cluster Decision Resource generator](Generators-Cluster-Decision-Resource.md): The Cluster Decision Resource generator is used to interface with Kubernetes custom resources that use custom resource-specific logic to decide which set of Argo CD clusters to deploy to.
 - [Pull Request generator](Generators-Pull-Request.md): The Pull Request generator uses the API of an SCMaaS provider (eg GitHub) to automatically discover open pull requests within an repository.
+- [Cluster Decision Resource generator](Generators-Cluster-Decision-Resource.md): The Cluster Decision Resource generator is used to interface with Kubernetes custom resources that use custom resource-specific logic to decide which set of Argo CD clusters to deploy to.
 
 If you are new to generators, begin with the **List** and **Cluster** generators. For more advanced use cases, see the documentation for the remaining generators above.
diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md
index c05ac0f..b5c6b2e 100644
--- a/docs/Getting-Started.md
+++ b/docs/Getting-Started.md
@@ -18,7 +18,7 @@ The ApplicationSet controller *must* be installed into the same namespace as the
 Presuming that Argo CD is installed into the `argocd` namespace, run the following command:
 
 ```bash
-kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/applicationset/master/manifests/install.yaml
+kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/applicationset/v0.3.0/manifests/install.yaml
 ```
 
 Once installed, the ApplicationSet controller requires no additional setup.
@@ -38,7 +38,7 @@ You may instead install both the ApplicationSet controller and the latest stable
 
 ```bash
 kubectl create namespace argocd
-kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/applicationset/master/manifests/install-with-argo-cd.yaml
+kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/applicationset/v0.3.0/manifests/install-with-argo-cd.yaml
 ```
 
 Once installed, follow the [Argo CD Getting Started](https://argo-cd.readthedocs.io/en/stable/getting_started/) to access Argo CD and log-in to the Web UI.
@@ -71,13 +71,18 @@ To extend or customize the ApplicationSet controller installation, [Kustomize](h
 
 ## Upgrading to a Newer Release
 
-To upgrade from an older release (eg 0.1.0) to a newer release (eg 0.2.0), you only need to `kubectl apply` the `install.yaml` for the new release, as described under *Installation* above.
+To upgrade from an older release (eg 0.1.0, 0.2.0) to a newer release (eg 0.3.0), you only need to `kubectl apply` the `install.yaml` for the new release, as described under *Installation* above.
 
-There are no manual upgrade steps required between any release of ApplicationSet controller, including 0.1.0 and 0.2.0, as of this writing.
+There are no manual upgrade steps required between any release of ApplicationSet controller, (including 0.1.0, 0.2.0, and 0.3.0) as of this writing, however, see the behaviour changes in ApplicationSet controller v0.3.0, below.
 
 !!! note 
     If you installed using the combined 'ApplicationSet and Argo CD' bundle, you may wish to consult the [Argo CD release upgrade docs](https://argo-cd.readthedocs.io/en/stable/operator-manual/upgrading/overview/) as well, to familiarize yourself with Argo CD upgrades, and to confirm if there is anything on the Argo CD side you need to be aware of.
 
+### Behaviour changes in ApplicationSet controller v0.3.0
+
+There are no breaking changes, however, a couple of behaviours have changed from v0.2.0 to v0.3.0. See the [v0.3.0 upgrade page](upgrading/v0.2.0-to-v0.3.0.md) for details.
+
+
 ### Optional: Additional Post-Upgrade Safeguards
 
 See the [Controlling Resource Modification](Controlling-Resource-Modification.md) page for information on additional parameters you may wish to add to the ApplicationSet `install.yaml`, to provide extra security against any initial, unexpected post-upgrade behaviour. 
@@ -95,4 +100,4 @@ However, as mentioned above, these steps are not strictly necessary: upgrading t
 
 ## Next Steps
 
-Once your ApplicationSet controller is up and running, proceed to [Use Cases](Use-Cases.md) to learn more about the supported scenarios, or proceed directly to [Generators](Generators.md) to see example `ApplicationSet` resources. 
\ No newline at end of file
+Once your ApplicationSet controller is up and running, proceed to [Use Cases](Use-Cases.md) to learn more about the supported scenarios, or proceed directly to [Generators](Generators.md) to see example `ApplicationSet` resources. 
diff --git a/docs/Release-Checklist-Template.md b/docs/Release-Checklist-Template.md
index 9e98ec2..3f6fe2f 100644
--- a/docs/Release-Checklist-Template.md
+++ b/docs/Release-Checklist-Template.md
@@ -64,6 +64,7 @@ Once complete, this is a set of checks to perform once you have a commit, tags,
 - [ ] Ensure that https://argocd-applicationset.readthedocs.io/ points to the new version, and contains the correct content 
     - **Note**: this may require a manually triggered build at rtd.io. See release process for details.
 - [ ] Ensure that the Getting Start page at rtd.io includes references to `kubectl apply` which points to the correct applicationset controller version
+    - Ensure that *both* Section A and Section B (of Install) are updated from pointing to `master`, to pointing to `v(version)`. 
     - On [Stable](https://argocd-applicationset.readthedocs.io/en/stable/Getting-Started/)
     - On the version-specific page: `https://argocd-applicationset.readthedocs.io/en/(version)/Getting-Started/`
         - Example: `https://argocd-applicationset.readthedocs.io/en/v0.2.0/Getting-Started/`
diff --git a/docs/Releasing.md b/docs/Releasing.md
index 246e929..11d0f2c 100644
--- a/docs/Releasing.md
+++ b/docs/Releasing.md
@@ -60,7 +60,7 @@ These new URLs won't (yet) work, because we haven't tagged the release yet, but
 From the repository root:
 ```sh
 CONTAINER_REGISTRY=quay.io IMAGE_NAMESPACE=argoproj hack/release.sh (VERSION)
-# Example: CONTAINER_REGISTRY=quay.io  IMAGE_NAMESPACE=argocdapplicationset hack/release.sh 0.1.0
+# Example: CONTAINER_REGISTRY=quay.io  IMAGE_NAMESPACE=argoproj hack/release.sh 0.1.0
 ```
 
 The release script will (as of this writing):
diff --git a/docs/upgrading/v0.2.0-to-v0.3.0.md b/docs/upgrading/v0.2.0-to-v0.3.0.md
new file mode 100644
index 0000000..6a425bd
--- /dev/null
+++ b/docs/upgrading/v0.2.0-to-v0.3.0.md
@@ -0,0 +1,21 @@
+# Upgrading from ApplicationSet controller v0.2.0 to v0.3.0.
+
+When moving from ApplicationSet v0.2.0 to v0.3.0, there are a couple of behaviour changes to be aware of.
+
+## Cluster generator: `{{name}}` parameter value will no longer be normalized, but existing behaviour is preserved in a new `{{nameNormalized}}` parameter
+
+The Cluster generator `{{name}}` parameter has now reverted to it's original behaviour: the cluster name within Argo CD will no longer be [normalized](https://github.com/argoproj-labs/applicationset/blob/11f1fe893b019c9a530865fa83ee78b16af2c090/pkg/generators/cluster.go#L168). The `{{name}}` parameter generated by the Cluster generator within the ApplicationSet will now be passed unmodified to the ApplicationSet template. 
+
+A new parameter, `{{nameNormalized}}` has been introduced which preserves the 0.2.0 behaviour. This allows you to choose which behaviour you wish to use in your ApplicationSet, based on the context in which it is used: either using the parameter as defined, or in a normalized form (which allows it to be used in the `name` field of a Application resource.)
+
+If your Argo CD cluster names are already valid, no change is required. However, to preserve the v0.2.0 behaviour of your ApplicationSet, replace `{{name}}` with `{{nameNormalized}}` within your ApplicationSet template. 
+
+More information about this change is [available from the issue](https://github.com/argoproj-labs/applicationset/pull/390).
+
+## If an ApplicationSet generates an invalid Application, the valid generated Applications will now be processed
+
+The responsibility of the ApplicationSet controller is to convert an `ApplicationSet` resource into one or more `Application` resources. However, with the previous releases, if at least one of the generated `Application` resources was invalid (eg it failed the internal validation logic), none of the generated Applications would be processed (they would not be neither created nor modified).
+
+With the latest ApplicationSet release, if a generator generates invalid Applications, those invalid generated Applications will still be skipped, **but** the valid generated Applications will now be processed (created/modified).
+
+Thus no `ApplicationSet` resource changes are required by this upgrade, but it is worth keeping in mind that your ApplicationSets which were previously blocked by a failing Application may no longer be blocked. This change might cause valid Applications to now be created/modified, whereas previously they were prevented from being processed.
diff --git a/go.mod b/go.mod
index 357778b..855fb00 100644
--- a/go.mod
+++ b/go.mod
@@ -3,9 +3,9 @@ module github.com/argoproj-labs/applicationset
 go 1.16
 
 require (
-	github.com/argoproj/argo-cd/v2 v2.2.0-rc1
-	github.com/argoproj/gitops-engine v0.4.1-0.20211103220110-c7bab2eeca22
-	github.com/argoproj/pkg v0.9.1
+	github.com/argoproj/argo-cd/v2 v2.2.0
+	github.com/argoproj/gitops-engine v0.5.1
+	github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0
 	github.com/go-logr/logr v0.4.0
 	github.com/google/go-github/v35 v35.0.0
 	github.com/imdario/mergo v0.3.12
diff --git a/go.sum b/go.sum
index 77b4a08..6a7edd2 100644
--- a/go.sum
+++ b/go.sum
@@ -94,12 +94,12 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
-github.com/argoproj/argo-cd/v2 v2.2.0-rc1 h1:Z/MRCgtPj25r0A70mZsd5mtzvsjsIYqSi3yIXWjITzY=
-github.com/argoproj/argo-cd/v2 v2.2.0-rc1/go.mod h1:WQqRr6PykO7CL/5mAyiVD6MgUbrFHd/fq9TKCWJ5TPE=
-github.com/argoproj/gitops-engine v0.4.1-0.20211103220110-c7bab2eeca22 h1:2i8r5XiuBDf7uYP4R5ZuSNsPebPw15g/LePNIX7YYi8=
-github.com/argoproj/gitops-engine v0.4.1-0.20211103220110-c7bab2eeca22/go.mod h1:K2RYpGXh11VdFwDksS23SyFTOJaPcsF+MVJ/FHlqEOE=
-github.com/argoproj/pkg v0.9.1 h1:osfOS3QkzfRf+W43lbCZb0o0bzrBweQhL+U3rgEg+5M=
-github.com/argoproj/pkg v0.9.1/go.mod h1:ra+bQPmbVAoEL+gYSKesuigt4m49i3Qa3mE/xQcjCiA=
+github.com/argoproj/argo-cd/v2 v2.2.0 h1:BW2MQ8kVSvgoAyr34zShwx6i+ZIPMfFW1OAj4+HEbSk=
+github.com/argoproj/argo-cd/v2 v2.2.0/go.mod h1:kVYB3AWGw5L+auLzOHJI1Q7xp5Bo+iA65ph07PxndZY=
+github.com/argoproj/gitops-engine v0.5.1 h1:kIPvoYFRvDA+3Tz2aEjwRBVGIgcuZ+HIzuHcRmvxo54=
+github.com/argoproj/gitops-engine v0.5.1/go.mod h1:K2RYpGXh11VdFwDksS23SyFTOJaPcsF+MVJ/FHlqEOE=
+github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0 h1:Cfp7rO/HpVxnwlRqJe0jHiBbZ77ZgXhB6HWlYD02Xdc=
+github.com/argoproj/pkg v0.11.1-0.20211203175135-36c59d8fafe0/go.mod h1:ra+bQPmbVAoEL+gYSKesuigt4m49i3Qa3mE/xQcjCiA=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
@@ -131,8 +131,8 @@ github.com/bombsimon/logrusr v1.0.0 h1:CTCkURYAt5nhCCnKH9eLShYayj2/8Kn/4Qg3QfiU+
 github.com/bombsimon/logrusr v1.0.0/go.mod h1:Jq0nHtvxabKE5EMwAAdgTaz7dfWE8C4i11NOltxGQpc=
 github.com/bradleyfalzon/ghinstallation/v2 v2.0.2 h1:VdhctVU4Kag+Yo5iuvEvFx4HNpLEI99Cm41UnE7y1WE=
 github.com/bradleyfalzon/ghinstallation/v2 v2.0.2/go.mod h1:GhRUp70E+QFvNemlFd4unyHZ8ryBiMQkJm6KgdilpUo=
-github.com/casbin/casbin v1.9.1/go.mod h1:z8uPsfBJGUsnkagrt3G8QvjgTKFMBJ32UP8HpZllfog=
 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
+github.com/casbin/casbin/v2 v2.39.1/go.mod h1:sEL80qBYTbd+BPeL4iyvwYzFT3qwLaESq5aFKVLbLfA=
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
 github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
diff --git a/hack/verify-argo-cd-versions.sh b/hack/verify-argo-cd-versions.sh
index d6dbacd..0a41ac9 100755
--- a/hack/verify-argo-cd-versions.sh
+++ b/hack/verify-argo-cd-versions.sh
@@ -3,7 +3,7 @@
 # To adopt a new version of Argo CD:
 # 1) Update this value to the GitHub tag of the target 'argoproj/argo-cd' release (example: 'v1.8.4'). 
 # 2) Fix the errors that are reported below (by editing the version string in the file reported in the error)
-TARGET_ARGO_CD_VERSION=v2.1.0
+TARGET_ARGO_CD_VERSION=v2.2.0
 
 # Extract the Argo CD repository string from ci-build.yaml, which SHOULD contain the target Argo CD version
 VERSION_FROM_CI_BUILD=$( awk '/BEGIN-ARGO-CD-VERSION/,/END-ARGO-CD-VERSION/' .github/workflows/ci-build.yaml )
diff --git a/manifests/install-with-argo-cd.yaml b/manifests/install-with-argo-cd.yaml
index bb95084..4de9543 100644
--- a/manifests/install-with-argo-cd.yaml
+++ b/manifests/install-with-argo-cd.yaml
@@ -217,6 +217,9 @@ spec:
                                   type: string
                               type: object
                             type: array
+                          passCredentials:
+                            description: PassCredentials pass credentials to all domains (Helm's --pass-credentials)
+                            type: boolean
                           releaseName:
                             description: ReleaseName is the Helm release name to use. If omitted it will use the application name
                             type: string
@@ -502,6 +505,9 @@ spec:
                               type: string
                           type: object
                         type: array
+                      passCredentials:
+                        description: PassCredentials pass credentials to all domains (Helm's --pass-credentials)
+                        type: boolean
                       releaseName:
                         description: ReleaseName is the Helm release name to use. If omitted it will use the application name
                         type: string
@@ -803,6 +809,9 @@ spec:
                                     type: string
                                 type: object
                               type: array
+                            passCredentials:
+                              description: PassCredentials pass credentials to all domains (Helm's --pass-credentials)
+                              type: boolean
                             releaseName:
                               description: ReleaseName is the Helm release name to use. If omitted it will use the application name
                               type: string
@@ -1105,6 +1114,9 @@ spec:
                                           type: string
                                       type: object
                                     type: array
+                                  passCredentials:
+                                    description: PassCredentials pass credentials to all domains (Helm's --pass-credentials)
+                                    type: boolean
                                   releaseName:
                                     description: ReleaseName is the Helm release name to use. If omitted it will use the application name
                                     type: string
@@ -1389,6 +1401,9 @@ spec:
                                       type: string
                                   type: object
                                 type: array
+                              passCredentials:
+                                description: PassCredentials pass credentials to all domains (Helm's --pass-credentials)
+                                type: boolean
                               releaseName:
                                 description: ReleaseName is the Helm release name to use. If omitted it will use the application name
                                 type: string
@@ -1670,6 +1685,9 @@ spec:
                                       type: string
                                   type: object
                                 type: array
+                              passCredentials:
+                                description: PassCredentials pass credentials to all domains (Helm's --pass-credentials)
+                                type: boolean
                               releaseName:
                                 description: ReleaseName is the Helm release name to use. If omitted it will use the application name
                                 type: string
@@ -15619,6 +15637,9 @@ spec:
                     schedule:
                       description: Schedule is the time the window will begin, specified in cron format
                       type: string
+                    timeZone:
+                      description: TimeZone of the sync that will be applied to the schedule
+                      type: string
                   type: object
                 type: array
             type: object
@@ -16085,7 +16106,7 @@ metadata:
 ---
 apiVersion: v1
 data:
-  ssh_known_hosts: |
+  ssh_known_hosts: |-
     bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAubiN81eDcafrgMeLzaFPsw2kNvEcqTKl/VqLat/MaB33pZy0y3rJZtnqwR2qOOvbwKZYKiEO1O6VqNEBxKvJJelCq0dTXWT5pbO2gDXC6h6QDXCaHo6pOHGPUy+YBaGQRGuSusMEASYiWunYN0vCAI8QaXnWMXNMdFP3jHAJH0eDsoiGnLPBlBp4TNm6rYI74nMzgz3B9IikW4WVK+dc8KZJZWYjAuORU3jc1c/NPskD2ASinf8v3xnfXeukU0sJ5N6m5E8VLjObPEO+mN2t/FZTMZLiFqPWc/ALSqnMnnhwrNi2rbfg/rd/IpL8Le3pSBne8+seeFVBoGqzHM9yXw==
     github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
     gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=
@@ -16093,6 +16114,8 @@ data:
     gitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9
     ssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H
     vs-ssh.visualstudio.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H
+    github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=
+    github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl
 kind: ConfigMap
 metadata:
   labels:
@@ -16347,7 +16370,7 @@ spec:
       - command:
         - /shared/argocd-dex
         - rundex
-        image: ghcr.io/dexidp/dex:v2.27.0
+        image: ghcr.io/dexidp/dex:v2.30.0
         imagePullPolicy: Always
         name: dex
         ports:
@@ -16369,7 +16392,7 @@ spec:
         - -n
         - /usr/local/bin/argocd
         - /shared/argocd-dex
-        image: quay.io/argoproj/argocd:v2.1.0
+        image: quay.io/argoproj/argocd:v2.2.0
         imagePullPolicy: Always
         name: copyutil
         volumeMounts:
@@ -16546,7 +16569,13 @@ spec:
               key: reposerver.default.cache.expiration
               name: argocd-cmd-params-cm
               optional: true
-        image: quay.io/argoproj/argocd:v2.1.0
+        - name: HELM_CACHE_HOME
+          value: /helm-working-dir
+        - name: HELM_CONFIG_HOME
+          value: /helm-working-dir
+        - name: HELM_DATA_HOME
+          value: /helm-working-dir
+        image: quay.io/argoproj/argocd:v2.2.0
         imagePullPolicy: Always
         livenessProbe:
           failureThreshold: 3
@@ -16585,6 +16614,21 @@ spec:
           name: argocd-repo-server-tls
         - mountPath: /tmp
           name: tmp
+        - mountPath: /helm-working-dir
+          name: helm-working-dir
+        - mountPath: /home/argocd/cmp-server/plugins
+          name: plugins
+      initContainers:
+      - command:
+        - cp
+        - -n
+        - /usr/local/bin/argocd
+        - /var/run/argocd/argocd-cmp-server
+        image: quay.io/argoproj/argocd:v2.2.0
+        name: copyutil
+        volumeMounts:
+        - mountPath: /var/run/argocd
+          name: var-files
       volumes:
       - configMap:
           name: argocd-ssh-known-hosts-cm
@@ -16599,6 +16643,8 @@ spec:
         name: gpg-keyring
       - emptyDir: {}
         name: tmp
+      - emptyDir: {}
+        name: helm-working-dir
       - name: argocd-repo-server-tls
         secret:
           items:
@@ -16610,6 +16656,10 @@ spec:
             path: ca.crt
           optional: true
           secretName: argocd-repo-server-tls
+      - emptyDir: {}
+        name: var-files
+      - emptyDir: {}
+        name: plugins
 ---
 apiVersion: apps/v1
 kind: Deployment
@@ -16762,6 +16812,12 @@ spec:
               key: server.login.attempts.expiration
               name: argocd-cmd-params-cm
               optional: true
+        - name: ARGOCD_SERVER_STATIC_ASSETS
+          valueFrom:
+            configMapKeyRef:
+              key: server.staticassets
+              name: argocd-cmd-params-cm
+              optional: true
         - name: ARGOCD_APP_STATE_CACHE_EXPIRATION
           valueFrom:
             configMapKeyRef:
@@ -16786,7 +16842,13 @@ spec:
               key: server.default.cache.expiration
               name: argocd-cmd-params-cm
               optional: true
-        image: quay.io/argoproj/argocd:v2.1.0
+        - name: ARGOCD_MAX_COOKIE_NUMBER
+          valueFrom:
+            configMapKeyRef:
+              key: server.http.cookie.maxnumber
+              name: argocd-cmd-params-cm
+              optional: true
+        image: quay.io/argoproj/argocd:v2.2.0
         imagePullPolicy: Always
         livenessProbe:
           httpGet:
@@ -16977,7 +17039,7 @@ spec:
               key: controller.default.cache.expiration
               name: argocd-cmd-params-cm
               optional: true
-        image: quay.io/argoproj/argocd:v2.1.0
+        image: quay.io/argoproj/argocd:v2.2.0
         imagePullPolicy: Always
         livenessProbe:
           httpGet:
diff --git a/manifests/namespace-install-with-argo-cd/kustomization.yaml b/manifests/namespace-install-with-argo-cd/kustomization.yaml
index c196a86..06a5e15 100644
--- a/manifests/namespace-install-with-argo-cd/kustomization.yaml
+++ b/manifests/namespace-install-with-argo-cd/kustomization.yaml
@@ -4,6 +4,6 @@ kind: Kustomization
 namespace: argocd
 
 resources:
-  - https://raw.githubusercontent.com/argoproj/argo-cd/v2.1.0/manifests/install.yaml
+  - https://raw.githubusercontent.com/argoproj/argo-cd/v2.2.0/manifests/install.yaml
   - ../base
   - ../crds
diff --git a/pkg/controllers/applicationset_controller.go b/pkg/controllers/applicationset_controller.go
index 5be9d5a..4de3898 100644
--- a/pkg/controllers/applicationset_controller.go
+++ b/pkg/controllers/applicationset_controller.go
@@ -143,12 +143,12 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
 		var message string
 		for _, v := range validateErrors {
 			message = v.Error()
-			break
+			log.Errorf("validation error found during application validation: %s", message)
 		}
 		if len(validateErrors) > 1 {
+			// Only the last message gets added to the appset status, to keep the size reasonable.
 			message = fmt.Sprintf("%s (and %d more)", message, len(validateErrors)-1)
 		}
-		log.Errorf("validation error found during application validation: %s", message)
 		_ = r.setApplicationSetStatusCondition(ctx,
 			&applicationSetInfo,
 			argoprojiov1alpha1.ApplicationSetCondition{
diff --git a/pkg/generators/git.go b/pkg/generators/git.go
index 83211f6..f331260 100644
--- a/pkg/generators/git.go
+++ b/pkg/generators/git.go
@@ -5,6 +5,8 @@ import (
 	"fmt"
 	"path"
 	"sort"
+	"strconv"
+	"strings"
 	"time"
 
 	argoprojiov1alpha1 "github.com/argoproj-labs/applicationset/api/v1alpha1"
@@ -159,6 +161,12 @@ func (g *GitGenerator) generateParamsFromGitFile(filePath string, fileContent []
 		}
 		params["path"] = path.Dir(filePath)
 		params["path.basename"] = path.Base(params["path"])
+		params["path.basenameNormalized"] = sanitizeName(path.Base(params["path"]))
+		for k, v := range strings.Split(strings.TrimSuffix(params["path"], params["path.basename"]), "/") {
+			if len(v) > 0 {
+				params["path["+strconv.Itoa(k)+"]"] = v
+			}
+		}
 		res = append(res, params)
 	}
 
@@ -203,7 +211,12 @@ func (g *GitGenerator) generateParamsFromApps(requestedApps []string, _ *argopro
 		params := make(map[string]string, 2)
 		params["path"] = a
 		params["path.basename"] = path.Base(a)
-
+		params["path.basenameNormalized"] = sanitizeName(path.Base(a))
+		for k, v := range strings.Split(strings.TrimSuffix(params["path"], params["path.basename"]), "/") {
+			if len(v) > 0 {
+				params["path["+strconv.Itoa(k)+"]"] = v
+			}
+		}
 		res[i] = params
 	}
 


Test patch:
diff --git a/pkg/generators/git_test.go b/pkg/generators/git_test.go
index 3abd1fe..e2a3913 100644
--- a/pkg/generators/git_test.go
+++ b/pkg/generators/git_test.go
@@ -62,12 +62,14 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
 			repoApps: []string{
 				"app1",
 				"app2",
-				"p1/app3",
+				"app_3",
+				"p1/app4",
 			},
 			repoError: nil,
 			expected: []map[string]string{
-				{"path": "app1", "path.basename": "app1"},
-				{"path": "app2", "path.basename": "app2"},
+				{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1"},
+				{"path": "app2", "path.basename": "app2", "path.basenameNormalized": "app2"},
+				{"path": "app_3", "path.basename": "app_3", "path.basenameNormalized": "app-3"},
 			},
 			expectedError: nil,
 		},
@@ -82,8 +84,8 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
 			},
 			repoError: nil,
 			expected: []map[string]string{
-				{"path": "p1/app2", "path.basename": "app2"},
-				{"path": "p1/p2/app3", "path.basename": "app3"},
+				{"path": "p1/app2", "path.basename": "app2", "path[0]": "p1", "path.basenameNormalized": "app2"},
+				{"path": "p1/p2/app3", "path.basename": "app3", "path[0]": "p1", "path[1]": "p2", "path.basenameNormalized": "app3"},
 			},
 			expectedError: nil,
 		},
@@ -99,9 +101,9 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
 			},
 			repoError: nil,
 			expected: []map[string]string{
-				{"path": "app1", "path.basename": "app1"},
-				{"path": "app2", "path.basename": "app2"},
-				{"path": "p2/app3", "path.basename": "app3"},
+				{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1"},
+				{"path": "app2", "path.basename": "app2", "path.basenameNormalized": "app2"},
+				{"path": "p2/app3", "path.basename": "app3", "path[0]": "p2", "path.basenameNormalized": "app3"},
 			},
 			expectedError: nil,
 		},
@@ -117,9 +119,9 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
 			},
 			repoError: nil,
 			expected: []map[string]string{
-				{"path": "app1", "path.basename": "app1"},
-				{"path": "app2", "path.basename": "app2"},
-				{"path": "p2/app3", "path.basename": "app3"},
+				{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1"},
+				{"path": "app2", "path.basename": "app2", "path.basenameNormalized": "app2"},
+				{"path": "p2/app3", "path.basename": "app3", "path[0]": "p2", "path.basenameNormalized": "app3"},
 			},
 			expectedError: nil,
 		},
@@ -225,22 +227,26 @@ func TestGitGenerateParamsFromFiles(t *testing.T) {
 			repoPathsError: nil,
 			expected: []map[string]string{
 				{
-					"cluster.owner":        "john.doe@example.com",
-					"cluster.name":         "production",
-					"cluster.address":      "https://kubernetes.default.svc",
-					"key1":                 "val1",
-					"key2.key2_1":          "val2_1",
-					"key2.key2_2.key2_2_1": "val2_2_1",
-					"key3":                 "123",
-					"path":                 "cluster-config/production",
-					"path.basename":        "production",
+					"cluster.owner":           "john.doe@example.com",
+					"cluster.name":            "production",
+					"cluster.address":         "https://kubernetes.default.svc",
+					"key1":                    "val1",
+					"key2.key2_1":             "val2_1",
+					"key2.key2_2.key2_2_1":    "val2_2_1",
+					"key3":                    "123",
+					"path":                    "cluster-config/production",
+					"path.basename":           "production",
+					"path[0]":                 "cluster-config",
+					"path.basenameNormalized": "production",
 				},
 				{
-					"cluster.owner":   "foo.bar@example.com",
-					"cluster.name":    "staging",
-					"cluster.address": "https://kubernetes.default.svc",
-					"path":            "cluster-config/staging",
-					"path.basename":   "staging",
+					"cluster.owner":           "foo.bar@example.com",
+					"cluster.name":            "staging",
+					"cluster.address":         "https://kubernetes.default.svc",
+					"path":                    "cluster-config/staging",
+					"path.basename":           "staging",
+					"path[0]":                 "cluster-config",
+					"path.basenameNormalized": "staging",
 				},
 			},
 			expectedError: nil,
@@ -291,19 +297,23 @@ func TestGitGenerateParamsFromFiles(t *testing.T) {
 			repoPathsError: nil,
 			expected: []map[string]string{
 				{
-					"cluster.owner":     "john.doe@example.com",
-					"cluster.name":      "production",
-					"cluster.address":   "https://kubernetes.default.svc",
-					"cluster.inner.one": "two",
-					"path":              "cluster-config/production",
-					"path.basename":     "production",
+					"cluster.owner":           "john.doe@example.com",
+					"cluster.name":            "production",
+					"cluster.address":         "https://kubernetes.default.svc",
+					"cluster.inner.one":       "two",
+					"path":                    "cluster-config/production",
+					"path.basename":           "production",
+					"path[0]":                 "cluster-config",
+					"path.basenameNormalized": "production",
 				},
 				{
-					"cluster.owner":   "john.doe@example.com",
-					"cluster.name":    "staging",
-					"cluster.address": "https://kubernetes.default.svc",
-					"path":            "cluster-config/production",
-					"path.basename":   "production",
+					"cluster.owner":           "john.doe@example.com",
+					"cluster.name":            "staging",
+					"cluster.address":         "https://kubernetes.default.svc",
+					"path":                    "cluster-config/production",
+					"path.basename":           "production",
+					"path[0]":                 "cluster-config",
+					"path.basenameNormalized": "production",
 				},
 			},
 			expectedError: nil,
@@ -333,21 +343,25 @@ cluster:
 			repoPathsError: nil,
 			expected: []map[string]string{
 				{
-					"cluster.owner":        "john.doe@example.com",
-					"cluster.name":         "production",
-					"cluster.address":      "https://kubernetes.default.svc",
-					"key1":                 "val1",
-					"key2.key2_1":          "val2_1",
-					"key2.key2_2.key2_2_1": "val2_2_1",
-					"path":                 "cluster-config/production",
-					"path.basename":        "production",
+					"cluster.owner":           "john.doe@example.com",
+					"cluster.name":            "production",
+					"cluster.address":         "https://kubernetes.default.svc",
+					"key1":                    "val1",
+					"key2.key2_1":             "val2_1",
+					"key2.key2_2.key2_2_1":    "val2_2_1",
+					"path":                    "cluster-config/production",
+					"path.basename":           "production",
+					"path[0]":                 "cluster-config",
+					"path.basenameNormalized": "production",
 				},
 				{
-					"cluster.owner":   "foo.bar@example.com",
-					"cluster.name":    "staging",
-					"cluster.address": "https://kubernetes.default.svc",
-					"path":            "cluster-config/staging",
-					"path.basename":   "staging",
+					"cluster.owner":           "foo.bar@example.com",
+					"cluster.name":            "staging",
+					"cluster.address":         "https://kubernetes.default.svc",
+					"path":                    "cluster-config/staging",
+					"path.basename":           "staging",
+					"path[0]":                 "cluster-config",
+					"path.basenameNormalized": "staging",
 				},
 			},
 			expectedError: nil,
@@ -371,19 +385,23 @@ cluster:
 			repoPathsError: nil,
 			expected: []map[string]string{
 				{
-					"cluster.owner":     "john.doe@example.com",
-					"cluster.name":      "production",
-					"cluster.address":   "https://kubernetes.default.svc",
-					"cluster.inner.one": "two",
-					"path":              "cluster-config/production",
-					"path.basename":     "production",
+					"cluster.owner":           "john.doe@example.com",
+					"cluster.name":            "production",
+					"cluster.address":         "https://kubernetes.default.svc",
+					"cluster.inner.one":       "two",
+					"path":                    "cluster-config/production",
+					"path.basename":           "production",
+					"path[0]":                 "cluster-config",
+					"path.basenameNormalized": "production",
 				},
 				{
-					"cluster.owner":   "john.doe@example.com",
-					"cluster.name":    "staging",
-					"cluster.address": "https://kubernetes.default.svc",
-					"path":            "cluster-config/production",
-					"path.basename":   "production",
+					"cluster.owner":           "john.doe@example.com",
+					"cluster.name":            "staging",
+					"cluster.address":         "https://kubernetes.default.svc",
+					"path":                    "cluster-config/production",
+					"path.basename":           "production",
+					"path[0]":                 "cluster-config",
+					"path.basenameNormalized": "production",
 				},
 			},
 			expectedError: nil,
diff --git a/pkg/generators/matrix_test.go b/pkg/generators/matrix_test.go
index 6173c55..506c7bc 100644
--- a/pkg/generators/matrix_test.go
+++ b/pkg/generators/matrix_test.go
@@ -39,8 +39,8 @@ func TestMatrixGenerate(t *testing.T) {
 				},
 			},
 			expected: []map[string]string{
-				{"path": "app1", "path.basename": "app1", "cluster": "Cluster", "url": "Url"},
-				{"path": "app2", "path.basename": "app2", "cluster": "Cluster", "url": "Url"},
+				{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "cluster": "Cluster", "url": "Url"},
+				{"path": "app2", "path.basename": "app2", "path.basenameNormalized": "app2", "cluster": "Cluster", "url": "Url"},
 			},
 		},
 		{
@@ -137,12 +137,14 @@ func TestMatrixGenerate(t *testing.T) {
 				}
 				mock.On("GenerateParams", &gitGeneratorSpec, appSet).Return([]map[string]string{
 					{
-						"path":          "app1",
-						"path.basename": "app1",
+						"path":                    "app1",
+						"path.basename":           "app1",
+						"path.basenameNormalized": "app1",
 					},
 					{
-						"path":          "app2",
-						"path.basename": "app2",
+						"path":                    "app2",
+						"path.basename":           "app2",
+						"path.basenameNormalized": "app2",
 					},
 				}, nil)
 
diff --git a/test/e2e/fixture/applicationsets/actions.go b/test/e2e/fixture/applicationsets/actions.go
index d4fcf77..1c994bd 100644
--- a/test/e2e/fixture/applicationsets/actions.go
+++ b/test/e2e/fixture/applicationsets/actions.go
@@ -102,35 +102,68 @@ func (a *Actions) CreateClusterSecret(secretName string, clusterName string, clu
 
 	fixtureClient := utils.GetE2EFixtureK8sClient()
 
-	bearerToken, err := GetServiceAccountBearerToken(fixtureClient.KubeClientset, utils.ArgoCDNamespace, "argocd-applicationset-controller")
-
-	// bearerToken
-	secret := &corev1.Secret{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      secretName,
-			Namespace: utils.ArgoCDNamespace,
-			Labels: map[string]string{
-				argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
-				utils.TestingLabel:            "true",
+	var serviceAccountName string
+
+	// Look for a service account matching '*application-controller*'
+	err := wait.Poll(500*time.Millisecond, 30*time.Second, func() (bool, error) {
+
+		serviceAccountList, err := fixtureClient.KubeClientset.CoreV1().ServiceAccounts(utils.ArgoCDNamespace).List(context.Background(), metav1.ListOptions{})
+		if err != nil {
+			fmt.Println("Unable to retrieve ServiceAccount list", err)
+			return false, nil
+		}
+
+		// If 'application-controller' service account is present, use that
+		for _, sa := range serviceAccountList.Items {
+			if strings.Contains(sa.Name, "application-controller") {
+				serviceAccountName = sa.Name
+				return true, nil
+			}
+		}
+
+		// Otherwise, use 'default'
+		for _, sa := range serviceAccountList.Items {
+			if sa.Name == "default" {
+				serviceAccountName = sa.Name
+				return true, nil
+			}
+		}
+
+		return false, nil
+	})
+
+	if err == nil {
+		var bearerToken string
+		bearerToken, err = GetServiceAccountBearerToken(fixtureClient.KubeClientset, utils.ArgoCDNamespace, serviceAccountName)
+
+		// bearerToken
+		secret := &corev1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      secretName,
+				Namespace: utils.ArgoCDNamespace,
+				Labels: map[string]string{
+					argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster,
+					utils.TestingLabel:            "true",
+				},
 			},
-		},
-		Data: map[string][]byte{
-			"name":   []byte(clusterName),
-			"server": []byte(clusterServer),
-			"config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"),
-		},
-	}
+			Data: map[string][]byte{
+				"name":   []byte(clusterName),
+				"server": []byte(clusterServer),
+				"config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"),
+			},
+		}
 
-	// If the bearer token is available, use it rather than the fake username/password
-	if bearerToken != "" && err == nil {
-		secret.Data = map[string][]byte{
-			"name":   []byte(clusterName),
-			"server": []byte(clusterServer),
-			"config": []byte("{\"bearerToken\":\"" + bearerToken + "\"}"),
+		// If the bearer token is available, use it rather than the fake username/password
+		if bearerToken != "" && err == nil {
+			secret.Data = map[string][]byte{
+				"name":   []byte(clusterName),
+				"server": []byte(clusterServer),
+				"config": []byte("{\"bearerToken\":\"" + bearerToken + "\"}"),
+			}
 		}
-	}
 
-	_, err = fixtureClient.KubeClientset.CoreV1().Secrets(secret.Namespace).Create(context.Background(), secret, metav1.CreateOptions{})
+		_, err = fixtureClient.KubeClientset.CoreV1().Secrets(secret.Namespace).Create(context.Background(), secret, metav1.CreateOptions{})
+	}
 
 	a.describeAction = fmt.Sprintf("creating cluster Secret '%s'", secretName)
 	a.lastOutput, a.lastError = "", err
diff --git a/test/e2e/fixture/applicationsets/consequences.go b/test/e2e/fixture/applicationsets/consequences.go
index a530d3e..b012cde 100644
--- a/test/e2e/fixture/applicationsets/consequences.go
+++ b/test/e2e/fixture/applicationsets/consequences.go
@@ -11,7 +11,6 @@ import (
 	"github.com/argoproj/pkg/errors"
 	log "github.com/sirupsen/logrus"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 // this implements the "then" part of given/when/then
@@ -75,7 +74,7 @@ func (c *Consequences) app(name string) *argov1alpha1.Application {
 func (c *Consequences) apps() []argov1alpha1.Application {
 
 	fixtureClient := utils.GetE2EFixtureK8sClient()
-	list, err := fixtureClient.AppClientset.ArgoprojV1alpha1().Applications(utils.ArgoCDNamespace).List(context.Background(), v1.ListOptions{})
+	list, err := fixtureClient.AppClientset.ArgoprojV1alpha1().Applications(utils.ArgoCDNamespace).List(context.Background(), metav1.ListOptions{})
 	errors.CheckError(err)
 
 	if list == nil {
diff --git a/test/e2e/fixture/applicationsets/expectation.go b/test/e2e/fixture/applicationsets/expectation.go
index 20770fb..7070db3 100644
--- a/test/e2e/fixture/applicationsets/expectation.go
+++ b/test/e2e/fixture/applicationsets/expectation.go
@@ -157,9 +157,13 @@ func getDiff(orig, new argov1alpha1.Application) (string, error) {
 
 // getConditionDiff returns a string containing a comparison result of two ApplicationSetCondition (for test output/debug purposes)
 func getConditionDiff(orig, new []v1alpha1.ApplicationSetCondition) (string, error) {
+	if len(orig) != len(new) {
+		return fmt.Sprintf("mismatch between condition sizes: %v %v", len(orig), len(new)), nil
+	}
+
 	var bytes []byte
 
-	for index, _ := range orig {
+	for index := range orig {
 		b, _, err := diff.CreateTwoWayMergePatch(orig[index], new[index], orig[index])
 		if err != nil {
 			return "", err