From 5c76d8f027e797d168a5c8006982c81de717ce90 Mon Sep 17 00:00:00 2001 From: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> Date: Wed, 9 Aug 2023 13:16:17 -0400 Subject: [PATCH 1/5] chore: space in 'Argo CD' (#14987) Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> --- SECURITY.md | 2 +- docs/developer-guide/releasing.md | 2 +- docs/faq.md | 8 ++++---- docs/index.md | 2 +- .../applicationset/Generators-Pull-Request.md | 2 +- .../applicationset/Generators-SCM-Provider.md | 2 +- docs/operator-manual/ingress.md | 10 +++++----- docs/operator-manual/metrics.md | 10 +++++----- docs/operator-manual/security.md | 2 +- docs/user-guide/app_deletion.md | 2 +- docs/user-guide/environment-variables.md | 4 ++-- docs/user-guide/external-url.md | 2 +- docs/user-guide/extra_info.md | 4 ++-- docs/user-guide/helm.md | 4 ++-- docs/user-guide/jsonnet.md | 2 +- docs/user-guide/private-repositories.md | 10 +++++----- docs/user-guide/projects.md | 8 ++++---- docs/user-guide/resource_hooks.md | 2 +- docs/user-guide/sync-options.md | 10 +++++----- 19 files changed, 44 insertions(+), 44 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 38574aa2bd0db..479cd5ef29c97 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -50,7 +50,7 @@ of releasing it within a patch branch for the currently supported releases. ## Reporting a Vulnerability -If you find a security related bug in ArgoCD, we kindly ask you for responsible +If you find a security related bug in Argo CD, we kindly ask you for responsible disclosure and for giving us appropriate time to react, analyze and develop a fix to mitigate the found security vulnerability. diff --git a/docs/developer-guide/releasing.md b/docs/developer-guide/releasing.md index a55be0d8b0c12..bb51ebfa8d14b 100644 --- a/docs/developer-guide/releasing.md +++ b/docs/developer-guide/releasing.md @@ -2,7 +2,7 @@ ## Introduction -ArgoCD is released in a 2 step automated fashion using GitHub actions. The release process takes about 60 minutes, +Argo CD is released in a 2 step automated fashion using GitHub actions. The release process takes about 60 minutes, sometimes a little less, depending on the performance of GitHub Actions runners. The target release branch must already exist in the GitHub repository. If you for diff --git a/docs/faq.md b/docs/faq.md index 588415fc04d2d..19273acc04d23 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -139,7 +139,7 @@ See [#1482](https://github.com/argoproj/argo-cd/issues/1482). ## How often does Argo CD check for changes to my Git or Helm repository ? The default polling interval is 3 minutes (180 seconds). -You can change the setting by updating the `timeout.reconciliation` value in the [argocd-cm](https://github.com/argoproj/argo-cd/blob/2d6ce088acd4fb29271ffb6f6023dbb27594d59b/docs/operator-manual/argocd-cm.yaml#L279-L282) config map. If there are any Git changes, ArgoCD will only update applications with the [auto-sync setting](user-guide/auto_sync.md) enabled. If you set it to `0` then Argo CD will stop polling Git repositories automatically and you can only use alternative methods such as [webhooks](operator-manual/webhook.md) and/or manual syncs for deploying applications. +You can change the setting by updating the `timeout.reconciliation` value in the [argocd-cm](https://github.com/argoproj/argo-cd/blob/2d6ce088acd4fb29271ffb6f6023dbb27594d59b/docs/operator-manual/argocd-cm.yaml#L279-L282) config map. If there are any Git changes, Argo CD will only update applications with the [auto-sync setting](user-guide/auto_sync.md) enabled. If you set it to `0` then Argo CD will stop polling Git repositories automatically and you can only use alternative methods such as [webhooks](operator-manual/webhook.md) and/or manual syncs for deploying applications. ## Why Are My Resource Limits `Out Of Sync`? @@ -194,7 +194,7 @@ argocd ... --insecure ## I have configured Dex via `dex.config` in `argocd-cm`, it still says Dex is unconfigured. Why? -Most likely you forgot to set the `url` in `argocd-cm` to point to your ArgoCD as well. See also +Most likely you forgot to set the `url` in `argocd-cm` to point to your Argo CD as well. See also [the docs](./operator-manual/user-management/index.md#2-configure-argo-cd-for-sso). ## Why are `SealedSecret` resources reporting a `Status`? @@ -208,14 +208,14 @@ fixed CRD if you want this feature to work at all. ## Why are resources of type `SealedSecret` stuck in the `Progressing` state? The controller of the `SealedSecret` resource may expose the status condition on resource it provisioned. Since -version `v2.0.0` ArgoCD picks up that status condition to derive a health status for the `SealedSecret`. +version `v2.0.0` Argo CD picks up that status condition to derive a health status for the `SealedSecret`. Versions before `v0.15.0` of the `SealedSecret` controller are affected by an issue regarding this status conditions updates, which is why this feature is disabled by default in these versions. Status condition updates may be enabled by starting the `SealedSecret` controller with the `--update-status` command line parameter or by setting the `SEALED_SECRETS_UPDATE_STATUS` environment variable. -To disable ArgoCD from checking the status condition on `SealedSecret` resources, add the following resource +To disable Argo CD from checking the status condition on `SealedSecret` resources, add the following resource customization in your `argocd-cm` ConfigMap via `resource.customizations.health.` key. ```yaml diff --git a/docs/index.md b/docs/index.md index 975b4ae56cae4..6315ced37efad 100644 --- a/docs/index.md +++ b/docs/index.md @@ -25,7 +25,7 @@ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/st ``` Follow our [getting started guide](getting_started.md). Further user oriented [documentation](user-guide/) -is provided for additional features. If you are looking to upgrade ArgoCD, see the [upgrade guide](./operator-manual/upgrading/overview.md). +is provided for additional features. If you are looking to upgrade Argo CD, see the [upgrade guide](./operator-manual/upgrading/overview.md). Developer oriented [documentation](developer-guide/) is available for people interested in building third-party integrations. ## How it works diff --git a/docs/operator-manual/applicationset/Generators-Pull-Request.md b/docs/operator-manual/applicationset/Generators-Pull-Request.md index cd37844548d29..693b43ac96415 100644 --- a/docs/operator-manual/applicationset/Generators-Pull-Request.md +++ b/docs/operator-manual/applicationset/Generators-Pull-Request.md @@ -232,7 +232,7 @@ spec: - `api`: Optional URL to access the Bitbucket REST API. For the example above, an API request would be made to `https://api.bitbucket.org/2.0/repositories/{workspace}/{repo_slug}/pullrequests`. If not set, defaults to `https://api.bitbucket.org/2.0` - `branchMatch`: Optional regexp filter which should match the source branch name. This is an alternative to labels which are not supported by Bitbucket server. -If you want to access a private repository, ArgoCD will need credentials to access repository in Bitbucket Cloud. You can use Bitbucket App Password (generated per user, with access to whole workspace), or Bitbucket App Token (generated per repository, with access limited to repository scope only). If both App Password and App Token are defined, App Token will be used. +If you want to access a private repository, Argo CD will need credentials to access repository in Bitbucket Cloud. You can use Bitbucket App Password (generated per user, with access to whole workspace), or Bitbucket App Token (generated per repository, with access limited to repository scope only). If both App Password and App Token are defined, App Token will be used. To use Bitbucket App Password, use `basicAuth` section. - `username`: The username to authenticate with. It only needs read access to the relevant repo. diff --git a/docs/operator-manual/applicationset/Generators-SCM-Provider.md b/docs/operator-manual/applicationset/Generators-SCM-Provider.md index 8f4a6ad96a986..9651633c9b172 100644 --- a/docs/operator-manual/applicationset/Generators-SCM-Provider.md +++ b/docs/operator-manual/applicationset/Generators-SCM-Provider.md @@ -318,7 +318,7 @@ Depending on whether `role` is provided in `awsCodeCommit` property, AWS IAM per #### Discover AWS CodeCommit Repositories in the same AWS Account as ApplicationSet Controller Without specifying `role`, ApplicationSet controller will use its own AWS identity to scan AWS CodeCommit repos. -This is suitable when you have a simple setup that all AWS CodeCommit repos reside in the same AWS account as your ArgoCD. +This is suitable when you have a simple setup that all AWS CodeCommit repos reside in the same AWS account as your Argo CD. As the ApplicationSet controller AWS identity is used directly for repo discovery, it must be granted below AWS permissions. diff --git a/docs/operator-manual/ingress.md b/docs/operator-manual/ingress.md index a8387b352f6fd..84b2bcaf34a67 100644 --- a/docs/operator-manual/ingress.md +++ b/docs/operator-manual/ingress.md @@ -415,9 +415,9 @@ Once we create this service, we can configure the Ingress to conditionally route ``` ## [Istio](https://www.istio.io) -You can put ArgoCD behind Istio using following configurations. Here we will achive both serving ArgoCD behind istio and using subpath on Istio +You can put Argo CD behind Istio using following configurations. Here we will achive both serving Argo CD behind istio and using subpath on Istio -First we need to make sure that we can run ArgoCD with subpath (ie /argocd). For this we have used install.yaml from argocd project as is +First we need to make sure that we can run Argo CD with subpath (ie /argocd). For this we have used install.yaml from argocd project as is ```bash curl -kLs -o install.yaml https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml @@ -467,7 +467,7 @@ spec: value: "0" ``` -After that install ArgoCD (there should be only 3 yml file defined above in current directory ) +After that install Argo CD (there should be only 3 yml file defined above in current directory ) ```bash kubectl apply -k ./ -n argocd --wait=true @@ -712,7 +712,7 @@ Once the DNS change is propagated, you're ready to use Argo with your Google Clo ## Authenticating through multiple layers of authenticating reverse proxies -ArgoCD endpoints may be protected by one or more reverse proxies layers, in that case, you can provide additional headers through the `argocd` CLI `--header` parameter to authenticate through those layers. +Argo CD endpoints may be protected by one or more reverse proxies layers, in that case, you can provide additional headers through the `argocd` CLI `--header` parameter to authenticate through those layers. ```shell $ argocd login : --header 'x-token1:foo' --header 'x-token2:bar' # can be repeated multiple times @@ -720,7 +720,7 @@ $ argocd login : --header 'x-token1:foo,x-token2:bar' # headers can ``` ## ArgoCD Server and UI Root Path (v1.5.3) -ArgoCD server and UI can be configured to be available under a non-root path (e.g. `/argo-cd`). +Argo CD server and UI can be configured to be available under a non-root path (e.g. `/argo-cd`). To do this, add the `--rootpath` flag into the `argocd-server` deployment command: ```yaml diff --git a/docs/operator-manual/metrics.md b/docs/operator-manual/metrics.md index da816f82f519b..174b08fd75c2c 100644 --- a/docs/operator-manual/metrics.md +++ b/docs/operator-manual/metrics.md @@ -7,7 +7,7 @@ Metrics about applications. Scraped at the `argocd-metrics:8082/metrics` endpoin | Metric | Type | Description | |--------|:----:|-------------| -| `argocd_app_info` | gauge | Information about Applications. It contains labels such as `sync_status` and `health_status` that reflect the application state in ArgoCD. | +| `argocd_app_info` | gauge | Information about Applications. It contains labels such as `sync_status` and `health_status` that reflect the application state in Argo CD. | | `argocd_app_k8s_request_total` | counter | Number of kubernetes requests executed during application reconciliation | | `argocd_app_labels` | gauge | Argo Application labels converted to Prometheus labels. Disabled by default. See section below about how to enable it. | | `argocd_app_reconcile` | histogram | Application reconciliation performance. | @@ -23,7 +23,7 @@ Metrics about applications. Scraped at the `argocd-metrics:8082/metrics` endpoin | `argocd_redis_request_duration` | histogram | Redis requests duration. | | `argocd_redis_request_total` | counter | Number of redis requests executed during application reconciliation | -If you use ArgoCD with many application and project creation and deletion, +If you use Argo CD with many application and project creation and deletion, the metrics page will keep in cache your application and project's history. If you are having issues because of a large number of metrics cardinality due to deleted resources, you can schedule a metrics reset to clean the @@ -32,16 +32,16 @@ history with an application controller flag. Example: ### Exposing Application labels as Prometheus metrics -There are use-cases where ArgoCD Applications contain labels that are desired to be exposed as Prometheus metrics. +There are use-cases where Argo CD Applications contain labels that are desired to be exposed as Prometheus metrics. Some examples are: * Having the team name as a label to allow routing alerts to specific receivers * Creating dashboards broken down by business units As the Application labels are specific to each company, this feature is disabled by default. To enable it, add the -`--metrics-application-labels` flag to the ArgoCD application controller. +`--metrics-application-labels` flag to the Argo CD application controller. -The example below will expose the ArgoCD Application labels `team-name` and `business-unit` to Prometheus: +The example below will expose the Argo CD Application labels `team-name` and `business-unit` to Prometheus: containers: - command: diff --git a/docs/operator-manual/security.md b/docs/operator-manual/security.md index 593030e1756e4..3ba9fdfe39363 100644 --- a/docs/operator-manual/security.md +++ b/docs/operator-manual/security.md @@ -173,7 +173,7 @@ kubectl edit clusterrole argocd-application-controller ``` !!! tip - If you want to deny ArgoCD access to a kind of resource then add it as an [excluded resource](declarative-setup.md#resource-exclusion). + If you want to deny Argo CD access to a kind of resource then add it as an [excluded resource](declarative-setup.md#resource-exclusion). ## Auditing diff --git a/docs/user-guide/app_deletion.md b/docs/user-guide/app_deletion.md index 65a17e7eb53ff..a1eaedf41cd04 100644 --- a/docs/user-guide/app_deletion.md +++ b/docs/user-guide/app_deletion.md @@ -54,7 +54,7 @@ When deleting an Application with this finalizer, the Argo CD application contro Adding the finalizer enables cascading deletes when implementing [the App of Apps pattern](../operator-manual/cluster-bootstrapping.md#cascading-deletion). The default propagation policy for cascading deletion is [foreground cascading deletion](https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion). -ArgoCD performs [background cascading deletion](https://kubernetes.io/docs/concepts/architecture/garbage-collection/#background-deletion) when `resources-finalizer.argocd.argoproj.io/background` is set. +Argo CD performs [background cascading deletion](https://kubernetes.io/docs/concepts/architecture/garbage-collection/#background-deletion) when `resources-finalizer.argocd.argoproj.io/background` is set. When you invoke `argocd app delete` with `--cascade`, the finalizer is added automatically. You can set the propagation policy with `--propagation-policy `. diff --git a/docs/user-guide/environment-variables.md b/docs/user-guide/environment-variables.md index ceea5798e83a3..461195d9ce855 100644 --- a/docs/user-guide/environment-variables.md +++ b/docs/user-guide/environment-variables.md @@ -4,6 +4,6 @@ The following environment variables can be used with `argocd` CLI: | Environment Variable | Description | | --- | --- | -| `ARGOCD_SERVER` | the address of the ArgoCD server without `https://` prefix
(instead of specifying `--server` for every command)
eg. `ARGOCD_SERVER=argocd.mycompany.com` if served through an ingress with DNS | -| `ARGOCD_AUTH_TOKEN` | the ArgoCD `apiKey` for your ArgoCD user to be able to authenticate | +| `ARGOCD_SERVER` | the address of the Argo CD server without `https://` prefix
(instead of specifying `--server` for every command)
eg. `ARGOCD_SERVER=argocd.mycompany.com` if served through an ingress with DNS | +| `ARGOCD_AUTH_TOKEN` | the Argo CD `apiKey` for your Argo CD user to be able to authenticate | | `ARGOCD_OPTS` | command-line options to pass to `argocd` CLI
eg. `ARGOCD_OPTS="--grpc-web"` | diff --git a/docs/user-guide/external-url.md b/docs/user-guide/external-url.md index 173a8724c5fea..792b8465b233b 100644 --- a/docs/user-guide/external-url.md +++ b/docs/user-guide/external-url.md @@ -1,6 +1,6 @@ # Add external URL -You can add additional external links to ArgoCD dashboard. For example +You can add additional external links to Argo CD dashboard. For example links monitoring pages or documentation instead of just ingress hosts or other apps. ArgoCD generates a clickable links to external pages for a resource based on per resource annotation. diff --git a/docs/user-guide/extra_info.md b/docs/user-guide/extra_info.md index 0a27e497ec46d..298b457a81bd4 100644 --- a/docs/user-guide/extra_info.md +++ b/docs/user-guide/extra_info.md @@ -1,6 +1,6 @@ # Add extra Application info -You can add additional information to an Application on your ArgoCD dashboard. +You can add additional information to an Application on your Argo CD dashboard. If you wish to add clickable links, see [Add external URL](https://argo-cd.readthedocs.io/en/stable/user-guide/external-url/). This is done by providing the 'info' field a key-value in your Application manifest. @@ -21,7 +21,7 @@ info: ``` ![External link](../assets/extra_info-1.png) -The additional information will be visible on the ArgoCD Application details page. +The additional information will be visible on the Argo CD Application details page. ![External link](../assets/extra_info.png) diff --git a/docs/user-guide/helm.md b/docs/user-guide/helm.md index 5c8b8c020adf5..b4681a169b181 100644 --- a/docs/user-guide/helm.md +++ b/docs/user-guide/helm.md @@ -122,7 +122,7 @@ source: ## Helm Release Name -By default, the Helm release name is equal to the Application name to which it belongs. Sometimes, especially on a centralised ArgoCD, +By default, the Helm release name is equal to the Application name to which it belongs. Sometimes, especially on a centralised Argo CD, you may want to override that name, and it is possible with the `release-name` flag on the cli: ```bash @@ -138,7 +138,7 @@ source: ``` !!! warning "Important notice on overriding the release name" - Please note that overriding the Helm release name might cause problems when the chart you are deploying is using the `app.kubernetes.io/instance` label. ArgoCD injects this label with the value of the Application name for tracking purposes. So when overriding the release name, the Application name will stop being equal to the release name. Because ArgoCD will overwrite the label with the Application name it might cause some selectors on the resources to stop working. In order to avoid this we can configure ArgoCD to use another label for tracking in the [ArgoCD configmap argocd-cm.yaml](../operator-manual/argocd-cm.yaml) - check the lines describing `application.instanceLabelKey`. + Please note that overriding the Helm release name might cause problems when the chart you are deploying is using the `app.kubernetes.io/instance` label. Argo CD injects this label with the value of the Application name for tracking purposes. So when overriding the release name, the Application name will stop being equal to the release name. Because Argo CD will overwrite the label with the Application name it might cause some selectors on the resources to stop working. In order to avoid this we can configure Argo CD to use another label for tracking in the [ArgoCD configmap argocd-cm.yaml](../operator-manual/argocd-cm.yaml) - check the lines describing `application.instanceLabelKey`. ## Helm Hooks diff --git a/docs/user-guide/jsonnet.md b/docs/user-guide/jsonnet.md index 699cd45335b61..194daa06c2591 100644 --- a/docs/user-guide/jsonnet.md +++ b/docs/user-guide/jsonnet.md @@ -1,6 +1,6 @@ # Jsonnet -Any file matching `*.jsonnet` in a directory app is treated as a Jsonnet file. ArgoCD evaluates the Jsonnet and is able to parse a generated object or array. +Any file matching `*.jsonnet` in a directory app is treated as a Jsonnet file. Argo CD evaluates the Jsonnet and is able to parse a generated object or array. ## Build Environment diff --git a/docs/user-guide/private-repositories.md b/docs/user-guide/private-repositories.md index cb984f9f9e7d0..790e3eca91ec2 100644 --- a/docs/user-guide/private-repositories.md +++ b/docs/user-guide/private-repositories.md @@ -3,7 +3,7 @@ !!!note Some Git hosters - notably GitLab and possibly on-premise GitLab instances as well - require you to specify the `.git` suffix in the repository URL, otherwise they will send a HTTP 301 redirect to the - repository URL suffixed with `.git`. ArgoCD will **not** follow these redirects, so you have to + repository URL suffixed with `.git`. Argo CD will **not** follow these redirects, so you have to adapt your repository URL to be suffixed with `.git`. ## Credentials @@ -52,7 +52,7 @@ Then, connect the repository using any non-empty string as username and the acce ### TLS Client Certificates for HTTPS repositories -If your repository server requires you to use TLS client certificates for authentication, you can configure ArgoCD repositories to make use of them. For this purpose, `--tls-client-cert-path` and `--tls-client-cert-key-path` switches to the `argocd repo add` command can be used to specify the files on your local system containing client certificate and the corresponding key, respectively: +If your repository server requires you to use TLS client certificates for authentication, you can configure Argo CD repositories to make use of them. For this purpose, `--tls-client-cert-path` and `--tls-client-cert-key-path` switches to the `argocd repo add` command can be used to specify the files on your local system containing client certificate and the corresponding key, respectively: ``` argocd repo add https://repo.example.com/repo.git --tls-client-cert-path ~/mycert.crt --tls-client-cert-key-path ~/mycert.key @@ -63,7 +63,7 @@ Of course, you can also use this in combination with the `--username` and `--pas Your TLS client certificate and corresponding key can also be configured using the UI, see instructions for adding Git repos using HTTPS. !!! note - Your client certificate and key data must be in PEM format, other formats (such as PKCS12) are not understood. Also make sure that your certificate's key is not password protected, otherwise it cannot be used by ArgoCD. + Your client certificate and key data must be in PEM format, other formats (such as PKCS12) are not understood. Also make sure that your certificate's key is not password protected, otherwise it cannot be used by Argo CD. !!! note When pasting TLS client certificate and key in the text areas in the web UI, make sure they contain no unintended line breaks or additional characters. @@ -169,7 +169,7 @@ To set up a credential template using the Web UI, simply fill in all relevant cr To manage credential templates using the CLI, use the `repocreds` sub-command, for example `argocd repocreds add https://github.com/argoproj --username youruser --password yourpass` would setup a credential template for the URL prefix `https://github.com/argoproj` using the specified username/password combination. Similar to the `repo` sub-command, you can also list and remove repository credentials using the `argocd repocreds list` and `argocd repocreds rm` commands, respectively. -In order for ArgoCD to use a credential template for any given repository, the following conditions must be met: +In order for Argo CD to use a credential template for any given repository, the following conditions must be met: * The repository must either not be configured at all, or if configured, must not contain any credential information * The URL configured for a credential template (e.g. `https://github.com/argoproj`) must match as prefix for the repository URL (e.g. `https://github.com/argoproj/argocd-example-apps`). @@ -204,7 +204,7 @@ FATA[0000] rpc error: code = Unknown desc = authentication required ## Self-signed & Untrusted TLS Certificates -If you are connecting a repository on a HTTPS server using a self-signed certificate, or a certificate signed by a custom Certificate Authority (CA) which are not known to ArgoCD, the repository will not be added due to security reasons. This is indicated by an error message such as `x509: certificate signed by unknown authority`. +If you are connecting a repository on a HTTPS server using a self-signed certificate, or a certificate signed by a custom Certificate Authority (CA) which are not known to Argo CD, the repository will not be added due to security reasons. This is indicated by an error message such as `x509: certificate signed by unknown authority`. 1. You can let ArgoCD connect the repository in an insecure way, without verifying the server's certificate at all. This can be accomplished by using the `--insecure-skip-server-verification` flag when adding the repository with the `argocd` CLI utility. However, this should be done only for non-production setups, as it imposes a serious security issue through possible man-in-the-middle attacks. diff --git a/docs/user-guide/projects.md b/docs/user-guide/projects.md index 666534975a854..0ed79ede623d5 100644 --- a/docs/user-guide/projects.md +++ b/docs/user-guide/projects.md @@ -271,15 +271,15 @@ projectName: `proj-global-test` should be replaced with your own global project ## Project scoped Repositories and Clusters -Normally, an ArgoCD admin creates a project and decides in advance which clusters and Git repositories +Normally, an Argo CD admin creates a project and decides in advance which clusters and Git repositories it defines. However, this creates a problem in scenarios where a developer wants to add a repository or cluster -after the initial creation of the project. This forces the developer to contact their ArgoCD admin again to update the project definition. +after the initial creation of the project. This forces the developer to contact their Argo CD admin again to update the project definition. It is possible to offer a self-service process for developers so that they can add a repository and/or cluster in a project on their own even after the initial creation of the project. -For this purpose ArgoCD supports project-scoped repositories and clusters. +For this purpose Argo CD supports project-scoped repositories and clusters. -To begin the process, ArgoCD admins must configure RBAC security to allow this self-service behavior. +To begin the process, Argo CD admins must configure RBAC security to allow this self-service behavior. For example, to allow users to add project scoped repositories and admin would have to add the following RBAC rules: diff --git a/docs/user-guide/resource_hooks.md b/docs/user-guide/resource_hooks.md index 9f8f98e033a20..d705f8d21423d 100644 --- a/docs/user-guide/resource_hooks.md +++ b/docs/user-guide/resource_hooks.md @@ -69,7 +69,7 @@ The following policies define when the hook will be deleted. | `HookFailed` | The hook resource is deleted after the hook failed. | | `BeforeHookCreation` | Any existing hook resource is deleted before the new one is created (since v1.3). It is meant to be used with `/metadata/name`. | -Note that if no deletion policy is specified, ArgoCD will automatically assume `BeforeHookCreation` rules. +Note that if no deletion policy is specified, Argo CD will automatically assume `BeforeHookCreation` rules. ### Sync Status with Jobs/Workflows with Time to Live (ttl) diff --git a/docs/user-guide/sync-options.md b/docs/user-guide/sync-options.md index 688e1800bf406..9afe031ba7469 100644 --- a/docs/user-guide/sync-options.md +++ b/docs/user-guide/sync-options.md @@ -316,10 +316,10 @@ spec: - CreateNamespace=true ``` -In order for ArgoCD to manage the labels and annotations on the namespace, `CreateNamespace=true` needs to be set as a +In order for Argo CD to manage the labels and annotations on the namespace, `CreateNamespace=true` needs to be set as a sync option, otherwise nothing will happen. If the namespace doesn't already exist, or if it already exists and doesn't already have labels and/or annotations set on it, you're good to go. Using `managedNamespaceMetadata` will also set the -resource tracking label (or annotation) on the namespace, so you can easily track which namespaces are managed by ArgoCD. +resource tracking label (or annotation) on the namespace, so you can easily track which namespaces are managed by Argo CD. In the case you do not have any custom annotations or labels but would nonetheless want to have resource tracking set on your namespace, that can be done by setting `managedNamespaceMetadata` with an empty `labels` and/or `annotations` map, @@ -339,7 +339,7 @@ spec: - CreateNamespace=true ``` -In the case where ArgoCD is "adopting" an existing namespace which already has metadata set on it, we rely on using +In the case where Argo CD is "adopting" an existing namespace which already has metadata set on it, we rely on using Server Side Apply in order not to lose metadata which has already been set. The main implication here is that it takes a few extra steps to get rid of an already preexisting field. @@ -355,7 +355,7 @@ metadata: abc: "123" ``` -If we want to manage the `foobar` namespace with ArgoCD and to then also remove the `foo: bar` annotation, in +If we want to manage the `foobar` namespace with Argo CD and to then also remove the `foo: bar` annotation, in `managedNamespaceMetadata` we'd need to first rename the `foo` value: ```yaml @@ -385,7 +385,7 @@ spec: - CreateNamespace=true ``` -Another thing to keep mind of is that if you have a k8s manifest for the same namespace in your ArgoCD application, that +Another thing to keep mind of is that if you have a k8s manifest for the same namespace in your Argo CD application, that will take precedence and *overwrite whatever values that have been set in `managedNamespaceMetadata`*. In other words, if you have an application that sets `managedNamespaceMetadata` From 19de408dbc535444a963cf4a4c1c9549ef2e2840 Mon Sep 17 00:00:00 2001 From: Alexander Matyushentsev Date: Wed, 9 Aug 2023 15:18:26 -0700 Subject: [PATCH 2/5] feat: Add Support for AzureDevops Webhooks (#14969) * feat: Add Support for AzureDevops Webhooks Signed-off-by: Alexander Matyushentsev * document azure devops webhook configuration Signed-off-by: Alexander Matyushentsev --------- Signed-off-by: Alexander Matyushentsev --- applicationset/webhook/webhook.go | 4 +- docs/assets/azure-devops-webhook-config.png | Bin 0 -> 40779 bytes docs/operator-manual/webhook.md | 23 +++- go.mod | 4 +- go.sum | 8 +- util/settings/settings.go | 20 ++++ .../testdata/azuredevops-git-push-event.json | 107 ++++++++++++++++++ util/webhook/webhook.go | 99 +++++++++++----- util/webhook/webhook_test.go | 27 ++++- 9 files changed, 244 insertions(+), 48 deletions(-) create mode 100644 docs/assets/azure-devops-webhook-config.png create mode 100644 util/webhook/testdata/azuredevops-git-push-event.json diff --git a/applicationset/webhook/webhook.go b/applicationset/webhook/webhook.go index f1dd5b5ebb0eb..22ac065f00b30 100644 --- a/applicationset/webhook/webhook.go +++ b/applicationset/webhook/webhook.go @@ -19,9 +19,9 @@ import ( "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" argosettings "github.com/argoproj/argo-cd/v2/util/settings" + "github.com/go-playground/webhooks/v6/github" + "github.com/go-playground/webhooks/v6/gitlab" log "github.com/sirupsen/logrus" - "gopkg.in/go-playground/webhooks.v5/github" - "gopkg.in/go-playground/webhooks.v5/gitlab" ) type WebhookHandler struct { diff --git a/docs/assets/azure-devops-webhook-config.png b/docs/assets/azure-devops-webhook-config.png new file mode 100644 index 0000000000000000000000000000000000000000..26fb6d0683d634a5ee1634a86950c765f5b696e7 GIT binary patch literal 40779 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%0D9=FoRMKhk3_HIhD& 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 literal 0 HcmV?d00001 diff --git a/docs/operator-manual/webhook.md b/docs/operator-manual/webhook.md index 9a93d6ff0208c..1d5ad5ec79c96 100644 --- a/docs/operator-manual/webhook.md +++ b/docs/operator-manual/webhook.md @@ -4,7 +4,7 @@ Argo CD polls Git repositories every three minutes to detect changes to the manifests. To eliminate this delay from polling, the API server can be configured to receive webhook events. Argo CD supports -Git webhook notifications from GitHub, GitLab, Bitbucket, Bitbucket Server and Gogs. The following explains how to configure +Git webhook notifications from GitHub, GitLab, Bitbucket, Bitbucket Server, Azure DevOps and Gogs. The following explains how to configure a Git webhook for GitHub, but the same process should be applicable to other providers. !!! note @@ -12,19 +12,28 @@ a Git webhook for GitHub, but the same process should be applicable to other pro the same. A hook event for a push to branch `x` will trigger a refresh for an app pointing at the same repo with `targetRevision: refs/tags/x`. -### 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 Argo CD instance (e.g. `https://argocd.example.com/api/webhook`). If you wish to use a shared secret, input an arbitrary value in the secret. This value will be used when configuring the webhook in the next step. +## Github + ![Add Webhook](../assets/webhook-config.png "Add Webhook") !!! 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 Argo CD With The WebHook Secret (Optional) +## Azure DevOps + +![Add Webhook](../assets/azure-devops-webhook-config.png "Add Webhook") + +Azure DevOps optionally supports securing the webhook using basic authentication. To use it, specify the username and password in the webhook configuration and configure the same username/password in `argocd-secret` Kubernetes secret in +`webhook.azuredevops.username` and `webhook.azuredevops.password` keys. + +## 2. Configure Argo CD With The WebHook Secret (Optional) Configuring a webhook shared secret is optional, since Argo CD will still refresh applications related to the Git repository, even with unauthenticated webhook events. This is safe to do since @@ -36,12 +45,14 @@ In the `argocd-secret` kubernetes secret, configure one of the following keys wi provider's webhook secret configured in step 1. | Provider | K8s Secret Key | -|-----------------| ---------------------------------| +|-----------------|----------------------------------| | GitHub | `webhook.github.secret` | | GitLab | `webhook.gitlab.secret` | | BitBucket | `webhook.bitbucket.uuid` | | BitBucketServer | `webhook.bitbucketserver.secret` | | Gogs | `webhook.gogs.secret` | +| Azure DevOps | `webhook.azuredevops.username` | +| | `webhook.azuredevops.password` | Edit the Argo CD kubernetes secret: @@ -79,6 +90,10 @@ stringData: # gogs server webhook secret webhook.gogs.secret: shhhh! it's a gogs server secret + + # azuredevops username and password + webhook.azuredevops.username: admin + webhook.azuredevops.password: secret-password ``` After saving, the changes should take effect automatically. diff --git a/go.mod b/go.mod index e3eea7a804b99..3f3659a474ff8 100644 --- a/go.mod +++ b/go.mod @@ -29,9 +29,10 @@ require ( github.com/go-logr/logr v1.2.4 github.com/go-openapi/loads v0.21.2 github.com/go-openapi/runtime v0.26.0 + github.com/go-playground/webhooks/v6 v6.2.1-0.20230808162451-10570b0a59e8 github.com/go-redis/cache/v9 v9.0.0 github.com/gobwas/glob v0.2.3 - github.com/gogits/go-gogs-client v0.0.0-20190616193657-5a05380e4bc2 + github.com/gogits/go-gogs-client v0.0.0-20200905025246-8bb8a50cb355 github.com/gogo/protobuf v1.3.2 github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang/protobuf v1.5.3 @@ -85,7 +86,6 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc google.golang.org/grpc v1.56.2 google.golang.org/protobuf v1.31.0 - gopkg.in/go-playground/webhooks.v5 v5.17.0 gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.24.2 diff --git a/go.sum b/go.sum index 28695dba8b53d..8b9fad0e0e528 100644 --- a/go.sum +++ b/go.sum @@ -1048,6 +1048,8 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-playground/webhooks/v6 v6.2.1-0.20230808162451-10570b0a59e8 h1:QDFjrpOZagU8KEpSCF0WvBKOGq2GYuVZ4ZDg/gelrEE= +github.com/go-playground/webhooks/v6 v6.2.1-0.20230808162451-10570b0a59e8/go.mod h1:GCocmfMtpJdkEOM1uG9p2nXzg1kY5X/LtvQgtPHUaaA= github.com/go-redis/cache/v9 v9.0.0 h1:0thdtFo0xJi0/WXbRVu8B066z8OvVymXTJGaXrVWnN0= github.com/go-redis/cache/v9 v9.0.0/go.mod h1:cMwi1N8ASBOufbIvk7cdXe2PbPjK/WMRL95FFHWsSgI= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -1094,8 +1096,8 @@ github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogits/go-gogs-client v0.0.0-20190616193657-5a05380e4bc2 h1:BbwX8wsMRDZRdNYxAna+4ls3wvMKJyn4PT6Zk1CPxP4= -github.com/gogits/go-gogs-client v0.0.0-20190616193657-5a05380e4bc2/go.mod h1:cY2AIrMgHm6oOHmR7jY+9TtjzSjQ3iG7tURJG3Y6XH0= +github.com/gogits/go-gogs-client v0.0.0-20200905025246-8bb8a50cb355 h1:HTVNOdTWO/gHYeFnr/HwpYwY6tgMcYd+Rgf1XrHnORY= +github.com/gogits/go-gogs-client v0.0.0-20200905025246-8bb8a50cb355/go.mod h1:cY2AIrMgHm6oOHmR7jY+9TtjzSjQ3iG7tURJG3Y6XH0= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -2791,8 +2793,6 @@ gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= -gopkg.in/go-playground/webhooks.v5 v5.17.0 h1:truBced5ZmkiNKK47cM8bMe86wUSjNks7SFMuNKwzlc= -gopkg.in/go-playground/webhooks.v5 v5.17.0/go.mod h1:LZbya/qLVdbqDR1aKrGuWV6qbia2zCYSR5dpom2SInQ= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= diff --git a/util/settings/settings.go b/util/settings/settings.go index a9d49b78cd5df..06fd0488ad711 100644 --- a/util/settings/settings.go +++ b/util/settings/settings.go @@ -71,6 +71,10 @@ type ArgoCDSettings struct { WebhookBitbucketServerSecret string `json:"webhookBitbucketServerSecret,omitempty"` // WebhookGogsSecret holds the shared secret for authenticating Gogs webhook events WebhookGogsSecret string `json:"webhookGogsSecret,omitempty"` + // WebhookAzureDevOpsUsername holds the username for authenticating Azure DevOps webhook events + WebhookAzureDevOpsUsername string `json:"webhookAzureDevOpsUsername,omitempty"` + // WebhookAzureDevOpsPassword holds the password for authenticating Azure DevOps webhook events + WebhookAzureDevOpsPassword string `json:"webhookAzureDevOpsPassword,omitempty"` // Secrets holds all secrets in argocd-secret as a map[string]string Secrets map[string]string `json:"secrets,omitempty"` // KustomizeBuildOptions is a string of kustomize build parameters @@ -411,6 +415,10 @@ const ( settingsWebhookBitbucketServerSecretKey = "webhook.bitbucketserver.secret" // settingsWebhookGogsSecret is the key for Gogs webhook secret settingsWebhookGogsSecretKey = "webhook.gogs.secret" + // settingsWebhookAzureDevOpsUsernameKey is the key for Azure DevOps webhook username + settingsWebhookAzureDevOpsUsernameKey = "webhook.azuredevops.username" + // settingsWebhookAzureDevOpsPasswordKey is the key for Azure DevOps webhook password + settingsWebhookAzureDevOpsPasswordKey = "webhook.azuredevops.password" // settingsApplicationInstanceLabelKey is the key to configure injected app instance label key settingsApplicationInstanceLabelKey = "application.instanceLabelKey" // settingsResourceTrackingMethodKey is the key to configure tracking method for application resources @@ -1457,6 +1465,12 @@ func (mgr *SettingsManager) updateSettingsFromSecret(settings *ArgoCDSettings, a if gogsWebhookSecret := argoCDSecret.Data[settingsWebhookGogsSecretKey]; len(gogsWebhookSecret) > 0 { settings.WebhookGogsSecret = string(gogsWebhookSecret) } + if azureDevOpsUsername := argoCDSecret.Data[settingsWebhookAzureDevOpsUsernameKey]; len(azureDevOpsUsername) > 0 { + settings.WebhookAzureDevOpsUsername = string(azureDevOpsUsername) + } + if azureDevOpsPassword := argoCDSecret.Data[settingsWebhookAzureDevOpsPasswordKey]; len(azureDevOpsPassword) > 0 { + settings.WebhookAzureDevOpsPassword = string(azureDevOpsPassword) + } // The TLS certificate may be externally managed. We try to load it from an // external secret first. If the external secret doesn't exist, we either @@ -1576,6 +1590,12 @@ func (mgr *SettingsManager) SaveSettings(settings *ArgoCDSettings) error { if settings.WebhookGogsSecret != "" { argoCDSecret.Data[settingsWebhookGogsSecretKey] = []byte(settings.WebhookGogsSecret) } + if settings.WebhookAzureDevOpsUsername != "" { + argoCDSecret.Data[settingsWebhookAzureDevOpsUsernameKey] = []byte(settings.WebhookAzureDevOpsUsername) + } + if settings.WebhookAzureDevOpsPassword != "" { + argoCDSecret.Data[settingsWebhookAzureDevOpsPasswordKey] = []byte(settings.WebhookAzureDevOpsPassword) + } // we only write the certificate to the secret if it's not externally // managed. if settings.Certificate != nil && !settings.CertificateIsExternal { diff --git a/util/webhook/testdata/azuredevops-git-push-event.json b/util/webhook/testdata/azuredevops-git-push-event.json new file mode 100644 index 0000000000000..102e7f08aab3d --- /dev/null +++ b/util/webhook/testdata/azuredevops-git-push-event.json @@ -0,0 +1,107 @@ +{ + "subscriptionId": "8fd412f1-9873-4b45-8854-655b1b8a2eff", + "notificationId": 2, + "id": "09b0b950-47fa-4f45-8b65-5a22686314f8", + "eventType": "git.push", + "publisherId": "tfs", + "message": { + "text": "Alexander Matyushentsev pushed updates to alex-test:master\r\n(https://dev.azure.com/alexander0053/alex-test/_git/alex-test/#version=GBmaster)", + "html": "Alexander Matyushentsev pushed updates to alex-test:master", + "markdown": "Alexander Matyushentsev pushed updates to [alex-test](https://dev.azure.com/alexander0053/alex-test/_git/alex-test/):[master](https://dev.azure.com/alexander0053/alex-test/_git/alex-test/#version=GBmaster)" + }, + "detailedMessage": { + "text": "Alexander Matyushentsev pushed a commit to alex-test:master\r\n - draft 298a79aa (https://dev.azure.com/alexander0053/alex-test/_git/alex-test/commit/298a79aa1552799a70718a0ee914d153d5a1a76b)", + "html": "Alexander Matyushentsev pushed a commit to alex-test:master\r\n", + "markdown": "Alexander Matyushentsev pushed a commit to [alex-test](https://dev.azure.com/alexander0053/alex-test/_git/alex-test/):[master](https://dev.azure.com/alexander0053/alex-test/_git/alex-test/#version=GBmaster)\r\n* draft [298a79aa](https://dev.azure.com/alexander0053/alex-test/_git/alex-test/commit/298a79aa1552799a70718a0ee914d153d5a1a76b)" + }, + "resource": { + "commits": [ + { + "commitId": "298a79aa1552799a70718a0ee914d153d5a1a76b", + "author": { + "name": "Alexander Matyushentsev", + "email": "AMatyushentsev@gmail.com", + "date": "2023-08-09T00:45:39Z" + }, + "committer": { + "name": "Alexander Matyushentsev", + "email": "AMatyushentsev@gmail.com", + "date": "2023-08-09T00:45:39Z" + }, + "comment": "draft\n\nSigned-off-by: Alexander Matyushentsev ", + "url": "https://dev.azure.com/alexander0053/_apis/git/repositories/ba2967cc-02c2-414c-8d10-1b99197cbaa6/commits/298a79aa1552799a70718a0ee914d153d5a1a76b" + } + ], + "refUpdates": [ + { + "name": "refs/heads/master", + "oldObjectId": "fa51eeb1e50b98293ce281e6d5492b9decae613b", + "newObjectId": "298a79aa1552799a70718a0ee914d153d5a1a76b" + } + ], + "repository": { + "id": "ba2967cc-02c2-414c-8d10-1b99197cbaa6", + "name": "alex-test", + "url": "https://dev.azure.com/alexander0053/_apis/git/repositories/ba2967cc-02c2-414c-8d10-1b99197cbaa6", + "project": { + "id": "ab1c194f-94fa-4d1a-87ff-e9458637d060", + "name": "alex-test", + "url": "https://dev.azure.com/alexander0053/_apis/projects/ab1c194f-94fa-4d1a-87ff-e9458637d060", + "state": "wellFormed", + "visibility": "unchanged", + "lastUpdateTime": "0001-01-01T00:00:00" + }, + "defaultBranch": "refs/heads/master", + "remoteUrl": "https://dev.azure.com/alexander0053/alex-test/_git/alex-test" + }, + "pushedBy": { + "displayName": "Alexander Matyushentsev", + "url": "https://spsprodcus4.vssps.visualstudio.com/A7a73fd0c-d080-434d-a8b4-0b4c0217e290/_apis/Identities/07220d5e-521c-683d-982c-726e80086d08", + "_links": { + "avatar": { + "href": "https://dev.azure.com/alexander0053/_apis/GraphProfile/MemberAvatars/aad.MDcyMjBkNWUtNTIxYy03ODNkLTk4MmMtNzI2ZTgwMDg2ZDA4" + } + }, + "id": "07220d5e-521c-683d-982c-726e80086d08", + "uniqueName": "alexander@akuity.onmicrosoft.com", + "imageUrl": "https://dev.azure.com/alexander0053/_api/_common/identityImage?id=07220d5e-521c-683d-982c-726e80086d08", + "descriptor": "aad.MDcyMjBkNWUtNTIxYy03ODNkLTk4MmMtNzI2ZTgwMDg2ZDA4" + }, + "pushId": 4, + "date": "2023-08-09T00:45:42.8315767Z", + "url": "https://dev.azure.com/alexander0053/_apis/git/repositories/ba2967cc-02c2-414c-8d10-1b99197cbaa6/pushes/4", + "_links": { + "self": { + "href": "https://dev.azure.com/alexander0053/_apis/git/repositories/ba2967cc-02c2-414c-8d10-1b99197cbaa6/pushes/4" + }, + "repository": { + "href": "https://dev.azure.com/alexander0053/ab1c194f-94fa-4d1a-87ff-e9458637d060/_apis/git/repositories/ba2967cc-02c2-414c-8d10-1b99197cbaa6" + }, + "commits": { + "href": "https://dev.azure.com/alexander0053/_apis/git/repositories/ba2967cc-02c2-414c-8d10-1b99197cbaa6/pushes/4/commits" + }, + "pusher": { + "href": "https://spsprodcus4.vssps.visualstudio.com/A7a73fd0c-d080-434d-a8b4-0b4c0217e290/_apis/Identities/07220d5e-521c-683d-982c-726e80086d08" + }, + "refs": { + "href": "https://dev.azure.com/alexander0053/ab1c194f-94fa-4d1a-87ff-e9458637d060/_apis/git/repositories/ba2967cc-02c2-414c-8d10-1b99197cbaa6/refs/heads/master" + } + } + }, + "resourceVersion": "1.0", + "resourceContainers": { + "collection": { + "id": "d54a3f95-82a0-47c4-8444-00da7391d976", + "baseUrl": "https://dev.azure.com/alexander0053/" + }, + "account": { + "id": "7a73fd0c-d080-434d-a8b4-0b4c0217e290", + "baseUrl": "https://dev.azure.com/alexander0053/" + }, + "project": { + "id": "ab1c194f-94fa-4d1a-87ff-e9458637d060", + "baseUrl": "https://dev.azure.com/alexander0053/" + } + }, + "createdDate": "2023-08-09T00:45:49.3448928Z" +} \ No newline at end of file diff --git a/util/webhook/webhook.go b/util/webhook/webhook.go index ca4742e31a1f1..9955540ea04a9 100644 --- a/util/webhook/webhook.go +++ b/util/webhook/webhook.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "github.com/argoproj/argo-cd/v2/util/glob" "html" "net/http" "net/url" @@ -12,13 +11,14 @@ import ( "regexp" "strings" + "github.com/go-playground/webhooks/v6/azuredevops" + "github.com/go-playground/webhooks/v6/bitbucket" + bitbucketserver "github.com/go-playground/webhooks/v6/bitbucket-server" + "github.com/go-playground/webhooks/v6/github" + "github.com/go-playground/webhooks/v6/gitlab" + "github.com/go-playground/webhooks/v6/gogs" gogsclient "github.com/gogits/go-gogs-client" log "github.com/sirupsen/logrus" - "gopkg.in/go-playground/webhooks.v5/bitbucket" - bitbucketserver "gopkg.in/go-playground/webhooks.v5/bitbucket-server" - "gopkg.in/go-playground/webhooks.v5/github" - "gopkg.in/go-playground/webhooks.v5/gitlab" - "gopkg.in/go-playground/webhooks.v5/gogs" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/argoproj/argo-cd/v2/common" @@ -28,6 +28,7 @@ import ( servercache "github.com/argoproj/argo-cd/v2/server/cache" "github.com/argoproj/argo-cd/v2/util/argo" "github.com/argoproj/argo-cd/v2/util/db" + "github.com/argoproj/argo-cd/v2/util/glob" "github.com/argoproj/argo-cd/v2/util/security" "github.com/argoproj/argo-cd/v2/util/settings" ) @@ -41,21 +42,26 @@ type settingsSource interface { // https://github.com/shadow-maint/shadow/blob/master/libmisc/chkname.c#L36 const usernameRegex = `[a-zA-Z0-9_\.][a-zA-Z0-9_\.-]{0,30}[a-zA-Z0-9_\.\$-]?` -var _ settingsSource = &settings.SettingsManager{} +var ( + _ settingsSource = &settings.SettingsManager{} + errBasicAuthVerificationFailed = errors.New("basic auth verification failed") +) type ArgoCDWebhookHandler struct { - repoCache *cache.Cache - serverCache *servercache.Cache - db db.ArgoDB - ns string - appNs []string - appClientset appclientset.Interface - github *github.Webhook - gitlab *gitlab.Webhook - bitbucket *bitbucket.Webhook - bitbucketserver *bitbucketserver.Webhook - gogs *gogs.Webhook - settingsSrc settingsSource + repoCache *cache.Cache + serverCache *servercache.Cache + db db.ArgoDB + ns string + appNs []string + appClientset appclientset.Interface + github *github.Webhook + gitlab *gitlab.Webhook + bitbucket *bitbucket.Webhook + bitbucketserver *bitbucketserver.Webhook + azuredevops *azuredevops.Webhook + azuredevopsAuthHandler func(r *http.Request) error + gogs *gogs.Webhook + settingsSrc settingsSource } func NewHandler(namespace string, applicationNamespaces []string, appClientset appclientset.Interface, set *settings.ArgoCDSettings, settingsSrc settingsSource, repoCache *cache.Cache, serverCache *servercache.Cache, argoDB db.ArgoDB) *ArgoCDWebhookHandler { @@ -79,20 +85,35 @@ func NewHandler(namespace string, applicationNamespaces []string, appClientset a if err != nil { log.Warnf("Unable to init the Gogs webhook") } + azuredevopsWebhook, err := azuredevops.New() + if err != nil { + log.Warnf("Unable to init the Azure DevOps webhook") + } + azuredevopsAuthHandler := func(r *http.Request) error { + if set.WebhookAzureDevOpsUsername != "" && set.WebhookAzureDevOpsPassword != "" { + username, password, ok := r.BasicAuth() + if !ok || username != set.WebhookAzureDevOpsUsername || password != set.WebhookAzureDevOpsPassword { + return errBasicAuthVerificationFailed + } + } + return nil + } acdWebhook := ArgoCDWebhookHandler{ - ns: namespace, - appNs: applicationNamespaces, - appClientset: appClientset, - github: githubWebhook, - gitlab: gitlabWebhook, - bitbucket: bitbucketWebhook, - bitbucketserver: bitbucketserverWebhook, - gogs: gogsWebhook, - settingsSrc: settingsSrc, - repoCache: repoCache, - serverCache: serverCache, - db: argoDB, + ns: namespace, + appNs: applicationNamespaces, + appClientset: appClientset, + github: githubWebhook, + gitlab: gitlabWebhook, + bitbucket: bitbucketWebhook, + bitbucketserver: bitbucketserverWebhook, + azuredevops: azuredevopsWebhook, + azuredevopsAuthHandler: azuredevopsAuthHandler, + gogs: gogsWebhook, + settingsSrc: settingsSrc, + repoCache: repoCache, + serverCache: serverCache, + db: argoDB, } return &acdWebhook @@ -107,6 +128,14 @@ func parseRevision(ref string) string { // the revision, and whether or not this affected origin/HEAD (the default branch of the repository) func affectedRevisionInfo(payloadIf interface{}) (webURLs []string, revision string, change changeInfo, touchedHead bool, changedFiles []string) { switch payload := payloadIf.(type) { + case azuredevops.GitPushEvent: + // See: https://learn.microsoft.com/en-us/azure/devops/service-hooks/events?view=azure-devops#git.push + webURLs = append(webURLs, payload.Resource.Repository.RemoteURL) + revision = parseRevision(payload.Resource.RefUpdates[0].Name) + change.shaAfter = parseRevision(payload.Resource.RefUpdates[0].NewObjectID) + change.shaBefore = parseRevision(payload.Resource.RefUpdates[0].OldObjectID) + touchedHead = payload.Resource.RefUpdates[0].Name == payload.Resource.Repository.DefaultBranch + // unfortunately, Azure DevOps doesn't provide a list of changed files case github.PushPayload: // See: https://developer.github.com/v3/activity/events/types/#pushevent webURLs = append(webURLs, payload.Repository.HTMLURL) @@ -430,6 +459,14 @@ func (a *ArgoCDWebhookHandler) Handler(w http.ResponseWriter, r *http.Request) { var err error switch { + case r.Header.Get("X-Vss-Activityid") != "": + if err = a.azuredevopsAuthHandler(r); err != nil { + if errors.Is(err, errBasicAuthVerificationFailed) { + log.WithField(common.SecurityField, common.SecurityHigh).Infof("Azure DevOps webhook basic auth verification failed") + } + } else { + payload, err = a.azuredevops.Parse(r, azuredevops.GitPushEventType) + } //Gogs needs to be checked before GitHub since it carries both Gogs and (incompatible) GitHub headers case r.Header.Get("X-Gogs-Event") != "": payload, err = a.gogs.Parse(r, gogs.PushEvent) diff --git a/util/webhook/webhook_test.go b/util/webhook/webhook_test.go index cf11162febc6c..b241d7c671841 100644 --- a/util/webhook/webhook_test.go +++ b/util/webhook/webhook_test.go @@ -5,18 +5,19 @@ import ( "encoding/json" "fmt" "io" - "k8s.io/apimachinery/pkg/types" "net/http" "net/http/httptest" "os" "testing" "time" + "k8s.io/apimachinery/pkg/types" + + "github.com/go-playground/webhooks/v6/bitbucket" + bitbucketserver "github.com/go-playground/webhooks/v6/bitbucket-server" + "github.com/go-playground/webhooks/v6/github" + "github.com/go-playground/webhooks/v6/gitlab" gogsclient "github.com/gogits/go-gogs-client" - "gopkg.in/go-playground/webhooks.v5/bitbucket" - bitbucketserver "gopkg.in/go-playground/webhooks.v5/bitbucket-server" - "gopkg.in/go-playground/webhooks.v5/github" - "gopkg.in/go-playground/webhooks.v5/gitlab" "k8s.io/apimachinery/pkg/runtime" kubetesting "k8s.io/client-go/testing" @@ -89,6 +90,22 @@ func TestGitHubCommitEvent(t *testing.T) { hook.Reset() } +func TestAzureDevOpsCommitEvent(t *testing.T) { + hook := test.NewGlobal() + h := NewMockHandler(nil, []string{}) + req := httptest.NewRequest(http.MethodPost, "/api/webhook", nil) + req.Header.Set("X-Vss-Activityid", "abc") + eventJSON, err := os.ReadFile("testdata/azuredevops-git-push-event.json") + assert.NoError(t, err) + req.Body = io.NopCloser(bytes.NewReader(eventJSON)) + w := httptest.NewRecorder() + h.Handler(w, req) + assert.Equal(t, w.Code, http.StatusOK) + expectedLogResult := "Received push event repo: https://dev.azure.com/alexander0053/alex-test/_git/alex-test, revision: master, touchedHead: true" + assert.Equal(t, expectedLogResult, hook.LastEntry().Message) + hook.Reset() +} + // TestGitHubCommitEvent_MultiSource_Refresh makes sure that a webhook will refresh a multi-source app when at least // one source matches. func TestGitHubCommitEvent_MultiSource_Refresh(t *testing.T) { From a4eeb0039573d0621a2af43cda07d33b9413786e Mon Sep 17 00:00:00 2001 From: Alexander Matyushentsev Date: Wed, 9 Aug 2023 16:52:28 -0700 Subject: [PATCH 3/5] fix: api server fails to call dex with istio (#14995) Signed-off-by: Alexander Matyushentsev --- manifests/base/dex/argocd-dex-server-service.yaml | 1 + manifests/ha/install.yaml | 3 ++- manifests/ha/namespace-install.yaml | 3 ++- manifests/install.yaml | 3 ++- manifests/namespace-install.yaml | 3 ++- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/manifests/base/dex/argocd-dex-server-service.yaml b/manifests/base/dex/argocd-dex-server-service.yaml index 7aeabca1e674e..cffbf006ae624 100644 --- a/manifests/base/dex/argocd-dex-server-service.yaml +++ b/manifests/base/dex/argocd-dex-server-service.yaml @@ -9,6 +9,7 @@ metadata: spec: ports: - name: http + appProtocol: TCP protocol: TCP port: 5556 targetPort: 5556 diff --git a/manifests/ha/install.yaml b/manifests/ha/install.yaml index 26801daea28a2..7716ec7e68bc9 100644 --- a/manifests/ha/install.yaml +++ b/manifests/ha/install.yaml @@ -19769,7 +19769,8 @@ metadata: name: argocd-dex-server spec: ports: - - name: http + - appProtocol: TCP + name: http port: 5556 protocol: TCP targetPort: 5556 diff --git a/manifests/ha/namespace-install.yaml b/manifests/ha/namespace-install.yaml index 9c6be39785fec..03e2dd32f2395 100644 --- a/manifests/ha/namespace-install.yaml +++ b/manifests/ha/namespace-install.yaml @@ -1275,7 +1275,8 @@ metadata: name: argocd-dex-server spec: ports: - - name: http + - appProtocol: TCP + name: http port: 5556 protocol: TCP targetPort: 5556 diff --git a/manifests/install.yaml b/manifests/install.yaml index 6a5afae6a87ae..370b3f22b35c8 100644 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -18973,7 +18973,8 @@ metadata: name: argocd-dex-server spec: ports: - - name: http + - appProtocol: TCP + name: http port: 5556 protocol: TCP targetPort: 5556 diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml index 415ea143c5b64..ac244c7ccfe1d 100644 --- a/manifests/namespace-install.yaml +++ b/manifests/namespace-install.yaml @@ -479,7 +479,8 @@ metadata: name: argocd-dex-server spec: ports: - - name: http + - appProtocol: TCP + name: http port: 5556 protocol: TCP targetPort: 5556 From 51164e82f4a34345a01515bc0c6b072a967922c0 Mon Sep 17 00:00:00 2001 From: Vladimir <31961982+zvlb@users.noreply.github.com> Date: Thu, 10 Aug 2023 05:28:07 +0300 Subject: [PATCH 4/5] fix(ui): Update default and max count for maxCookieNumber (#14979) * Update default and max count for maxCookieNumber Signed-off-by: zvlb --- util/http/http.go | 4 ++-- util/http/http_test.go | 10 +++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/util/http/http.go b/util/http/http.go index 42981d62867fa..2572e739f009d 100644 --- a/util/http/http.go +++ b/util/http/http.go @@ -18,8 +18,8 @@ import ( const maxCookieLength = 4093 // max number of chunks a cookie can be broken into. To be compatible with -// widest range of browsers, we shouldn't create more than 30 cookies per domain -var maxCookieNumber = env.ParseNumFromEnv(common.EnvMaxCookieNumber, 10, 0, 30) +// widest range of browsers, you shouldn't create more than 30 cookies per domain +var maxCookieNumber = env.ParseNumFromEnv(common.EnvMaxCookieNumber, 20, 0, math.MaxInt64) // MakeCookieMetadata generates a string representing a Web cookie. Yum! func MakeCookieMetadata(key, value string, flags ...string) ([]string, error) { diff --git a/util/http/http_test.go b/util/http/http_test.go index cb37f74b39716..9655c5b42c249 100644 --- a/util/http/http_test.go +++ b/util/http/http_test.go @@ -15,10 +15,18 @@ func TestCookieMaxLength(t *testing.T) { // keys will be of format foo, foo-1, foo-2 .. cookies, err = MakeCookieMetadata("foo", strings.Repeat("_", (maxCookieLength-5)*maxCookieNumber)) - assert.EqualError(t, err, "the authentication token is 40880 characters long and requires 11 cookies but the max number of cookies is 10. Contact your Argo CD administrator to increase the max number of cookies") + assert.EqualError(t, err, "the authentication token is 81760 characters long and requires 21 cookies but the max number of cookies is 20. Contact your Argo CD administrator to increase the max number of cookies") assert.Equal(t, 0, len(cookies)) } +func TestCookieWithAttributes(t *testing.T) { + flags := []string{"SameSite=lax", "httpOnly"} + + cookies, err := MakeCookieMetadata("foo", "bar", flags...) + assert.NoError(t, err) + assert.Equal(t, "foo=bar; SameSite=lax; httpOnly", cookies[0]) +} + func TestSplitCookie(t *testing.T) { cookieValue := strings.Repeat("_", (maxCookieLength-6)*4) cookies, err := MakeCookieMetadata("foo", cookieValue) From c31da643aad81c598b824a686d7d30bb5cbdfda5 Mon Sep 17 00:00:00 2001 From: Kevin Yue Date: Thu, 10 Aug 2023 10:29:45 +0800 Subject: [PATCH 5/5] fix: correct the swagger ui link to support --rootpath (#14845) Signed-off-by: Kevin Yue --- ui/src/app/help/components/help.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/help/components/help.tsx b/ui/src/app/help/components/help.tsx index d27427c089e67..54da9758f7fa6 100644 --- a/ui/src/app/help/components/help.tsx +++ b/ui/src/app/help/components/help.tsx @@ -66,7 +66,7 @@ export const Help = () => {

You want to develop against Argo CD's API?

- + Open the API docs