Compare commits

...

67 Commits

Author SHA1 Message Date
github-actions[bot]
4d70c51e64 Bump version to 2.12.7 (#20668)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: crenshaw-dev <350466+crenshaw-dev@users.noreply.github.com>
2024-11-05 10:29:34 -05:00
gcp-cherry-pick-bot[bot]
0cae929ae1 docs(rbac): clarify glob pattern behavior for fine-grain RBAC (#20624) (#20627)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-10-31 14:11:28 -04:00
gcp-cherry-pick-bot[bot]
e48878b11e fix(diff): avoid cache miss in server-side diff (#20605) (#20609)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-10-30 21:36:56 -04:00
Kunho Lee
cacb06a5e5 fix: check err before use schedule and duration (#20043) (#20371) 2024-10-24 08:13:37 -04:00
gcp-cherry-pick-bot[bot]
a41f868dc0 fix(ui): fix create app panel reappear after closed (#19717) (#20507)
Signed-off-by: henry.liu <henry.liu@daocloud.io>
Co-authored-by: Henry Liu <henry.liu@daocloud.io>
2024-10-23 10:13:25 -07:00
Alexander Matyushentsev
32ef2e5f1e fix: support managing cluster with multiple argocd instances and annotation based tracking (#20222) (#20482)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2024-10-22 00:52:18 -07:00
Alexander Matyushentsev
db5876fb3b feat: support using exponential backoff between self heal attempts (#20275) (#20479)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2024-10-22 00:51:17 -07:00
github-actions[bot]
4dab5bd6a6 Bump version to 2.12.6 (#20455)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: crenshaw-dev <350466+crenshaw-dev@users.noreply.github.com>
2024-10-18 13:27:10 -04:00
gcp-cherry-pick-bot[bot]
358930be06 fix: don't disable buttons for multi-source apps (#20446) (#20448)
With #20381 multi-source apps were not taken into account 🤦

Fixes #20445.

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2024-10-18 11:14:36 -04:00
gcp-cherry-pick-bot[bot]
68f63e7a2b fix(diff): avoid cache miss in server-side diff (#20423) (#20424) (#20450)
* fix(diff): avoid cache miss in server-side diff (#20423)



* fix silly mistakes



---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-10-18 11:13:00 -04:00
github-actions[bot]
85be9a4f22 Bump version to 2.12.5 (#20437)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: crenshaw-dev <350466+crenshaw-dev@users.noreply.github.com>
2024-10-17 16:12:16 -04:00
gcp-cherry-pick-bot[bot]
53bb2e4109 fix(ci): handle new k3s test version matrix (#20223) (#20427) (#20434)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-10-17 16:06:21 -04:00
Blake Pettersson
6e6857ee8b fix(ui): source can in fact be undefined (#20381) (#20420)
* fix(ui): source can in fact be `undefined`

The assumption that a source is always there is not always true. To
repro, create an app-of-apps containing a single app without any `source`
present. In the UI this will crash, horribly. This PR fixes that so
that instead of crashing the user will get useful info indicating what
is wrong with the app.



* chore(ui): some cr tweaks



* chore(ui): some cr tweaks



---------

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
2024-10-17 07:59:52 -04:00
Linghao Su
5f5fb0d2de fix(controller/ui): fix pod with sidecar state (#19843) (#20394)
* fix(controller): change pod status calculate with sidecar



* fix(controller): add restartable sidecar count in total container display



* fix(controller): update info test case conditions




* fix(controller): add more test case to cover more conditions



* fix(ui): check is condition exist before for of



---------

Signed-off-by: linghaoSu <linghao.su@daocloud.io>
Signed-off-by: Linghao Su <slh001@live.cn>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-10-16 10:52:19 -04:00
Paul Larsen
e2eb54c102 fix: cherrypick semver fix #20096 into 2.12 (#20215)
* cherrypick

Signed-off-by: Paul Larsen <pnvlarsen@gmail.com>

* Update util/git/client_test.go

Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
Signed-off-by: Paul Larsen <pnvlarsen@gmail.com>

* rm bad metrics cherry pick

Signed-off-by: Paul Larsen <pnvlarsen@gmail.com>

---------

Signed-off-by: Paul Larsen <pnvlarsen@gmail.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2024-10-10 11:43:31 +03:00
Netanel Kadosh
3ff57d288d fix(cli): cherrypick Redis password fix #19599 into 2.12 (#20262)
* fix(cli): add optional password setting for headless redis client (#19035) (#19039)

* chore: add optional password setting for headless redis client

Signed-off-by: Rachel Sheikh <rsheikh@squareup.com>

* fix: remove import cycle

Signed-off-by: Rachel Sheikh <rsheikh@squareup.com>

* fix: add shared SetOptionalRedisPasswordFromKubeConfig method

Signed-off-by: Rachel Sheikh <rsheikh@squareup.com>

* fix: export redis consts

Signed-off-by: Rachel Sheikh <rsheikh@squareup.com>

* test: add test cases for SetOptionalRedisPasswordFromKubeConfig()

Signed-off-by: Rachel Sheikh <rsheikh@squareup.com>

* chore: go mod tidy

Signed-off-by: Rachel Sheikh <rsheikh@squareup.com>

* fix: use require instead of assert

Signed-off-by: Rachel Sheikh <rsheikh@squareup.com>

* fix: Update common/common.go

Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Signed-off-by: Rachel Sheikh <sheikhrachel97@gmail.com>

---------

Signed-off-by: Rachel Sheikh <rsheikh@squareup.com>
Signed-off-by: Rachel Sheikh <sheikhrachel97@gmail.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

* fix: Add redis password to `forwardCacheClient` struct (#19599)

Signed-off-by: Netanel Kadosh <kadoshnetanel@gmail.com>

---------

Signed-off-by: Rachel Sheikh <rsheikh@squareup.com>
Signed-off-by: Rachel Sheikh <sheikhrachel97@gmail.com>
Signed-off-by: Netanel Kadosh <kadoshnetanel@gmail.com>
Co-authored-by: Rachel Sheikh <sheikhrachel97@gmail.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-10-09 13:58:16 +03:00
Michael Crenshaw
a05b042876 chore(tests): fix erroneous cherry-pick (#20274)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-10-07 14:29:00 -04:00
Alexandre Gaudreault
50271e10c0 fix: prevent crash during timer expiration after stream is closed (#19917) (#20271)
Reorder ticker stop and close merge to prevent send(true) happens after merge is closed, in rare situation when the timer expires exactly at the point between close(merge) and ticker.Stop()

Signed-off-by: morapet <peter@moran.sk>
Co-authored-by: morapet <peter@moran.sk>
2024-10-07 14:02:58 -04:00
gcp-cherry-pick-bot[bot]
ee4f09ebd2 docs(ui): sorting version (#20181) (#20204)
Signed-off-by: nueavv <nuguni@kakao.com>
Co-authored-by: 1102 <90682513+nueavv@users.noreply.github.com>
2024-10-02 13:41:27 -04:00
github-actions[bot]
27d1e641b6 Bump version to 2.12.4 (#20115)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: ishitasequeira <46771830+ishitasequeira@users.noreply.github.com>
2024-09-26 02:25:44 -04:00
gcp-cherry-pick-bot[bot]
b76a09e070 fix: CVE-2024-45296 Backtracking regular expressions cause ReDoS by upgrading path-to-regexp from 1.8.0 to 1.9.0 (#20087) (#20090)
Signed-off-by: Cheng Fang <cfang@redhat.com>
Co-authored-by: Cheng Fang <cfang@redhat.com>
2024-09-24 23:28:03 -04:00
Ishita Sequeira
ff3ef717e2 cherry-pick chore(deps-dev): bump webpack from 5.84.1 to 5.94.0 in /ui (#20056)
Signed-off-by: Ishita Sequeira <ishiseq29@gmail.com>
2024-09-23 09:55:15 -04:00
gcp-cherry-pick-bot[bot]
08fe6f5aea chore(deps): bump dompurify from 2.3.6 to 2.5.6 in /ui (#19955) (#20016)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 2.3.6 to 2.5.6.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/2.3.6...2.5.6)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-19 21:18:42 -04:00
gcp-cherry-pick-bot[bot]
1568165166 fix(appset): Fix perpetual appset reconciliation (#19822) (#19995)
Golang maps do not guarantee the order of the application resources
from the applicationset which causes rapid sync activity for the applicationset
as the objects and hence their resourceVersions are updated after each reconcile loop.

This then triggers reconciliation of all objects watching the
ApplicationSet.

In order to prevent this behaviour, ensure that the ApplicationSet
reconciler provides an idempotent list of resources, ensuring objects
are not updated.

Fixes: #19757

Signed-off-by: Thibault Jamet <thibault.jamet@adevinta.com>
Signed-off-by: Fabián Sellés <fabian.selles@adevinta.com>
Co-authored-by: Thibault Jamet <tjamet@users.noreply.github.com>
Co-authored-by: Fabian Selles <fabian.sellesrosa@gmail.com>
Co-authored-by: Ariadna Rouco <ariadna.rouco@adevinta.com>
2024-09-19 13:11:28 +05:30
Cheng Fang
8590550a22 chore(deps): bump express from 4.19.2 to 4.20.0 in /ui (#19883) (#19987) 2024-09-18 16:18:26 -04:00
gcp-cherry-pick-bot[bot]
d56ef7641c fix: diffing should not fail if resource fail schema validation (#19714) (#19729)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2024-08-29 06:14:49 -10:00
gcp-cherry-pick-bot[bot]
02b8336890 docs: note cluster scoping changes in 2.12x (#19684) (#19702)
* docs: note cluster scoping changes in 2.12x

Related to #18748,#19585 and #19587.



* docs: add note in projects doc.



---------

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2024-08-28 13:30:13 +05:30
github-actions[bot]
6b9cd828c6 Bump version to 2.12.3 (#19694)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: ishitasequeira <46771830+ishitasequeira@users.noreply.github.com>
2024-08-27 07:56:43 -04:00
gcp-cherry-pick-bot[bot]
cafd35cea7 fix(AnyNameSpaceRegex): Additional Functions Glob to Regexexp (#19516) (#19665)
Signed-off-by: Arthur <arthur@arthurvardevanyan.com>
Co-authored-by: Arthur Vardevanyan <arthur@arthurvardevanyan.com>
2024-08-23 15:19:13 -04:00
gcp-cherry-pick-bot[bot]
343dec049a feat(sourceNamespace): Regex Support (#19016) (#19017) (#19664)
* feat(sourceNamespace): Regex Support



* feat(sourceNamespace): Separate exactMatch into patternMatch



---------

Signed-off-by: Arthur <arthur@arthurvardevanyan.com>
Co-authored-by: Arthur Vardevanyan <arthur@arthurvardevanyan.com>
2024-08-23 08:53:48 -04:00
github-actions[bot]
560953c37b Bump version to 2.12.2 (#19657)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: crenshaw-dev <350466+crenshaw-dev@users.noreply.github.com>
2024-08-22 23:29:03 -04:00
rumstead
7244c2d5e5 fix(appset): remove cache references (#19652)
Signed-off-by: rumstead <37445536+rumstead@users.noreply.github.com>
2024-08-22 18:09:56 -04:00
gcp-cherry-pick-bot[bot]
b068220503 fix(appset): informer is not a kubernetes informer (#18905) (#19618) (#19636)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-08-21 20:17:21 -08:00
gcp-cherry-pick-bot[bot]
c873d5c68a fix: Floating title content incorrect for multi-sources (#17274) (#19623) (#19627)
Signed-off-by: Keith Chong <kykchong@redhat.com>
Co-authored-by: Keith Chong <kykchong@redhat.com>
2024-08-21 19:31:48 -04:00
gcp-cherry-pick-bot[bot]
88f85daf52 fix: Parse hostname correctly from repoURL to fetch correct CA cert (#19488) (#19602)
Signed-off-by: Siddhesh Ghadi <sghadi1203@gmail.com>
Co-authored-by: Siddhesh Ghadi <61187612+svghadi@users.noreply.github.com>
Co-authored-by: Jann Fischer <jann@mistrust.net>
2024-08-21 00:48:58 -04:00
github-actions[bot]
26b2039a55 Bump version to 2.12.1 (#19568)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: ishitasequeira <46771830+ishitasequeira@users.noreply.github.com>
2024-08-16 12:39:58 -04:00
Ishita Sequeira
952838cdde fix(appset): cherry-pick - fix appset-in-any-namespace issue with git generators (#19558)
* fix appset-in-any-namespace issue with git generators

Signed-off-by: Ishita Sequeira <ishiseq29@gmail.com>

* fix lint issue

Signed-off-by: Ishita Sequeira <ishiseq29@gmail.com>

---------

Signed-off-by: Ishita Sequeira <ishiseq29@gmail.com>
2024-08-15 17:14:05 -04:00
gcp-cherry-pick-bot[bot]
7af4526666 fix: appset gpg limitation for templated project fields (#19492) (#19534)
* document templating project field while using applicationset git generator and signature verification



* revert changes to generated mocks



* Add check for templated project field and add limitation to the docs



* optimize checks and rephrase documentation



* remove unwanted variable declaration



* Add unit tests



---------

Signed-off-by: Ishita Sequeira <ishiseq29@gmail.com>
Co-authored-by: Ishita Sequeira <46771830+ishitasequeira@users.noreply.github.com>
2024-08-14 12:27:32 -04:00
gcp-cherry-pick-bot[bot]
b156b61e22 fix(appset): missing permissions for cluster install (#19059) (#19430) (#19435)
Signed-off-by: Dmitry Khodorov <el1191@woyd.ru>
Co-authored-by: Dmitry Khodorov <el1191@woyd.ru>
2024-08-08 00:35:24 -04:00
Jae Ryong Song
fd478450e6 fix: docs version regex changed (#18756) (#19352)
Signed-off-by: jasong <jasong@student.42seoul.kr>
2024-08-07 20:36:13 -04:00
github-actions[bot]
ec30a48bce Bump version to 2.12.0 (#19383)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: ishitasequeira <46771830+ishitasequeira@users.noreply.github.com>
2024-08-05 09:24:37 -04:00
gcp-cherry-pick-bot[bot]
57e61b2d8a feat: Add custom health check for cluster-api AWSManagedControlPlane (#19304) (#19360)
* add custom health check for awsmanagedcontrolplane



* chore(deps): bump github.com/aws/aws-sdk-go from 1.55.3 to 1.55.4 (#19295)

Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.55.3 to 1.55.4.
- [Release notes](https://github.com/aws/aws-sdk-go/releases)
- [Commits](https://github.com/aws/aws-sdk-go/compare/v1.55.3...v1.55.4)

---
updated-dependencies:
- dependency-name: github.com/aws/aws-sdk-go
  dependency-type: direct:production
  update-type: version-update:semver-patch
...





* add new line at the end of health_test.yaml file



---------

Signed-off-by: Iulian Taiatu <itaiatu@adobe.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: itaiatu <140485521+itaiatu@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-02 10:55:46 -07:00
github-actions[bot]
6f2ae0dd46 Bump version to 2.12.0-rc5 (#19349)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: ishitasequeira <46771830+ishitasequeira@users.noreply.github.com>
2024-08-01 19:24:32 -04:00
gcp-cherry-pick-bot[bot]
3e31ce9470 fix: ArgoCD 2.11 - Loop of PATCH calls to Application objects (#19340) (#19343)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2024-08-01 16:11:47 -04:00
gcp-cherry-pick-bot[bot]
dee59f3002 feat(rbac): allow validation of fine-grained policy in project (#19338) (#19339)
Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
Co-authored-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
2024-08-01 10:37:55 -04:00
pasha-codefresh
d6c37aab88 Merge commit from fork (#19330) 2024-08-01 01:11:15 +03:00
pasha-codefresh
eaa1972e69 feat: webhook ddos -2.12 (#19329) 2024-08-01 00:53:59 +03:00
gcp-cherry-pick-bot[bot]
004cabba47 fix(applicationset): ensure that older applicationStatus is updated with new required values (#19165) (#19216)
Signed-off-by: wparr-circle <william.parr@circle.com>
Co-authored-by: William Parr <william.parr@circle.com>
2024-07-25 10:12:31 -04:00
gcp-cherry-pick-bot[bot]
d9263101fc chore: revert "feat: removed legacy app tracking label (#13203)" (cherry-pick #19196)
This reverts commit 4d8436b7e1.

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-24 16:31:36 -04:00
Blake Pettersson
cec8504df2 fix: cherry-pick #18761 (v2.12) (#19109)
* fix(applicationset): use requeue after if generate app errors out (#18761)

The `GenerateApplications` can call to external resources like Github
API for instance which might be rate limited or fail. If those requests
somehow fail we should requeue them after some time like (same
reason as e98d3b2a87/applicationset/controllers/applicationset_controller.go (L154)).

For instance, in our environments we were rate limited by Github and the ArgoCD
applicationset controller was logging the following error about every
second or less for every application set using the pull request generator
that we have:
```
time="2024-06-21T14:17:15Z" level=error msg="error generating params" error="error listing repos: error listing pull requests for LedgerHQ/xxx: GET https://api.github.com/repos/LedgerHQ/xxx/pulls?per_page=100: 403 API rate limit exceeded for installation ID xxx. If you reach out to GitHub Support for help, please include the request ID xxx and timestamp 2024-06-xx xxx UTC. [rate reset in 8m18s]" generator="&{0xc000d652c0 0x289a100 {0xc00087bdd0}  [] true}"
time="2024-06-21T14:17:15Z" level=error msg="error generating application from params" applicationset=argocd/xxx error="error listing repos: error listing pull requests for LedgerHQ/xxxx: GET https://api.github.com/repos/LedgerHQ/xxx/pulls?per_page=100: 403 API rate limit exceeded for installation ID xxx. If you reach out to GitHub Support for help, please include the request ID xxx and timestamp 2024-06-xx xxx UTC. [rate reset in 8m18s]" generator="{nil nil nil nil nil &PullRequestGenerator{Github:&PullRequestGeneratorGithub{Owner:LedgerHQ,Repo:xxx,API:,TokenRef:nil,AppSecretName:xxxx,Labels:[argocd/preview],},GitLab:nil,Gitea:nil,BitbucketServer:nil,Filters:[]PullRequestGeneratorFilter{},RequeueAfterSeconds:*1800,Template:ApplicationSetTemplate{ApplicationSetTemplateMeta:ApplicationSetTemplateMeta{Name:,Namespace:,Labels:map[string]string{},Annotations:map[string]string{},Finalizers:[],},Spec:ApplicationSpec{Source:nil,Destination:ApplicationDestination{Server:,Namespace:,Name:,},Project:,SyncPolicy:nil,IgnoreDifferences:[]ResourceIgnoreDifferences{},Info:[]Info{},RevisionHistoryLimit:nil,Sources:[]ApplicationSource{},},},Bitbucket:nil,AzureDevOps:nil,} nil nil nil nil}"
```

Signed-off-by: Arthur Outhenin-Chalandre <arthur.outhenin-chalandre@ledger.fr>

* test: cherry-pick fixes

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>

* chore: please the linter

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>

---------

Signed-off-by: Arthur Outhenin-Chalandre <arthur.outhenin-chalandre@ledger.fr>
Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: Arthur Outhenin-Chalandre <arthur.outhenin-chalandre@ledger.fr>
2024-07-18 22:31:40 -04:00
github-actions[bot]
0704aa6506 Bump version to 2.12.0-rc4 (#19060)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: crenshaw-dev <350466+crenshaw-dev@users.noreply.github.com>
2024-07-15 13:28:20 -04:00
Alexandre Gaudreault
511d6e371c chore: bump gitops-engine (#19056)
Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
2024-07-15 12:08:15 -04:00
gcp-cherry-pick-bot[bot]
065f8494cf fix(cli): Get Redis password from secret in loadClusters() (#18951) (#18955)
* Get Redis password from secret in `loadClusters()`



* feat: support redis password in admin stats command



* Simplify code



---------

Signed-off-by: David Wu <155603967+david-wu-octopus@users.noreply.github.com>
Signed-off-by: pashakostohrys <pavel@codefresh.io>
Co-authored-by: david-wu-octopus <155603967+david-wu-octopus@users.noreply.github.com>
Co-authored-by: pashakostohrys <pavel@codefresh.io>
2024-07-05 11:12:24 -04:00
gcp-cherry-pick-bot[bot]
beacacc43d fix(appset): missing permissions (#18829) (#18943) (#18944) 2024-07-04 10:44:58 -04:00
gcp-cherry-pick-bot[bot]
81d454215d remove unwanted updating of source-position in app set command (#18887) (#18897)
Signed-off-by: ishitasequeira <ishiseq29@gmail.com>
Co-authored-by: Ishita Sequeira <46771830+ishitasequeira@users.noreply.github.com>
2024-07-02 13:14:21 -04:00
github-actions[bot]
1237d4ea17 Bump version to 2.12.0-rc3 (#18893)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: crenshaw-dev <350466+crenshaw-dev@users.noreply.github.com>
2024-07-02 13:04:14 -04:00
Michael Crenshaw
b211d3e038 chore: bump gitops-engine (#18871)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-07-01 13:22:54 -04:00
gcp-cherry-pick-bot[bot]
50c32b53f0 docs: Fix .path to .path.segments go template (#18872) (#18873)
Signed-off-by: Jaeseok Lee <devsunb@gmail.com>
Co-authored-by: Jaeseok Lee <devsunb@gmail.com>
2024-07-01 10:52:36 -04:00
gcp-cherry-pick-bot[bot]
444d332d0a fix: Handle nil health check in post-delete hooks (#18270) (#18767) (#18828)
* fix: Handle nil health check in post-delete hooks



* test: Validate deletion of RBAC resources for post-delete hook



* Update appcontroller_test.go



---------

Signed-off-by: Yonatan Sasson <yonatanxd7@gmail.com>
Signed-off-by: Yonatan Sasson <107778824+Yuni-sa@users.noreply.github.com>
Co-authored-by: Yonatan Sasson <107778824+Yuni-sa@users.noreply.github.com>
2024-06-26 17:21:19 +03:00
gcp-cherry-pick-bot[bot]
573c771d2b fix(webhook): bitbucket and azure not triggering refresh (#18289) (#18765) (#18818)
* fix(webhook): bitbucket and azure webhook not triggering refresh



* update unit test



* fix merge



* adjust logic for reposerver using ls-remote



---------

Signed-off-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
Co-authored-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
2024-06-26 08:48:16 -04:00
Michael Crenshaw
e616dff2d1 fix(appset): revert "keep reconciling even when params error occurred" (#17062) (#18781)
This reverts commit 86369ca71d.

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-06-25 15:33:59 -04:00
gcp-cherry-pick-bot[bot]
e36e19de4e chore(deps): bump github.com/hashicorp/go-retryablehttp (#18805) (#18811)
Bumps [github.com/hashicorp/go-retryablehttp](https://github.com/hashicorp/go-retryablehttp) from 0.7.4 to 0.7.7.
- [Changelog](https://github.com/hashicorp/go-retryablehttp/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/go-retryablehttp/compare/v0.7.4...v0.7.7)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/go-retryablehttp
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-25 11:29:12 -04:00
github-actions[bot]
bbfa79ad9e Bump version to 2.12.0-rc2 (#18802)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: crenshaw-dev <350466+crenshaw-dev@users.noreply.github.com>
2024-06-24 16:31:31 -04:00
gcp-cherry-pick-bot[bot]
00fc93b8b2 fix: Bug in edit support in Sources tab; Input to loader (#17588) (#18800) (#18801)
Co-authored-by: Keith Chong <kykchong@redhat.com>
2024-06-24 14:02:06 -04:00
gcp-cherry-pick-bot[bot]
16c20a84b8 fix(server): could not find source for metadata revision (#18744) (#18763) (#18782)
* fix(server): could not find source for metadata revision (#18744)



* lint



* the linter behaves poorly



* fix test



* share logic with chart endpoint



* more intuitive check



* remove debug line



---------

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2024-06-24 09:55:56 -04:00
gcp-cherry-pick-bot[bot]
2a9a62eeb7 fix(ui): set project to empty string if undefined (#18732) (#18733)
There are some situations where the project will be `undefined`. When
that happens, attempting to delete a repo won't be possible, since
the backend will be looking for a project with the literal name
`undefined`. To fix this, set an empty string for `undefined|null`
values.

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2024-06-20 12:00:39 +03:00
github-actions[bot]
fe60670885 Bump version to 2.12.0-rc1 (#18716)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: ishitasequeira <46771830+ishitasequeira@users.noreply.github.com>
2024-06-18 09:09:23 -04:00
130 changed files with 5417 additions and 2023 deletions

View File

@@ -39,6 +39,7 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Beez Innovation Labs](https://www.beezlabs.com/)
1. [Bedag Informatik AG](https://www.bedag.ch/)
1. [Beleza Na Web](https://www.belezanaweb.com.br/)
1. [Believable Bots](https://believablebots.io)
1. [BigPanda](https://bigpanda.io)
1. [BioBox Analytics](https://biobox.io)
1. [BMW Group](https://www.bmwgroup.com/)

View File

@@ -1 +1 @@
2.12.0
2.12.7

View File

@@ -18,6 +18,7 @@ import (
"context"
"fmt"
"reflect"
"sort"
"strings"
"time"
@@ -31,11 +32,9 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
k8scache "k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -88,7 +87,6 @@ type ApplicationSetReconciler struct {
SCMRootCAPath string
GlobalPreservedAnnotations []string
GlobalPreservedLabels []string
Cache cache.Cache
}
// +kubebuilder:rbac:groups=argoproj.io,resources=applicationsets,verbs=get;list;watch;create;update;patch;delete
@@ -126,23 +124,26 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
return ctrl.Result{}, nil
}
if err := r.migrateStatus(ctx, &applicationSetInfo); err != nil {
logCtx.Errorf("failed to migrate status subresource %v", err)
return ctrl.Result{}, err
}
// Log a warning if there are unrecognized generators
_ = utils.CheckInvalidGenerators(&applicationSetInfo)
// desiredApplications is the main list of all expected Applications from all generators in this appset.
desiredApplications, applicationSetReason, generatorsErr := r.generateApplications(logCtx, applicationSetInfo)
if generatorsErr != nil {
desiredApplications, applicationSetReason, err := r.generateApplications(logCtx, applicationSetInfo)
if err != nil {
_ = r.setApplicationSetStatusCondition(ctx,
&applicationSetInfo,
argov1alpha1.ApplicationSetCondition{
Type: argov1alpha1.ApplicationSetConditionErrorOccurred,
Message: generatorsErr.Error(),
Message: err.Error(),
Reason: string(applicationSetReason),
Status: argov1alpha1.ApplicationSetConditionStatusTrue,
}, parametersGenerated,
)
if len(desiredApplications) < 1 {
return ctrl.Result{}, generatorsErr
}
return ctrl.Result{RequeueAfter: ReconcileRequeueOnValidationError}, err
}
parametersGenerated = true
@@ -320,7 +321,7 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
requeueAfter := r.getMinRequeueAfter(&applicationSetInfo)
if len(validateErrors) == 0 && generatorsErr == nil {
if len(validateErrors) == 0 {
if err := r.setApplicationSetStatusCondition(ctx,
&applicationSetInfo,
argov1alpha1.ApplicationSetCondition{
@@ -580,7 +581,7 @@ func (r *ApplicationSetReconciler) applyTemplatePatch(app *argov1alpha1.Applicat
func ignoreNotAllowedNamespaces(namespaces []string) predicate.Predicate {
return predicate.Funcs{
CreateFunc: func(e event.CreateEvent) bool {
return glob.MatchStringInList(namespaces, e.Object.GetNamespace(), false)
return glob.MatchStringInList(namespaces, e.Object.GetNamespace(), glob.REGEXP)
},
}
}
@@ -623,25 +624,6 @@ func (r *ApplicationSetReconciler) SetupWithManager(mgr ctrl.Manager, enableProg
Complete(r)
}
func (r *ApplicationSetReconciler) updateCache(ctx context.Context, obj client.Object, logger *log.Entry) {
informer, err := r.Cache.GetInformer(ctx, obj)
if err != nil {
logger.Errorf("failed to get informer: %v", err)
return
}
// The controller runtime abstract away informers creation
// so unfortunately could not find any other way to access informer store.
k8sInformer, ok := informer.(k8scache.SharedInformer)
if !ok {
logger.Error("informer is not a kubernetes informer")
return
}
if err := k8sInformer.GetStore().Update(obj); err != nil {
logger.Errorf("failed to update cache: %v", err)
return
}
}
// createOrUpdateInCluster will create / update application resources in the cluster.
// - For new applications, it will call create
// - For existing application, it will call update
@@ -743,7 +725,6 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
}
continue
}
r.updateCache(ctx, found, appLog)
if action != controllerutil.OperationResultNone {
// Don't pollute etcd with "unchanged Application" events
@@ -910,7 +891,6 @@ func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx conte
if err := r.Client.Patch(ctx, updated, patch); err != nil {
return fmt.Errorf("error updating finalizers: %w", err)
}
r.updateCache(ctx, updated, appLog)
// Application must have updated list of finalizers
updated.DeepCopyInto(app)
@@ -1153,6 +1133,13 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
} else {
// we have an existing AppStatus
currentAppStatus = applicationSet.Status.ApplicationStatus[idx]
// upgrade any existing AppStatus that might have been set by an older argo-cd version
// note: currentAppStatus.TargetRevisions may be set to empty list earlier during migrations,
// to prevent other usage of r.Client.Status().Update to fail before reaching here.
if currentAppStatus.TargetRevisions == nil || len(currentAppStatus.TargetRevisions) == 0 {
currentAppStatus.TargetRevisions = app.Status.GetRevisions()
}
}
appOutdated := false
@@ -1350,6 +1337,27 @@ func findApplicationStatusIndex(appStatuses []argov1alpha1.ApplicationSetApplica
return -1
}
// migrateStatus run migrations on the status subresource of ApplicationSet early during the run of ApplicationSetReconciler.Reconcile
// this handles any defaulting of values - which would otherwise cause the references to r.Client.Status().Update to fail given missing required fields.
func (r *ApplicationSetReconciler) migrateStatus(ctx context.Context, appset *argov1alpha1.ApplicationSet) error {
update := false
if statusList := appset.Status.ApplicationStatus; statusList != nil {
for idx := range statusList {
if statusList[idx].TargetRevisions == nil {
statusList[idx].TargetRevisions = []string{}
update = true
}
}
}
if update {
if err := r.Client.Status().Update(ctx, appset); err != nil {
return fmt.Errorf("unable to set application set status: %w", err)
}
}
return nil
}
func (r *ApplicationSetReconciler) updateResourcesStatus(ctx context.Context, logCtx *log.Entry, appset *argov1alpha1.ApplicationSet, apps []argov1alpha1.Application) error {
statusMap := getResourceStatusMap(appset)
statusMap = buildResourceStatus(statusMap, apps)
@@ -1358,6 +1366,9 @@ func (r *ApplicationSetReconciler) updateResourcesStatus(ctx context.Context, lo
for _, status := range statusMap {
statuses = append(statuses, status)
}
sort.Slice(statuses, func(i, j int) bool {
return statuses[i].Name < statuses[j].Name
})
appset.Status.Resources = statuses
namespacedName := types.NamespacedName{Namespace: appset.Namespace, Name: appset.Name}

View File

@@ -5,10 +5,13 @@ import (
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
"testing"
"time"
"github.com/argoproj/argo-cd/v2/applicationset/generators/mocks"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@@ -20,11 +23,8 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
kubefake "k8s.io/client-go/kubernetes/fake"
k8scache "k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/cache"
crtcache "sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/client"
crtclient "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -45,34 +45,6 @@ import (
"github.com/argoproj/argo-cd/v2/pkg/apis/application"
)
type fakeStore struct {
k8scache.Store
}
func (f *fakeStore) Update(obj interface{}) error {
return nil
}
type fakeInformer struct {
k8scache.SharedInformer
}
func (f *fakeInformer) AddIndexers(indexers k8scache.Indexers) error {
return nil
}
func (f *fakeInformer) GetStore() k8scache.Store {
return &fakeStore{}
}
type fakeCache struct {
cache.Cache
}
func (f *fakeCache) GetInformer(ctx context.Context, obj crtclient.Object, opt ...crtcache.InformerGetOption) (cache.Informer, error) {
return &fakeInformer{}, nil
}
type generatorMock struct {
mock.Mock
}
@@ -224,7 +196,6 @@ func TestExtractApplications(t *testing.T) {
},
Renderer: &rendererMock,
KubeClientset: kubefake.NewSimpleClientset(),
Cache: &fakeCache{},
}
got, reason, err := r.generateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{
@@ -1361,7 +1332,6 @@ func TestCreateOrUpdateInCluster(t *testing.T) {
Client: client,
Scheme: scheme,
Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)),
Cache: &fakeCache{},
}
err = r.createOrUpdateInCluster(context.TODO(), log.NewEntry(log.StandardLogger()), c.appSet, c.desiredApps)
@@ -1472,7 +1442,6 @@ func TestRemoveFinalizerOnInvalidDestination_FinalizerTypes(t *testing.T) {
Scheme: scheme,
Recorder: record.NewFakeRecorder(10),
KubeClientset: kubeclientset,
Cache: &fakeCache{},
}
// settingsMgr := settings.NewSettingsManager(context.TODO(), kubeclientset, "namespace")
// argoDB := db.NewDB("namespace", settingsMgr, r.KubeClientset)
@@ -1630,7 +1599,6 @@ func TestRemoveFinalizerOnInvalidDestination_DestinationTypes(t *testing.T) {
Scheme: scheme,
Recorder: record.NewFakeRecorder(10),
KubeClientset: kubeclientset,
Cache: &fakeCache{},
}
// settingsMgr := settings.NewSettingsManager(context.TODO(), kubeclientset, "argocd")
// argoDB := db.NewDB("argocd", settingsMgr, r.KubeClientset)
@@ -1718,7 +1686,6 @@ func TestRemoveOwnerReferencesOnDeleteAppSet(t *testing.T) {
Scheme: scheme,
Recorder: record.NewFakeRecorder(10),
KubeClientset: nil,
Cache: &fakeCache{},
}
err = r.removeOwnerReferencesOnDeleteAppSet(context.Background(), appSet)
@@ -1915,7 +1882,6 @@ func TestCreateApplications(t *testing.T) {
Client: client,
Scheme: scheme,
Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)),
Cache: &fakeCache{},
}
err = r.createInCluster(context.TODO(), log.NewEntry(log.StandardLogger()), c.appSet, c.apps)
@@ -2122,7 +2088,6 @@ func TestGetMinRequeueAfter(t *testing.T) {
Client: client,
Scheme: scheme,
Recorder: record.NewFakeRecorder(0),
Cache: &fakeCache{},
Generators: map[string]generators.Generator{
"List": &generatorMock10,
"Git": &generatorMock1,
@@ -2139,6 +2104,57 @@ func TestGetMinRequeueAfter(t *testing.T) {
assert.Equal(t, time.Duration(1)*time.Second, got)
}
func TestRequeueGeneratorFails(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
err = v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
appSet := v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{{
PullRequest: &v1alpha1.PullRequestGenerator{},
}},
},
}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).Build()
generator := v1alpha1.ApplicationSetGenerator{
PullRequest: &v1alpha1.PullRequestGenerator{},
}
generatorMock := mocks.Generator{}
generatorMock.On("GetTemplate", &generator).
Return(&v1alpha1.ApplicationSetTemplate{})
generatorMock.On("GenerateParams", &generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).
Return([]map[string]interface{}{}, fmt.Errorf("Simulated error generating params that could be related to an external service/API call"))
r := ApplicationSetReconciler{
Client: client,
Scheme: scheme,
Recorder: record.NewFakeRecorder(0),
Generators: map[string]generators.Generator{
"PullRequest": &generatorMock,
},
}
req := ctrl.Request{
NamespacedName: types.NamespacedName{
Namespace: "argocd",
Name: "name",
},
}
res, err := r.Reconcile(context.Background(), req)
require.Error(t, err)
assert.Equal(t, ReconcileRequeueOnValidationError, res.RequeueAfter)
}
func TestValidateGeneratedApplications(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
@@ -2333,7 +2349,6 @@ func TestValidateGeneratedApplications(t *testing.T) {
Client: client,
Scheme: scheme,
Recorder: record.NewFakeRecorder(1),
Cache: &fakeCache{},
Generators: map[string]generators.Generator{},
ArgoDB: &argoDBMock,
ArgoCDNamespace: "namespace",
@@ -2436,7 +2451,6 @@ func TestReconcilerValidationProjectErrorBehaviour(t *testing.T) {
Scheme: scheme,
Renderer: &utils.Render{},
Recorder: record.NewFakeRecorder(1),
Cache: &fakeCache{},
Generators: map[string]generators.Generator{
"List": generators.NewListGenerator(),
},
@@ -2471,90 +2485,6 @@ func TestReconcilerValidationProjectErrorBehaviour(t *testing.T) {
require.Error(t, err)
}
func TestReconcilerCreateAppsRecoveringRenderError(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
err = v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
project := v1alpha1.AppProject{
ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"},
}
appSet := v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSetSpec{
GoTemplate: true,
Generators: []v1alpha1.ApplicationSetGenerator{
{
List: &v1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{{
Raw: []byte(`{"name": "very-good-app"}`),
}, {
Raw: []byte(`{"name": "bad-app"}`),
}},
},
},
},
Template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
Name: "{{ index (splitList \"-\" .name ) 2 }}",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSpec{
Source: &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argocd-example-apps", Path: "guestbook"},
Project: "default",
Destination: v1alpha1.ApplicationDestination{Server: "https://kubernetes.default.svc"},
},
},
},
}
kubeclientset := kubefake.NewSimpleClientset()
argoDBMock := dbmocks.ArgoDB{}
argoObjs := []runtime.Object{&project}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet).WithStatusSubresource(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build()
r := ApplicationSetReconciler{
Client: client,
Scheme: scheme,
Renderer: &utils.Render{},
Recorder: record.NewFakeRecorder(1),
Cache: &fakeCache{},
Generators: map[string]generators.Generator{
"List": generators.NewListGenerator(),
},
ArgoDB: &argoDBMock,
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
KubeClientset: kubeclientset,
Policy: v1alpha1.ApplicationsSyncPolicySync,
ArgoCDNamespace: "argocd",
}
req := ctrl.Request{
NamespacedName: types.NamespacedName{
Namespace: "argocd",
Name: "name",
},
}
// Verify that on generatorsError, no error is returned, but the object is requeued
res, err := r.Reconcile(context.Background(), req)
require.NoError(t, err)
assert.Equal(t, ReconcileRequeueOnValidationError, res.RequeueAfter)
var app v1alpha1.Application
// make sure good app got created
err = r.Client.Get(context.TODO(), crtclient.ObjectKey{Namespace: "argocd", Name: "app"}, &app)
require.NoError(t, err)
assert.Equal(t, "app", app.Name)
}
func TestSetApplicationSetStatusCondition(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
@@ -2597,7 +2527,6 @@ func TestSetApplicationSetStatusCondition(t *testing.T) {
Scheme: scheme,
Renderer: &utils.Render{},
Recorder: record.NewFakeRecorder(1),
Cache: &fakeCache{},
Generators: map[string]generators.Generator{
"List": generators.NewListGenerator(),
},
@@ -2671,7 +2600,6 @@ func applicationsUpdateSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alp
Scheme: scheme,
Renderer: &utils.Render{},
Recorder: record.NewFakeRecorder(recordBuffer),
Cache: &fakeCache{},
Generators: map[string]generators.Generator{
"List": generators.NewListGenerator(),
},
@@ -2835,7 +2763,6 @@ func applicationsDeleteSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alp
Scheme: scheme,
Renderer: &utils.Render{},
Recorder: record.NewFakeRecorder(recordBuffer),
Cache: &fakeCache{},
Generators: map[string]generators.Generator{
"List": generators.NewListGenerator(),
},
@@ -3021,7 +2948,6 @@ func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) {
Client: client,
Scheme: scheme,
Recorder: record.NewFakeRecorder(1),
Cache: &fakeCache{},
Generators: map[string]generators.Generator{
"PullRequest": &generatorMock,
},
@@ -3146,7 +3072,6 @@ func TestPolicies(t *testing.T) {
Scheme: scheme,
Renderer: &utils.Render{},
Recorder: record.NewFakeRecorder(10),
Cache: &fakeCache{},
Generators: map[string]generators.Generator{
"List": generators.NewListGenerator(),
},
@@ -3307,7 +3232,6 @@ func TestSetApplicationSetApplicationStatus(t *testing.T) {
Scheme: scheme,
Renderer: &utils.Render{},
Recorder: record.NewFakeRecorder(1),
Cache: &fakeCache{},
Generators: map[string]generators.Generator{
"List": generators.NewListGenerator(),
},
@@ -4069,7 +3993,6 @@ func TestBuildAppDependencyList(t *testing.T) {
Client: client,
Scheme: scheme,
Recorder: record.NewFakeRecorder(1),
Cache: &fakeCache{},
Generators: map[string]generators.Generator{},
ArgoDB: &argoDBMock,
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
@@ -4660,7 +4583,6 @@ func TestBuildAppSyncMap(t *testing.T) {
Client: client,
Scheme: scheme,
Recorder: record.NewFakeRecorder(1),
Cache: &fakeCache{},
Generators: map[string]generators.Generator{},
ArgoDB: &argoDBMock,
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
@@ -4791,6 +4713,58 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
},
},
},
{
name: "handles an outdated list of statuses with a healthy application, setting required variables",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSetSpec{
Strategy: &v1alpha1.ApplicationSetStrategy{
Type: "RollingSync",
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
},
},
Status: v1alpha1.ApplicationSetStatus{
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "Application resource is already Healthy, updating status from Waiting to Healthy.",
Status: "Healthy",
Step: "1",
},
},
},
},
apps: []v1alpha1.Application{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
},
Status: v1alpha1.ApplicationStatus{
Health: v1alpha1.HealthStatus{
Status: health.HealthStatusHealthy,
},
OperationState: &v1alpha1.OperationState{
Phase: common.OperationSucceeded,
},
Sync: v1alpha1.SyncStatus{
Status: v1alpha1.SyncStatusCodeSynced,
},
},
},
},
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "Application resource is already Healthy, updating status from Waiting to Healthy.",
Status: "Healthy",
Step: "1",
TargetRevisions: []string{},
},
},
},
{
name: "progresses an OutOfSync RollingSync application to waiting",
appSet: v1alpha1.ApplicationSet{
@@ -4880,10 +4854,11 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
Status: v1alpha1.ApplicationSetStatus{
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "",
Status: "Pending",
Step: "1",
Application: "app1",
Message: "",
Status: "Pending",
Step: "1",
TargetRevisions: []string{"Next"},
},
},
},
@@ -4902,15 +4877,16 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
},
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "Application resource became Progressing, updating status from Pending to Progressing.",
Status: "Progressing",
Step: "1",
Application: "app1",
Message: "Application resource became Progressing, updating status from Pending to Progressing.",
Status: "Progressing",
Step: "1",
TargetRevisions: []string{"Next"},
},
},
},
{
name: "progresses a pending syncing application to progressing",
name: "progresses a pending synced application to progressing",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
@@ -4925,10 +4901,11 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
Status: v1alpha1.ApplicationSetStatus{
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "",
Status: "Pending",
Step: "1",
Application: "app1",
Message: "",
Status: "Pending",
Step: "1",
TargetRevisions: []string{"Current"},
},
},
},
@@ -4953,10 +4930,11 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
},
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "Application resource became Progressing, updating status from Pending to Progressing.",
Status: "Progressing",
Step: "1",
Application: "app1",
Message: "Application resource became Progressing, updating status from Pending to Progressing.",
Status: "Progressing",
Step: "1",
TargetRevisions: []string{"Current"},
},
},
},
@@ -4976,10 +4954,11 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
Status: v1alpha1.ApplicationSetStatus{
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "",
Status: "Progressing",
Step: "1",
Application: "app1",
Message: "",
Status: "Progressing",
Step: "1",
TargetRevisions: []string{"Next"},
},
},
},
@@ -5004,10 +4983,11 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
},
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "Application resource became Healthy, updating status from Progressing to Healthy.",
Status: "Healthy",
Step: "1",
Application: "app1",
Message: "Application resource became Healthy, updating status from Progressing to Healthy.",
Status: "Healthy",
Step: "1",
TargetRevisions: []string{"Next"},
},
},
},
@@ -5027,10 +5007,11 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
Status: v1alpha1.ApplicationSetStatus{
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "",
Status: "Waiting",
Step: "1",
Application: "app1",
Message: "",
Status: "Waiting",
Step: "1",
TargetRevisions: []string{"Current"},
},
},
},
@@ -5055,10 +5036,11 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
},
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "Application resource is already Healthy, updating status from Waiting to Healthy.",
Status: "Healthy",
Step: "1",
Application: "app1",
Message: "Application resource is already Healthy, updating status from Waiting to Healthy.",
Status: "Healthy",
Step: "1",
TargetRevisions: []string{"Current"},
},
},
},
@@ -5334,16 +5316,18 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
Status: v1alpha1.ApplicationSetStatus{
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "Application has pending changes, setting status to Waiting.",
Status: "Waiting",
Step: "1",
Application: "app1",
Message: "Application has pending changes, setting status to Waiting.",
Status: "Waiting",
Step: "1",
TargetRevisions: []string{"Current"},
},
{
Application: "app2",
Message: "Application has pending changes, setting status to Waiting.",
Status: "Waiting",
Step: "1",
Application: "app2",
Message: "Application has pending changes, setting status to Waiting.",
Status: "Waiting",
Step: "1",
TargetRevisions: []string{"Current"},
},
},
},
@@ -5368,10 +5352,11 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
},
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "Application resource is already Healthy, updating status from Waiting to Healthy.",
Status: "Healthy",
Step: "1",
Application: "app1",
Message: "Application resource is already Healthy, updating status from Waiting to Healthy.",
Status: "Healthy",
Step: "1",
TargetRevisions: []string{"Current"},
},
},
},
@@ -5387,7 +5372,6 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
Client: client,
Scheme: scheme,
Recorder: record.NewFakeRecorder(1),
Cache: &fakeCache{},
Generators: map[string]generators.Generator{},
ArgoDB: &argoDBMock,
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
@@ -5533,9 +5517,10 @@ func TestUpdateApplicationSetApplicationStatusProgress(t *testing.T) {
Status: v1alpha1.ApplicationSetStatus{
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
Application: "app1",
Message: "Application is out of date with the current AppSet generation, setting status to Waiting.",
Status: "Waiting",
Application: "app1",
Message: "Application is out of date with the current AppSet generation, setting status to Waiting.",
Status: "Waiting",
TargetRevisions: []string{"Next"},
},
},
},
@@ -5553,6 +5538,7 @@ func TestUpdateApplicationSetApplicationStatusProgress(t *testing.T) {
Message: "Application moved to Pending status, watching for the Application resource to start Progressing.",
Status: "Pending",
Step: "1",
TargetRevisions: []string{"Next"},
},
},
},
@@ -6138,7 +6124,6 @@ func TestUpdateApplicationSetApplicationStatusProgress(t *testing.T) {
Client: client,
Scheme: scheme,
Recorder: record.NewFakeRecorder(1),
Cache: &fakeCache{},
Generators: map[string]generators.Generator{},
ArgoDB: &argoDBMock,
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
@@ -6353,7 +6338,6 @@ func TestUpdateResourceStatus(t *testing.T) {
Client: client,
Scheme: scheme,
Recorder: record.NewFakeRecorder(1),
Cache: &fakeCache{},
Generators: map[string]generators.Generator{},
ArgoDB: &argoDBMock,
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
@@ -6368,6 +6352,102 @@ func TestUpdateResourceStatus(t *testing.T) {
}
}
func generateNAppResourceStatuses(n int) []v1alpha1.ResourceStatus {
var r []v1alpha1.ResourceStatus
for i := 0; i < n; i++ {
r = append(r, v1alpha1.ResourceStatus{
Name: "app" + strconv.Itoa(i),
Status: v1alpha1.SyncStatusCodeSynced,
Health: &v1alpha1.HealthStatus{
Status: health.HealthStatusHealthy,
Message: "OK",
},
},
)
}
return r
}
func generateNHealthyApps(n int) []v1alpha1.Application {
var r []v1alpha1.Application
for i := 0; i < n; i++ {
r = append(r, v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "app" + strconv.Itoa(i),
},
Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{
Status: v1alpha1.SyncStatusCodeSynced,
},
Health: v1alpha1.HealthStatus{
Status: health.HealthStatusHealthy,
Message: "OK",
},
},
})
}
return r
}
func TestResourceStatusAreOrdered(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
err = v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
for _, cc := range []struct {
name string
appSet v1alpha1.ApplicationSet
apps []v1alpha1.Application
expectedResources []v1alpha1.ResourceStatus
}{
{
name: "Ensures AppSet is always ordered",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "argocd",
},
Status: v1alpha1.ApplicationSetStatus{
Resources: []v1alpha1.ResourceStatus{},
},
},
apps: generateNHealthyApps(10),
expectedResources: generateNAppResourceStatuses(10),
},
} {
t.Run(cc.name, func(t *testing.T) {
kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
argoDBMock := dbmocks.ArgoDB{}
argoObjs := []runtime.Object{}
client := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(&cc.appSet).WithObjects(&cc.appSet).Build()
r := ApplicationSetReconciler{
Client: client,
Scheme: scheme,
Recorder: record.NewFakeRecorder(1),
Generators: map[string]generators.Generator{},
ArgoDB: &argoDBMock,
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
KubeClientset: kubeclientset,
}
err := r.updateResourcesStatus(context.TODO(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.apps)
require.NoError(t, err, "expected no errors, but errors occurred")
err = r.updateResourcesStatus(context.TODO(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.apps)
require.NoError(t, err, "expected no errors, but errors occurred")
err = r.updateResourcesStatus(context.TODO(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.apps)
require.NoError(t, err, "expected no errors, but errors occurred")
assert.Equal(t, cc.expectedResources, cc.appSet.Status.Resources, "expected resources did not match actual")
})
}
}
func TestOwnsHandler(t *testing.T) {
// progressive syncs do not affect create, delete, or generic
ownsHandler := getOwnsHandlerPredicates(true)
@@ -6559,3 +6639,74 @@ func TestOwnsHandler(t *testing.T) {
})
}
}
func TestMigrateStatus(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
err = v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
for _, tc := range []struct {
name string
appset v1alpha1.ApplicationSet
expectedStatus v1alpha1.ApplicationSetStatus
}{
{
name: "status without applicationstatus target revisions set will default to empty list",
appset: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "test",
},
Status: v1alpha1.ApplicationSetStatus{
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
{},
},
},
},
expectedStatus: v1alpha1.ApplicationSetStatus{
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
TargetRevisions: []string{},
},
},
},
},
{
name: "status with applicationstatus target revisions set will do nothing",
appset: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "test",
},
Status: v1alpha1.ApplicationSetStatus{
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
TargetRevisions: []string{"Current"},
},
},
},
},
expectedStatus: v1alpha1.ApplicationSetStatus{
ApplicationStatus: []v1alpha1.ApplicationSetApplicationStatus{
{
TargetRevisions: []string{"Current"},
},
},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
client := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(&tc.appset).WithObjects(&tc.appset).Build()
r := ApplicationSetReconciler{
Client: client,
}
err := r.migrateStatus(context.Background(), &tc.appset)
require.NoError(t, err)
assert.Equal(t, tc.expectedStatus, tc.appset.Status)
})
}
}

View File

@@ -60,7 +60,7 @@ func TestRequeueAfter(t *testing.T) {
terminalGenerators := map[string]generators.Generator{
"List": generators.NewListGenerator(),
"Clusters": generators.NewClusterGenerator(k8sClient, ctx, appClientset, "argocd"),
"Git": generators.NewGitGenerator(mockServer),
"Git": generators.NewGitGenerator(mockServer, "namespace"),
"SCMProvider": generators.NewSCMProviderGenerator(fake.NewClientBuilder().WithObjects(&corev1.Secret{}).Build(), generators.SCMAuthProviders{}, "", []string{""}, true),
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, fakeDynClient, appClientset, "argocd"),
"PullRequest": generators.NewPullRequestGenerator(k8sClient, generators.SCMAuthProviders{}, "", []string{""}, true),

View File

@@ -346,7 +346,7 @@ func getMockClusterGenerator() Generator {
func getMockGitGenerator() Generator {
argoCDServiceMock := mocks.Repos{}
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything).Return([]string{"app1", "app2", "app_3", "p1/app4"}, nil)
gitGenerator := NewGitGenerator(&argoCDServiceMock)
gitGenerator := NewGitGenerator(&argoCDServiceMock, "namespace")
return gitGenerator
}

View File

@@ -24,13 +24,16 @@ import (
var _ Generator = (*GitGenerator)(nil)
type GitGenerator struct {
repos services.Repos
repos services.Repos
namespace string
}
func NewGitGenerator(repos services.Repos) Generator {
func NewGitGenerator(repos services.Repos, namespace string) Generator {
g := &GitGenerator{
repos: repos,
repos: repos,
namespace: namespace,
}
return g
}
@@ -59,21 +62,25 @@ func (g *GitGenerator) GenerateParams(appSetGenerator *argoprojiov1alpha1.Applic
noRevisionCache := appSet.RefreshRequired()
var project string
if strings.Contains(appSet.Spec.Template.Spec.Project, "{{") {
project = appSetGenerator.Git.Template.Spec.Project
} else {
project = appSet.Spec.Template.Spec.Project
}
verifyCommit := false
appProject := &argoprojiov1alpha1.AppProject{}
if err := client.Get(context.TODO(), types.NamespacedName{Name: appSet.Spec.Template.Spec.Project, Namespace: appSet.Namespace}, appProject); err != nil {
return nil, fmt.Errorf("error getting project %s: %w", project, err)
// When the project field is templated, the contents of the git repo are required to run the git generator and get the templated value,
// but git generator cannot be called without verifying the commit signature.
// In this case, we skip the signature verification.
if !strings.Contains(appSet.Spec.Template.Spec.Project, "{{") {
project := appSet.Spec.Template.Spec.Project
appProject := &argoprojiov1alpha1.AppProject{}
namespace := g.namespace
if namespace == "" {
namespace = appSet.Namespace
}
if err := client.Get(context.TODO(), types.NamespacedName{Name: project, Namespace: namespace}, appProject); err != nil {
return nil, fmt.Errorf("error getting project %s: %w", project, err)
}
// we need to verify the signature on the Git revision if GPG is enabled
verifyCommit = appProject.Spec.SignatureKeys != nil && len(appProject.Spec.SignatureKeys) > 0 && gpg.IsGPGEnabled()
}
// we need to verify the signature on the Git revision if GPG is enabled
verifyCommit := appProject.Spec.SignatureKeys != nil && len(appProject.Spec.SignatureKeys) > 0 && gpg.IsGPGEnabled()
var err error
var res []map[string]interface{}
if len(appSetGenerator.Git.Directories) != 0 {

View File

@@ -323,7 +323,7 @@ func TestGitGenerateParamsFromDirectories(t *testing.T) {
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
gitGenerator := NewGitGenerator(&argoCDServiceMock)
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -624,7 +624,7 @@ func TestGitGenerateParamsFromDirectoriesGoTemplate(t *testing.T) {
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCaseCopy.repoApps, testCaseCopy.repoError)
gitGenerator := NewGitGenerator(&argoCDServiceMock)
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -989,7 +989,7 @@ cluster:
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
gitGenerator := NewGitGenerator(&argoCDServiceMock)
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -1345,7 +1345,7 @@ cluster:
argoCDServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(testCaseCopy.repoFileContents, testCaseCopy.repoPathsError)
gitGenerator := NewGitGenerator(&argoCDServiceMock)
gitGenerator := NewGitGenerator(&argoCDServiceMock, "")
applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
@@ -1383,3 +1383,114 @@ cluster:
})
}
}
func TestGitGenerator_GenerateParams(t *testing.T) {
cases := []struct {
name string
directories []argoprojiov1alpha1.GitDirectoryGeneratorItem
pathParamPrefix string
repoApps []string
repoPathsError error
repoFileContents map[string][]byte
values map[string]string
expected []map[string]interface{}
expectedError error
appset argoprojiov1alpha1.ApplicationSet
callGetDirectories bool
}{
{
name: "Signature Verification - ignores templated project field",
repoApps: []string{
"app1",
},
repoPathsError: nil,
appset: argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
Namespace: "namespace",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
Git: &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL",
Revision: "Revision",
Directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
PathParamPrefix: "",
Values: map[string]string{
"foo": "bar",
},
},
}},
Template: argoprojiov1alpha1.ApplicationSetTemplate{
Spec: argoprojiov1alpha1.ApplicationSpec{
Project: "{{.project}}",
},
},
},
},
callGetDirectories: true,
expected: []map[string]interface{}{{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "path[0]": "app1", "values.foo": "bar"}},
expectedError: nil,
},
{
name: "Signature Verification - Checks for non-templated project field",
repoApps: []string{
"app1",
},
repoPathsError: nil,
appset: argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "set",
Namespace: "namespace",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
Git: &argoprojiov1alpha1.GitGenerator{
RepoURL: "RepoURL",
Revision: "Revision",
Directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{Path: "*"}},
PathParamPrefix: "",
Values: map[string]string{
"foo": "bar",
},
},
}},
Template: argoprojiov1alpha1.ApplicationSetTemplate{
Spec: argoprojiov1alpha1.ApplicationSpec{
Project: "project",
},
},
},
},
callGetDirectories: false,
expected: []map[string]interface{}{{"path": "app1", "path.basename": "app1", "path.basenameNormalized": "app1", "path[0]": "app1", "values.foo": "bar"}},
expectedError: fmt.Errorf("error getting project project: appprojects.argoproj.io \"project\" not found"),
},
}
for _, testCase := range cases {
argoCDServiceMock := mocks.Repos{}
if testCase.callGetDirectories {
argoCDServiceMock.On("GetDirectories", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCase.repoApps, testCase.repoPathsError)
}
gitGenerator := NewGitGenerator(&argoCDServiceMock, "namespace")
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
appProject := argoprojiov1alpha1.AppProject{}
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appProject).Build()
got, err := gitGenerator.GenerateParams(&testCase.appset.Spec.Generators[0], &testCase.appset, client)
if testCase.expectedError != nil {
require.EqualError(t, err, testCase.expectedError.Error())
} else {
require.NoError(t, err)
assert.Equal(t, testCase.expected, got)
}
argoCDServiceMock.AssertExpectations(t)
}
}

View File

@@ -9,6 +9,8 @@ import (
argoprojiov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
//go:generate go run github.com/vektra/mockery/v2@v2.40.2 --name=Generator
// Generator defines the interface implemented by all ApplicationSet generators.
type Generator interface {
// GenerateParams interprets the ApplicationSet and generates all relevant parameters for the application template.

View File

@@ -1089,7 +1089,7 @@ func TestGitGenerator_GenerateParams_list_x_git_matrix_generator(t *testing.T) {
repoServiceMock.On("GetFiles", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(map[string][]byte{
"some/path.json": []byte("test: content"),
}, nil)
gitGenerator := NewGitGenerator(repoServiceMock)
gitGenerator := NewGitGenerator(repoServiceMock, "")
matrixGenerator := NewMatrixGenerator(map[string]Generator{
"List": listGeneratorMock,

View File

@@ -0,0 +1,100 @@
// Code generated by mockery v2.40.2. DO NOT EDIT.
package mocks
import (
client "sigs.k8s.io/controller-runtime/pkg/client"
mock "github.com/stretchr/testify/mock"
time "time"
v1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
// Generator is an autogenerated mock type for the Generator type
type Generator struct {
mock.Mock
}
// GenerateParams provides a mock function with given fields: appSetGenerator, applicationSetInfo, _a2
func (_m *Generator) GenerateParams(appSetGenerator *v1alpha1.ApplicationSetGenerator, applicationSetInfo *v1alpha1.ApplicationSet, _a2 client.Client) ([]map[string]interface{}, error) {
ret := _m.Called(appSetGenerator, applicationSetInfo, _a2)
if len(ret) == 0 {
panic("no return value specified for GenerateParams")
}
var r0 []map[string]interface{}
var r1 error
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) ([]map[string]interface{}, error)); ok {
return rf(appSetGenerator, applicationSetInfo, _a2)
}
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) []map[string]interface{}); ok {
r0 = rf(appSetGenerator, applicationSetInfo, _a2)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]map[string]interface{})
}
}
if rf, ok := ret.Get(1).(func(*v1alpha1.ApplicationSetGenerator, *v1alpha1.ApplicationSet, client.Client) error); ok {
r1 = rf(appSetGenerator, applicationSetInfo, _a2)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetRequeueAfter provides a mock function with given fields: appSetGenerator
func (_m *Generator) GetRequeueAfter(appSetGenerator *v1alpha1.ApplicationSetGenerator) time.Duration {
ret := _m.Called(appSetGenerator)
if len(ret) == 0 {
panic("no return value specified for GetRequeueAfter")
}
var r0 time.Duration
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) time.Duration); ok {
r0 = rf(appSetGenerator)
} else {
r0 = ret.Get(0).(time.Duration)
}
return r0
}
// GetTemplate provides a mock function with given fields: appSetGenerator
func (_m *Generator) GetTemplate(appSetGenerator *v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate {
ret := _m.Called(appSetGenerator)
if len(ret) == 0 {
panic("no return value specified for GetTemplate")
}
var r0 *v1alpha1.ApplicationSetTemplate
if rf, ok := ret.Get(0).(func(*v1alpha1.ApplicationSetGenerator) *v1alpha1.ApplicationSetTemplate); ok {
r0 = rf(appSetGenerator)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.ApplicationSetTemplate)
}
}
return r0
}
// NewGenerator creates a new instance of Generator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewGenerator(t interface {
mock.TestingT
Cleanup(func())
}) *Generator {
mock := &Generator{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -4653,6 +4653,9 @@
"help": {
"$ref": "#/definitions/clusterHelp"
},
"installationID": {
"type": "string"
},
"kustomizeOptions": {
"$ref": "#/definitions/v1alpha1KustomizeOptions"
},
@@ -6960,7 +6963,7 @@
},
"serverVersion": {
"type": "string",
"title": "DEPRECATED: use Info.ServerVersion field instead.\nThe server version"
"title": "Deprecated: use Info.ServerVersion field instead.\nThe server version"
},
"shard": {
"description": "Shard contains optional shard number. Calculated on the fly by the application controller if not specified.",
@@ -9020,6 +9023,11 @@
"description": "SyncOperation contains details about a sync operation.",
"type": "object",
"properties": {
"autoHealAttemptsCount": {
"type": "integer",
"format": "int64",
"title": "SelfHealAttemptsCount contains the number of auto-heal attempts"
},
"dryRun": {
"type": "boolean",
"title": "DryRun specifies to perform a `kubectl apply --dry-run` without actually performing the sync"

View File

@@ -10,6 +10,7 @@ import (
"github.com/redis/go-redis/v9"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
@@ -53,6 +54,9 @@ func NewCommand() *cobra.Command {
repoServerAddress string
repoServerTimeoutSeconds int
selfHealTimeoutSeconds int
selfHealBackoffTimeoutSeconds int
selfHealBackoffFactor int
selfHealBackoffCapSeconds int
statusProcessors int
operationProcessors int
glogLevel int
@@ -148,6 +152,14 @@ func NewCommand() *cobra.Command {
kubectl := kubeutil.NewKubectl()
clusterSharding, err := sharding.GetClusterSharding(kubeClient, settingsMgr, shardingAlgorithm, enableDynamicClusterDistribution)
errors.CheckError(err)
var selfHealBackoff *wait.Backoff
if selfHealBackoffTimeoutSeconds != 0 {
selfHealBackoff = &wait.Backoff{
Duration: time.Duration(selfHealBackoffTimeoutSeconds) * time.Second,
Factor: float64(selfHealBackoffFactor),
Cap: time.Duration(selfHealBackoffCapSeconds) * time.Second,
}
}
appController, err = controller.NewApplicationController(
namespace,
settingsMgr,
@@ -160,6 +172,7 @@ func NewCommand() *cobra.Command {
hardResyncDuration,
time.Duration(appResyncJitter)*time.Second,
time.Duration(selfHealTimeoutSeconds)*time.Second,
selfHealBackoff,
time.Duration(repoErrorGracePeriod)*time.Second,
metricsPort,
metricsCacheExpiration,
@@ -209,7 +222,10 @@ func NewCommand() *cobra.Command {
command.Flags().IntVar(&glogLevel, "gloglevel", 0, "Set the glog logging level")
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortArgoCDMetrics, "Start metrics server on given port")
command.Flags().DurationVar(&metricsCacheExpiration, "metrics-cache-expiration", env.ParseDurationFromEnv("ARGOCD_APPLICATION_CONTROLLER_METRICS_CACHE_EXPIRATION", 0*time.Second, 0, math.MaxInt64), "Prometheus metrics cache expiration (disabled by default. e.g. 24h0m0s)")
command.Flags().IntVar(&selfHealTimeoutSeconds, "self-heal-timeout-seconds", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_TIMEOUT_SECONDS", 5, 0, math.MaxInt32), "Specifies timeout between application self heal attempts")
command.Flags().IntVar(&selfHealTimeoutSeconds, "self-heal-timeout-seconds", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_TIMEOUT_SECONDS", 0, 0, math.MaxInt32), "Specifies timeout between application self heal attempts")
command.Flags().IntVar(&selfHealBackoffTimeoutSeconds, "self-heal-backoff-timeout-seconds", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_TIMEOUT_SECONDS", 2, 0, math.MaxInt32), "Specifies initial timeout of exponential backoff between self heal attempts")
command.Flags().IntVar(&selfHealBackoffFactor, "self-heal-backoff-factor", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_FACTOR", 3, 0, math.MaxInt32), "Specifies factor of exponential timeout between application self heal attempts")
command.Flags().IntVar(&selfHealBackoffCapSeconds, "self-heal-backoff-cap-seconds", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_CAP_SECONDS", 300, 0, math.MaxInt32), "Specifies max timeout of exponential backoff between application self heal attempts")
command.Flags().Int64Var(&kubectlParallelismLimit, "kubectl-parallelism-limit", env.ParseInt64FromEnv("ARGOCD_APPLICATION_CONTROLLER_KUBECTL_PARALLELISM_LIMIT", 20, 0, math.MaxInt64), "Number of allowed concurrent kubectl fork/execs. Any value less than 1 means no limit.")
command.Flags().BoolVar(&repoServerPlaintext, "repo-server-plaintext", env.ParseBoolFromEnv("ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT", false), "Disable TLS on connections to repo server")
command.Flags().BoolVar(&repoServerStrictTLS, "repo-server-strict-tls", env.ParseBoolFromEnv("ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_STRICT_TLS", false), "Whether to use strict validation of the TLS cert presented by the repo server")

View File

@@ -177,7 +177,7 @@ func NewCommand() *cobra.Command {
terminalGenerators := map[string]generators.Generator{
"List": generators.NewListGenerator(),
"Clusters": generators.NewClusterGenerator(mgr.GetClient(), ctx, k8sClient, namespace),
"Git": generators.NewGitGenerator(argoCDService),
"Git": generators.NewGitGenerator(argoCDService, namespace),
"SCMProvider": generators.NewSCMProviderGenerator(mgr.GetClient(), scmAuth, scmRootCAPath, allowedScmProviders, enableScmProviders),
"ClusterDecisionResource": generators.NewDuckTypeGenerator(ctx, dynamicClient, k8sClient, namespace),
"PullRequest": generators.NewPullRequestGenerator(mgr.GetClient(), scmAuth, scmRootCAPath, allowedScmProviders, enableScmProviders),
@@ -234,7 +234,6 @@ func NewCommand() *cobra.Command {
SCMRootCAPath: scmRootCAPath,
GlobalPreservedAnnotations: globalPreservedAnnotations,
GlobalPreservedLabels: globalPreservedLabels,
Cache: mgr.GetCache(),
}).SetupWithManager(mgr, enableProgressiveSyncs, maxConcurrentReconciliations); err != nil {
log.Error(err, "unable to create controller", "controller", "ApplicationSet")
os.Exit(1)

View File

@@ -104,7 +104,12 @@ func loadClusters(ctx context.Context, kubeClient *kubernetes.Clientset, appClie
if err != nil {
return nil, err
}
client := redis.NewClient(&redis.Options{Addr: fmt.Sprintf("localhost:%d", port)})
redisOptions := &redis.Options{Addr: fmt.Sprintf("localhost:%d", port)}
if err = common.SetOptionalRedisPasswordFromKubeConfig(ctx, kubeClient, namespace, redisOptions); err != nil {
log.Warnf("Failed to fetch & set redis password for namespace %s: %v", namespace, err)
}
client := redis.NewClient(redisOptions)
compressionType, err := cacheutil.CompressionTypeFromString(redisCompressionStr)
if err != nil {
return nil, err

View File

@@ -6,25 +6,18 @@ import (
"fmt"
"math/big"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/cli"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/util/cli"
"github.com/argoproj/argo-cd/v2/util/errors"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
)
const (
defaulRedisInitialPasswordSecretName = "argocd-redis"
defaultResisInitialPasswordKey = "auth"
)
func generateRandomPassword() (string, error) {
@@ -52,8 +45,8 @@ func NewRedisInitialPasswordCommand() *cobra.Command {
namespace, _, err := clientConfig.Namespace()
errors.CheckError(err)
redisInitialPasswordSecretName := defaulRedisInitialPasswordSecretName
redisInitialPasswordKey := defaultResisInitialPasswordKey
redisInitialPasswordSecretName := common.DefaultRedisInitialPasswordSecretName
redisInitialPasswordKey := common.DefaultRedisInitialPasswordKey
fmt.Printf("Checking for initial Redis password in secret %s/%s at key %s. \n", namespace, redisInitialPasswordSecretName, redisInitialPasswordKey)
config, err := clientConfig.ClientConfig()

View File

@@ -572,8 +572,8 @@ func printAppSummaryTable(app *argoappv1.Application, appURL string, windows *ar
var status string
var allow, deny, inactiveAllows bool
if windows.HasWindows() {
active := windows.Active()
if active.HasWindows() {
active, err := windows.Active()
if err == nil && active.HasWindows() {
for _, w := range *active {
if w.Kind == "deny" {
deny = true
@@ -582,13 +582,14 @@ func printAppSummaryTable(app *argoappv1.Application, appURL string, windows *ar
}
}
}
if windows.InactiveAllows().HasWindows() {
inactiveAllowWindows, err := windows.InactiveAllows()
if err == nil && inactiveAllowWindows.HasWindows() {
inactiveAllows = true
}
s := windows.CanSync(true)
if deny || !deny && !allow && inactiveAllows {
if s {
s, err := windows.CanSync(true)
if err == nil && s {
status = "Manual Allowed"
} else {
status = "Sync Denied"
@@ -776,8 +777,6 @@ func NewApplicationSetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
}
}
// sourcePosition startes with 1, thus, it needs to be decreased by 1 to find the correct index in the list of sources
sourcePosition = sourcePosition - 1
visited := cmdutil.SetAppSpecOptions(c.Flags(), &app.Spec, &appOpts, sourcePosition)
if visited == 0 {
log.Error("Please set at least one option to update")
@@ -1350,7 +1349,7 @@ func groupObjsForDiff(resources *application.ManagedResourcesResponse, objs map[
}
if local, ok := objs[key]; ok || live != nil {
if local != nil && !kube.IsCRD(local) {
err = resourceTracking.SetAppInstance(local, argoSettings.AppLabelKey, appName, namespace, argoappv1.TrackingMethod(argoSettings.GetTrackingMethod()))
err = resourceTracking.SetAppInstance(local, argoSettings.AppLabelKey, appName, namespace, argoappv1.TrackingMethod(argoSettings.GetTrackingMethod()), argoSettings.GetInstallationID())
errors.CheckError(err)
}

View File

@@ -8,15 +8,11 @@ import (
"sync"
"time"
"github.com/spf13/cobra"
"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/initialize"
"github.com/argoproj/argo-cd/v2/common"
"github.com/alicebob/miniredis/v2"
"github.com/golang/protobuf/ptypes/empty"
"github.com/redis/go-redis/v9"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/runtime"
@@ -25,6 +21,8 @@ import (
"k8s.io/client-go/tools/clientcmd"
"k8s.io/utils/ptr"
"github.com/argoproj/argo-cd/v2/cmd/argocd/commands/initialize"
"github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/pkg/apiclient"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
@@ -48,6 +46,7 @@ type forwardCacheClient struct {
err error
redisHaProxyName string
redisName string
redisPassword string
}
func (c *forwardCacheClient) doLazy(action func(client cache.CacheClient) error) error {
@@ -64,7 +63,7 @@ func (c *forwardCacheClient) doLazy(action func(client cache.CacheClient) error)
return
}
redisClient := redis.NewClient(&redis.Options{Addr: fmt.Sprintf("localhost:%d", redisPort)})
redisClient := redis.NewClient(&redis.Options{Addr: fmt.Sprintf("localhost:%d", redisPort), Password: c.redisPassword})
c.client = cache.NewRedisCache(redisClient, time.Hour, c.compression)
})
if c.err != nil {
@@ -240,14 +239,19 @@ func MaybeStartLocalServer(ctx context.Context, clientOpts *apiclient.ClientOpti
if err != nil {
return fmt.Errorf("error running miniredis: %w", err)
}
appstateCache := appstatecache.NewCache(cache.NewCache(&forwardCacheClient{namespace: namespace, context: ctxStr, compression: compression, redisHaProxyName: clientOpts.RedisHaProxyName, redisName: clientOpts.RedisName}), time.Hour)
redisOptions := &redis.Options{Addr: mr.Addr()}
if err = common.SetOptionalRedisPasswordFromKubeConfig(ctx, kubeClientset, namespace, redisOptions); err != nil {
log.Warnf("Failed to fetch & set redis password for namespace %s: %v", namespace, err)
}
appstateCache := appstatecache.NewCache(cache.NewCache(&forwardCacheClient{namespace: namespace, context: ctxStr, compression: compression, redisHaProxyName: clientOpts.RedisHaProxyName, redisName: clientOpts.RedisName, redisPassword: redisOptions.Password}), time.Hour)
srv := server.NewServer(ctx, server.ArgoCDServerOpts{
EnableGZip: false,
Namespace: namespace,
ListenPort: *port,
AppClientset: appClientset,
DisableAuth: true,
RedisClient: redis.NewClient(&redis.Options{Addr: mr.Addr()}),
RedisClient: redis.NewClient(redisOptions),
Cache: servercache.NewCache(appstateCache, 0, 0, 0),
KubeClientset: kubeClientset,
Insecure: true,

View File

@@ -352,9 +352,10 @@ func printSyncWindows(proj *v1alpha1.AppProject) {
fmt.Fprintf(w, fmtStr, headers...)
if proj.Spec.SyncWindows.HasWindows() {
for i, window := range proj.Spec.SyncWindows {
isActive, _ := window.Active()
vals := []interface{}{
strconv.Itoa(i),
formatBoolOutput(window.Active()),
formatBoolOutput(isActive),
window.Kind,
window.Schedule,
window.Duration,

View File

@@ -1,15 +1,20 @@
package common
import (
"errors"
"context"
"fmt"
"os"
"path/filepath"
"strconv"
"time"
"github.com/pkg/errors"
"github.com/redis/go-redis/v9"
"github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
// Component names
@@ -172,6 +177,7 @@ const (
// AnnotationKeyAppInstance is the Argo CD application name is used as the instance name
AnnotationKeyAppInstance = "argocd.argoproj.io/tracking-id"
AnnotationInstallationID = "argocd.argoproj.io/installation-id"
// AnnotationCompareOptions is a comma-separated list of options for comparison
AnnotationCompareOptions = "argocd.argoproj.io/compare-options"
@@ -414,3 +420,30 @@ const TokenVerificationError = "failed to verify the token"
var TokenVerificationErr = errors.New(TokenVerificationError)
var PermissionDeniedAPIError = status.Error(codes.PermissionDenied, "permission denied")
// Redis password consts
const (
DefaultRedisInitialPasswordSecretName = "argocd-redis"
DefaultRedisInitialPasswordKey = "auth"
)
/*
SetOptionalRedisPasswordFromKubeConfig sets the optional Redis password if it exists in the k8s namespace's secrets.
We specify kubeClient as kubernetes.Interface to allow for mocking in tests, but this should be treated as a kubernetes.Clientset param.
*/
func SetOptionalRedisPasswordFromKubeConfig(ctx context.Context, kubeClient kubernetes.Interface, namespace string, redisOptions *redis.Options) error {
secret, err := kubeClient.CoreV1().Secrets(namespace).Get(ctx, DefaultRedisInitialPasswordSecretName, v1.GetOptions{})
if err != nil {
return fmt.Errorf("failed to get secret %s/%s: %w", namespace, DefaultRedisInitialPasswordSecretName, err)
}
if secret == nil {
return fmt.Errorf("failed to get secret %s/%s: secret is nil", namespace, DefaultRedisInitialPasswordSecretName)
}
_, ok := secret.Data[DefaultRedisInitialPasswordKey]
if !ok {
return fmt.Errorf("secret %s/%s does not contain key %s", namespace, DefaultRedisInitialPasswordSecretName, DefaultRedisInitialPasswordKey)
}
redisOptions.Password = string(secret.Data[DefaultRedisInitialPasswordKey])
return nil
}

View File

@@ -1,12 +1,18 @@
package common
import (
"context"
"fmt"
"os"
"testing"
"time"
"github.com/redis/go-redis/v9"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubefake "k8s.io/client-go/kubernetes/fake"
)
// Test env var not set for EnvGRPCKeepAliveMin
@@ -44,3 +50,63 @@ func Test_GRPCKeepAliveMinIncorrectlySet(t *testing.T) {
grpcKeepAliveTime := GetGRPCKeepAliveTime()
assert.Equal(t, 2*grpcKeepAliveExpectedMin, grpcKeepAliveTime)
}
func TestSetOptionalRedisPasswordFromKubeConfig(t *testing.T) {
t.Parallel()
testCases := []struct {
name, namespace, expectedPassword, expectedErr string
secret *corev1.Secret
}{
{
name: "Secret exists with correct key",
namespace: "default",
expectedPassword: "password123",
expectedErr: "",
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: DefaultRedisInitialPasswordSecretName},
Data: map[string][]byte{DefaultRedisInitialPasswordKey: []byte("password123")},
},
},
{
name: "Secret does not exist",
namespace: "default",
expectedPassword: "",
expectedErr: fmt.Sprintf("failed to get secret default/%s", DefaultRedisInitialPasswordSecretName),
secret: nil,
},
{
name: "Secret exists without correct key",
namespace: "default",
expectedPassword: "",
expectedErr: fmt.Sprintf("secret default/%s does not contain key %s", DefaultRedisInitialPasswordSecretName, DefaultRedisInitialPasswordKey),
secret: &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: DefaultRedisInitialPasswordSecretName},
Data: map[string][]byte{},
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
var (
ctx = context.TODO()
kubeClient = kubefake.NewSimpleClientset()
redisOptions = &redis.Options{}
)
if tc.secret != nil {
if _, err := kubeClient.CoreV1().Secrets(tc.namespace).Create(ctx, tc.secret, metav1.CreateOptions{}); err != nil {
t.Fatalf("Failed to create secret: %v", err)
}
}
err := SetOptionalRedisPasswordFromKubeConfig(ctx, kubeClient, tc.namespace, redisOptions)
if tc.expectedErr != "" {
require.Error(t, err)
require.Contains(t, err.Error(), tc.expectedErr)
} else {
require.NoError(t, err)
}
require.Equal(t, tc.expectedPassword, redisOptions.Password)
})
}
}

View File

@@ -129,6 +129,7 @@ type ApplicationController struct {
statusHardRefreshTimeout time.Duration
statusRefreshJitter time.Duration
selfHealTimeout time.Duration
selfHealBackOff *wait.Backoff
repoClientset apiclient.Clientset
db db.ArgoDB
settingsMgr *settings_util.SettingsManager
@@ -159,6 +160,7 @@ func NewApplicationController(
appHardResyncPeriod time.Duration,
appResyncJitter time.Duration,
selfHealTimeout time.Duration,
selfHealBackoff *wait.Backoff,
repoErrorGracePeriod time.Duration,
metricsPort int,
metricsCacheExpiration time.Duration,
@@ -198,6 +200,7 @@ func NewApplicationController(
auditLogger: argo.NewAuditLogger(namespace, kubeClientset, common.ApplicationController),
settingsMgr: settingsMgr,
selfHealTimeout: selfHealTimeout,
selfHealBackOff: selfHealBackoff,
clusterSharding: clusterSharding,
projByNameCache: sync.Map{},
applicationNamespaces: applicationNamespaces,
@@ -1609,7 +1612,8 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
app.Status.Summary = tree.GetSummary(app)
}
if project.Spec.SyncWindows.Matches(app).CanSync(false) {
canSync, _ := project.Spec.SyncWindows.Matches(app).CanSync(false)
if canSync {
syncErrCond, opMS := ctrl.autoSync(app, compareResult.syncStatus, compareResult.resources)
setOpMs = opMS
if syncErrCond != nil {
@@ -1767,6 +1771,22 @@ func (ctrl *ApplicationController) normalizeApplication(orig, app *appv1.Applica
}
}
func createMergePatch(orig, new interface{}) ([]byte, bool, error) {
origBytes, err := json.Marshal(orig)
if err != nil {
return nil, false, err
}
newBytes, err := json.Marshal(new)
if err != nil {
return nil, false, err
}
patch, err := jsonpatch.CreateMergePatch(origBytes, newBytes)
if err != nil {
return nil, false, err
}
return patch, string(patch) != "{}", nil
}
// persistAppStatus persists updates to application status. If no changes were made, it is a no-op
func (ctrl *ApplicationController) persistAppStatus(orig *appv1.Application, newStatus *appv1.ApplicationStatus) (patchMs time.Duration) {
logCtx := getAppLog(orig)
@@ -1786,9 +1806,9 @@ func (ctrl *ApplicationController) persistAppStatus(orig *appv1.Application, new
}
delete(newAnnotations, appv1.AnnotationKeyRefresh)
}
patch, modified, err := diff.CreateTwoWayMergePatch(
patch, modified, err := createMergePatch(
&appv1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: orig.GetAnnotations()}, Status: orig.Status},
&appv1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: newAnnotations}, Status: *newStatus}, appv1.Application{})
&appv1.Application{ObjectMeta: metav1.ObjectMeta{Annotations: newAnnotations}, Status: *newStatus})
if err != nil {
logCtx.Errorf("Error constructing app status patch: %v", err)
return
@@ -1862,6 +1882,9 @@ func (ctrl *ApplicationController) autoSync(app *appv1.Application, syncStatus *
InitiatedBy: appv1.OperationInitiator{Automated: true},
Retry: appv1.RetryStrategy{Limit: 5},
}
if app.Status.OperationState != nil && app.Status.OperationState.Operation.Sync != nil {
op.Sync.SelfHealAttemptsCount = app.Status.OperationState.Operation.Sync.SelfHealAttemptsCount
}
if app.Spec.SyncPolicy.Retry != nil {
op.Retry = *app.Spec.SyncPolicy.Retry
}
@@ -1879,6 +1902,7 @@ func (ctrl *ApplicationController) autoSync(app *appv1.Application, syncStatus *
return nil, 0
} else if alreadyAttempted && selfHeal {
if shouldSelfHeal, retryAfter := ctrl.shouldSelfHeal(app); shouldSelfHeal {
op.Sync.SelfHealAttemptsCount++
for _, resource := range resources {
if resource.Status != appv1.SyncStatusCodeSynced {
op.Sync.Resources = append(op.Sync.Resources, appv1.SyncOperationResource{
@@ -1984,10 +2008,24 @@ func (ctrl *ApplicationController) shouldSelfHeal(app *appv1.Application) (bool,
}
var retryAfter time.Duration
if app.Status.OperationState.FinishedAt == nil {
retryAfter = ctrl.selfHealTimeout
if ctrl.selfHealBackOff == nil {
if app.Status.OperationState.FinishedAt == nil {
retryAfter = ctrl.selfHealTimeout
} else {
retryAfter = ctrl.selfHealTimeout - time.Since(app.Status.OperationState.FinishedAt.Time)
}
} else {
retryAfter = ctrl.selfHealTimeout - time.Since(app.Status.OperationState.FinishedAt.Time)
backOff := *ctrl.selfHealBackOff
backOff.Steps = int(app.Status.OperationState.Operation.Sync.SelfHealAttemptsCount)
var delay time.Duration
for backOff.Steps > 0 {
delay = backOff.Step()
}
if app.Status.OperationState.FinishedAt == nil {
retryAfter = delay
} else {
retryAfter = delay - time.Since(app.Status.OperationState.FinishedAt.Time)
}
}
return retryAfter <= 0, retryAfter
}
@@ -1995,7 +2033,7 @@ func (ctrl *ApplicationController) shouldSelfHeal(app *appv1.Application) (bool,
// isAppNamespaceAllowed returns whether the application is allowed in the
// namespace it's residing in.
func (ctrl *ApplicationController) isAppNamespaceAllowed(app *appv1.Application) bool {
return app.Namespace == ctrl.namespace || glob.MatchStringInList(ctrl.applicationNamespaces, app.Namespace, false)
return app.Namespace == ctrl.namespace || glob.MatchStringInList(ctrl.applicationNamespaces, app.Namespace, glob.REGEXP)
}
func (ctrl *ApplicationController) canProcessApp(obj interface{}) bool {

View File

@@ -4,16 +4,18 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"testing"
"time"
clustercache "github.com/argoproj/gitops-engine/pkg/cache"
"github.com/argoproj/gitops-engine/pkg/utils/kube/kubetest"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/rest"
clustercache "github.com/argoproj/gitops-engine/pkg/cache"
"k8s.io/utils/ptr"
"github.com/argoproj/argo-cd/v2/common"
statecache "github.com/argoproj/argo-cd/v2/controller/cache"
@@ -151,6 +153,7 @@ func newFakeController(data *fakeData, repoErr error) *ApplicationController {
time.Hour,
time.Second,
time.Minute,
nil,
time.Second*10,
common.DefaultPortArgoCDMetrics,
data.metricsCacheExpiration,
@@ -374,8 +377,8 @@ data:
var fakePostDeleteHook = `
{
"apiVersion": "v1",
"kind": "Pod",
"apiVersion": "batch/v1",
"kind": "Job",
"metadata": {
"name": "post-delete-hook",
"namespace": "default",
@@ -388,22 +391,93 @@ var fakePostDeleteHook = `
}
},
"spec": {
"containers": [
{
"name": "post-delete-hook",
"image": "busybox",
"restartPolicy": "Never",
"command": [
"/bin/sh",
"-c",
"sleep 5 && echo hello from the post-delete-hook pod"
]
"template": {
"metadata": {
"name": "post-delete-hook"
},
"spec": {
"containers": [
{
"name": "post-delete-hook",
"image": "busybox",
"command": [
"/bin/sh",
"-c",
"sleep 5 && echo hello from the post-delete-hook job"
]
}
],
"restartPolicy": "Never"
}
]
}
}
}
`
var fakeServiceAccount = `
{
"apiVersion": "v1",
"kind": "ServiceAccount",
"metadata": {
"name": "hook-serviceaccount",
"namespace": "default",
"annotations": {
"argocd.argoproj.io/hook": "PostDelete",
"argocd.argoproj.io/hook-delete-policy": "BeforeHookCreation,HookSucceeded"
}
}
}
`
var fakeRole = `
{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "Role",
"metadata": {
"name": "hook-role",
"namespace": "default",
"annotations": {
"argocd.argoproj.io/hook": "PostDelete",
"argocd.argoproj.io/hook-delete-policy": "BeforeHookCreation,HookSucceeded"
}
},
"rules": [
{
"apiGroups": [""],
"resources": ["secrets"],
"verbs": ["get", "delete", "list"]
}
]
}
`
var fakeRoleBinding = `
{
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "RoleBinding",
"metadata": {
"name": "hook-rolebinding",
"namespace": "default",
"annotations": {
"argocd.argoproj.io/hook": "PostDelete",
"argocd.argoproj.io/hook-delete-policy": "BeforeHookCreation,HookSucceeded"
}
},
"roleRef": {
"apiGroup": "rbac.authorization.k8s.io",
"kind": "Role",
"name": "hook-role"
},
"subjects": [
{
"kind": "ServiceAccount",
"name": "hook-serviceaccount",
"namespace": "default"
}
]
}
`
func newFakeApp() *v1alpha1.Application {
return createFakeApp(fakeApp)
}
@@ -439,12 +513,39 @@ func newFakeCM() map[string]interface{} {
}
func newFakePostDeleteHook() map[string]interface{} {
var cm map[string]interface{}
err := yaml.Unmarshal([]byte(fakePostDeleteHook), &cm)
var hook map[string]interface{}
err := yaml.Unmarshal([]byte(fakePostDeleteHook), &hook)
if err != nil {
panic(err)
}
return cm
return hook
}
func newFakeRoleBinding() map[string]interface{} {
var roleBinding map[string]interface{}
err := yaml.Unmarshal([]byte(fakeRoleBinding), &roleBinding)
if err != nil {
panic(err)
}
return roleBinding
}
func newFakeRole() map[string]interface{} {
var role map[string]interface{}
err := yaml.Unmarshal([]byte(fakeRole), &role)
if err != nil {
panic(err)
}
return role
}
func newFakeServiceAccount() map[string]interface{} {
var serviceAccount map[string]interface{}
err := yaml.Unmarshal([]byte(fakeServiceAccount), &serviceAccount)
if err != nil {
panic(err)
}
return serviceAccount
}
func TestAutoSync(t *testing.T) {
@@ -721,7 +822,7 @@ func TestFinalizeAppDeletion(t *testing.T) {
// Ensure any stray resources irregularly labeled with instance label of app are not deleted upon deleting,
// when app project restriction is in place
t.Run("ProjectRestrictionEnforced", func(*testing.T) {
t.Run("ProjectRestrictionEnforced", func(t *testing.T) {
restrictedProj := v1alpha1.AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: "restricted",
@@ -882,7 +983,13 @@ func TestFinalizeAppDeletion(t *testing.T) {
app.SetPostDeleteFinalizer()
app.Spec.Destination.Namespace = test.FakeArgoCDNamespace
liveHook := &unstructured.Unstructured{Object: newFakePostDeleteHook()}
require.NoError(t, unstructured.SetNestedField(liveHook.Object, "Succeeded", "status", "phase"))
conditions := []interface{}{
map[string]interface{}{
"type": "Complete",
"status": "True",
},
}
require.NoError(t, unstructured.SetNestedField(liveHook.Object, conditions, "status", "conditions"))
ctrl := newFakeController(&fakeData{
manifestResponses: []*apiclient.ManifestResponse{{
Manifests: []string{fakePostDeleteHook},
@@ -916,15 +1023,27 @@ func TestFinalizeAppDeletion(t *testing.T) {
app := newFakeApp()
app.SetPostDeleteFinalizer("cleanup")
app.Spec.Destination.Namespace = test.FakeArgoCDNamespace
liveRoleBinding := &unstructured.Unstructured{Object: newFakeRoleBinding()}
liveRole := &unstructured.Unstructured{Object: newFakeRole()}
liveServiceAccount := &unstructured.Unstructured{Object: newFakeServiceAccount()}
liveHook := &unstructured.Unstructured{Object: newFakePostDeleteHook()}
require.NoError(t, unstructured.SetNestedField(liveHook.Object, "Succeeded", "status", "phase"))
conditions := []interface{}{
map[string]interface{}{
"type": "Complete",
"status": "True",
},
}
require.NoError(t, unstructured.SetNestedField(liveHook.Object, conditions, "status", "conditions"))
ctrl := newFakeController(&fakeData{
manifestResponses: []*apiclient.ManifestResponse{{
Manifests: []string{fakePostDeleteHook},
Manifests: []string{fakeRoleBinding, fakeRole, fakeServiceAccount, fakePostDeleteHook},
}},
apps: []runtime.Object{app, &defaultProj},
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(liveHook): liveHook,
kube.GetResourceKey(liveRoleBinding): liveRoleBinding,
kube.GetResourceKey(liveRole): liveRole,
kube.GetResourceKey(liveServiceAccount): liveServiceAccount,
kube.GetResourceKey(liveHook): liveHook,
},
}, nil)
@@ -943,9 +1062,14 @@ func TestFinalizeAppDeletion(t *testing.T) {
return []*v1alpha1.Cluster{}, nil
})
require.NoError(t, err)
// post-delete hook is deleted
require.Len(t, ctrl.kubectl.(*MockKubectl).DeletedResources, 1)
require.Equal(t, "post-delete-hook", ctrl.kubectl.(*MockKubectl).DeletedResources[0].Name)
// post-delete hooks are deleted
require.Len(t, ctrl.kubectl.(*MockKubectl).DeletedResources, 4)
deletedResources := []string{}
for _, res := range ctrl.kubectl.(*MockKubectl).DeletedResources {
deletedResources = append(deletedResources, res.Name)
}
expectedNames := []string{"hook-rolebinding", "hook-role", "hook-serviceaccount", "post-delete-hook"}
require.ElementsMatch(t, expectedNames, deletedResources, "Deleted resources should match expected names")
// finalizer is not removed
assert.False(t, patched)
})
@@ -992,7 +1116,7 @@ func TestNormalizeApplication(t *testing.T) {
normalized := false
fakeAppCs.AddReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
if patchAction, ok := action.(kubetesting.PatchAction); ok {
if string(patchAction.GetPatch()) == `{"spec":{"project":"default"},"status":{"sync":{"comparedTo":{"destination":{},"source":{"repoURL":""}}}}}` {
if string(patchAction.GetPatch()) == `{"spec":{"project":"default"}}` {
normalized = true
}
}
@@ -1949,3 +2073,128 @@ func TestAddControllerNamespace(t *testing.T) {
assert.Equal(t, test.FakeArgoCDNamespace, updatedApp.Status.ControllerNamespace)
})
}
func TestHelmValuesObjectHasReplaceStrategy(t *testing.T) {
app := v1alpha1.Application{
Status: v1alpha1.ApplicationStatus{Sync: v1alpha1.SyncStatus{ComparedTo: v1alpha1.ComparedTo{
Source: v1alpha1.ApplicationSource{
Helm: &v1alpha1.ApplicationSourceHelm{
ValuesObject: &runtime.RawExtension{
Object: &unstructured.Unstructured{Object: map[string]interface{}{"key": []string{"value"}}},
},
},
},
}}},
}
appModified := v1alpha1.Application{
Status: v1alpha1.ApplicationStatus{Sync: v1alpha1.SyncStatus{ComparedTo: v1alpha1.ComparedTo{
Source: v1alpha1.ApplicationSource{
Helm: &v1alpha1.ApplicationSourceHelm{
ValuesObject: &runtime.RawExtension{
Object: &unstructured.Unstructured{Object: map[string]interface{}{"key": []string{"value-modified1"}}},
},
},
},
}}},
}
patch, _, err := createMergePatch(
app,
appModified)
require.NoError(t, err)
assert.Equal(t, `{"status":{"sync":{"comparedTo":{"source":{"helm":{"valuesObject":{"key":["value-modified1"]}}}}}}}`, string(patch))
}
func TestAppStatusIsReplaced(t *testing.T) {
original := &v1alpha1.ApplicationStatus{Sync: v1alpha1.SyncStatus{
ComparedTo: v1alpha1.ComparedTo{
Destination: v1alpha1.ApplicationDestination{
Server: "https://mycluster",
},
},
}}
updated := &v1alpha1.ApplicationStatus{Sync: v1alpha1.SyncStatus{
ComparedTo: v1alpha1.ComparedTo{
Destination: v1alpha1.ApplicationDestination{
Name: "mycluster",
},
},
}}
patchData, ok, err := createMergePatch(original, updated)
require.NoError(t, err)
require.True(t, ok)
patchObj := map[string]interface{}{}
require.NoError(t, json.Unmarshal(patchData, &patchObj))
val, has, err := unstructured.NestedFieldNoCopy(patchObj, "sync", "comparedTo", "destination", "server")
require.NoError(t, err)
require.True(t, has)
require.Nil(t, val)
}
func assertDurationAround(t *testing.T, expected time.Duration, actual time.Duration) {
delta := time.Second / 2
assert.GreaterOrEqual(t, expected, actual-delta)
assert.LessOrEqual(t, expected, actual+delta)
}
func TestSelfHealExponentialBackoff(t *testing.T) {
ctrl := newFakeController(&fakeData{}, nil)
ctrl.selfHealBackOff = &wait.Backoff{
Factor: 3,
Duration: 2 * time.Second,
Cap: 5 * time.Minute,
}
app := &v1alpha1.Application{
Status: v1alpha1.ApplicationStatus{
OperationState: &v1alpha1.OperationState{
Operation: v1alpha1.Operation{
Sync: &v1alpha1.SyncOperation{},
},
},
},
}
testCases := []struct {
attempts int64
finishedAt *metav1.Time
expectedDuration time.Duration
shouldSelfHeal bool
}{{
attempts: 0,
finishedAt: ptr.To(metav1.Now()),
expectedDuration: 0,
shouldSelfHeal: true,
}, {
attempts: 1,
finishedAt: ptr.To(metav1.Now()),
expectedDuration: 2 * time.Second,
shouldSelfHeal: false,
}, {
attempts: 2,
finishedAt: ptr.To(metav1.Now()),
expectedDuration: 6 * time.Second,
shouldSelfHeal: false,
}, {
attempts: 3,
finishedAt: nil,
expectedDuration: 18 * time.Second,
shouldSelfHeal: false,
}}
for i := range testCases {
tc := testCases[i]
t.Run(fmt.Sprintf("test case %d", i), func(t *testing.T) {
app.Status.OperationState.Operation.Sync.SelfHealAttemptsCount = tc.attempts
app.Status.OperationState.FinishedAt = tc.finishedAt
ok, duration := ctrl.shouldSelfHeal(app)
require.Equal(t, ok, tc.shouldSelfHeal)
assertDurationAround(t, tc.expectedDuration, duration)
})
}
}

View File

@@ -192,6 +192,7 @@ type cacheSettings struct {
clusterSettings clustercache.Settings
appInstanceLabelKey string
trackingMethod appv1.TrackingMethod
installationID string
// resourceOverrides provides a list of ignored differences to ignore watched resource updates
resourceOverrides map[string]appv1.ResourceOverride
@@ -220,6 +221,10 @@ func (c *liveStateCache) loadCacheSettings() (*cacheSettings, error) {
if err != nil {
return nil, err
}
installationID, err := c.settingsMgr.GetInstallationID()
if err != nil {
return nil, err
}
resourceUpdatesOverrides, err := c.settingsMgr.GetIgnoreResourceUpdatesOverrides()
if err != nil {
return nil, err
@@ -241,7 +246,7 @@ func (c *liveStateCache) loadCacheSettings() (*cacheSettings, error) {
ResourcesFilter: resourcesFilter,
}
return &cacheSettings{clusterSettings, appInstanceLabelKey, argo.GetTrackingMethod(c.settingsMgr), resourceUpdatesOverrides, ignoreResourceUpdatesEnabled}, nil
return &cacheSettings{clusterSettings, appInstanceLabelKey, argo.GetTrackingMethod(c.settingsMgr), installationID, resourceUpdatesOverrides, ignoreResourceUpdatesEnabled}, nil
}
func asResourceNode(r *clustercache.Resource) appv1.ResourceNode {
@@ -501,7 +506,7 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
res.Health, _ = health.GetResourceHealth(un, cacheSettings.clusterSettings.ResourceHealthOverride)
appName := c.resourceTracking.GetAppName(un, cacheSettings.appInstanceLabelKey, cacheSettings.trackingMethod)
appName := c.resourceTracking.GetAppName(un, cacheSettings.appInstanceLabelKey, cacheSettings.trackingMethod, cacheSettings.installationID)
if isRoot && appName != "" {
res.AppName = appName
}

View File

@@ -278,6 +278,32 @@ func populateIstioVirtualServiceInfo(un *unstructured.Unstructured, res *Resourc
res.NetworkingInfo = &v1alpha1.ResourceNetworkingInfo{TargetRefs: targets, ExternalURLs: urls}
}
func isPodInitializedConditionTrue(status *v1.PodStatus) bool {
for _, condition := range status.Conditions {
if condition.Type != v1.PodInitialized {
continue
}
return condition.Status == v1.ConditionTrue
}
return false
}
func isRestartableInitContainer(initContainer *v1.Container) bool {
if initContainer == nil {
return false
}
if initContainer.RestartPolicy == nil {
return false
}
return *initContainer.RestartPolicy == v1.ContainerRestartPolicyAlways
}
func isPodPhaseTerminal(phase v1.PodPhase) bool {
return phase == v1.PodFailed || phase == v1.PodSucceeded
}
func populatePodInfo(un *unstructured.Unstructured, res *ResourceInfo) {
pod := v1.Pod{}
err := runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, &pod)
@@ -288,7 +314,8 @@ func populatePodInfo(un *unstructured.Unstructured, res *ResourceInfo) {
totalContainers := len(pod.Spec.Containers)
readyContainers := 0
reason := string(pod.Status.Phase)
podPhase := pod.Status.Phase
reason := string(podPhase)
if pod.Status.Reason != "" {
reason = pod.Status.Reason
}
@@ -306,6 +333,21 @@ func populatePodInfo(un *unstructured.Unstructured, res *ResourceInfo) {
res.Images = append(res.Images, image)
}
// If the Pod carries {type:PodScheduled, reason:SchedulingGated}, set reason to 'SchedulingGated'.
for _, condition := range pod.Status.Conditions {
if condition.Type == v1.PodScheduled && condition.Reason == v1.PodReasonSchedulingGated {
reason = v1.PodReasonSchedulingGated
}
}
initContainers := make(map[string]*v1.Container)
for i := range pod.Spec.InitContainers {
initContainers[pod.Spec.InitContainers[i].Name] = &pod.Spec.InitContainers[i]
if isRestartableInitContainer(&pod.Spec.InitContainers[i]) {
totalContainers++
}
}
initializing := false
for i := range pod.Status.InitContainerStatuses {
container := pod.Status.InitContainerStatuses[i]
@@ -313,6 +355,12 @@ func populatePodInfo(un *unstructured.Unstructured, res *ResourceInfo) {
switch {
case container.State.Terminated != nil && container.State.Terminated.ExitCode == 0:
continue
case isRestartableInitContainer(initContainers[container.Name]) &&
container.Started != nil && *container.Started:
if container.Ready {
readyContainers++
}
continue
case container.State.Terminated != nil:
// initialization is failed
if len(container.State.Terminated.Reason) == 0 {
@@ -334,8 +382,7 @@ func populatePodInfo(un *unstructured.Unstructured, res *ResourceInfo) {
}
break
}
if !initializing {
restarts = 0
if !initializing || isPodInitializedConditionTrue(&pod.Status) {
hasRunning := false
for i := len(pod.Status.ContainerStatuses) - 1; i >= 0; i-- {
container := pod.Status.ContainerStatuses[i]
@@ -370,7 +417,9 @@ func populatePodInfo(un *unstructured.Unstructured, res *ResourceInfo) {
// and https://github.com/kubernetes/kubernetes/issues/90358#issuecomment-617859364
if pod.DeletionTimestamp != nil && pod.Status.Reason == "NodeLost" {
reason = "Unknown"
} else if pod.DeletionTimestamp != nil {
// If the pod is being deleted and the pod phase is not succeeded or failed, set the reason to "Terminating".
// See https://github.com/kubernetes/kubectl/issues/1595#issuecomment-2080001023
} else if pod.DeletionTimestamp != nil && !isPodPhaseTerminal(podPhase) {
reason = "Terminating"
}

View File

@@ -285,6 +285,552 @@ func TestGetPodInfo(t *testing.T) {
assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{Labels: map[string]string{"app": "guestbook"}}, info.NetworkingInfo)
}
func TestGetPodWithInitialContainerInfo(t *testing.T) {
pod := strToUnstructured(`
apiVersion: "v1"
kind: "Pod"
metadata:
labels:
app: "app-with-initial-container"
name: "app-with-initial-container-5f46976fdb-vd6rv"
namespace: "default"
ownerReferences:
- apiVersion: "apps/v1"
kind: "ReplicaSet"
name: "app-with-initial-container-5f46976fdb"
spec:
containers:
- image: "alpine:latest"
imagePullPolicy: "Always"
name: "app-with-initial-container"
initContainers:
- image: "alpine:latest"
imagePullPolicy: "Always"
name: "app-with-initial-container-logshipper"
nodeName: "minikube"
status:
containerStatuses:
- image: "alpine:latest"
name: "app-with-initial-container"
ready: true
restartCount: 0
started: true
state:
running:
startedAt: "2024-10-08T08:44:25Z"
initContainerStatuses:
- image: "alpine:latest"
name: "app-with-initial-container-logshipper"
ready: true
restartCount: 0
started: false
state:
terminated:
exitCode: 0
reason: "Completed"
phase: "Running"
`)
info := &ResourceInfo{}
populateNodeInfo(pod, info, []string{})
assert.Equal(t, []v1alpha1.InfoItem{
{Name: "Status Reason", Value: "Running"},
{Name: "Node", Value: "minikube"},
{Name: "Containers", Value: "1/1"},
}, info.Info)
}
func TestGetPodInfoWithSidecar(t *testing.T) {
pod := strToUnstructured(`
apiVersion: v1
kind: Pod
metadata:
labels:
app: app-with-sidecar
name: app-with-sidecar-6664cc788c-lqlrp
namespace: default
ownerReferences:
- apiVersion: apps/v1
kind: ReplicaSet
name: app-with-sidecar-6664cc788c
spec:
containers:
- image: 'docker.m.daocloud.io/library/alpine:latest'
imagePullPolicy: Always
name: app-with-sidecar
initContainers:
- image: 'docker.m.daocloud.io/library/alpine:latest'
imagePullPolicy: Always
name: logshipper
restartPolicy: Always
nodeName: minikube
status:
containerStatuses:
- image: 'docker.m.daocloud.io/library/alpine:latest'
name: app-with-sidecar
ready: true
restartCount: 0
started: true
state:
running:
startedAt: '2024-10-08T08:39:43Z'
initContainerStatuses:
- image: 'docker.m.daocloud.io/library/alpine:latest'
name: logshipper
ready: true
restartCount: 0
started: true
state:
running:
startedAt: '2024-10-08T08:39:40Z'
phase: Running
`)
info := &ResourceInfo{}
populateNodeInfo(pod, info, []string{})
assert.Equal(t, []v1alpha1.InfoItem{
{Name: "Status Reason", Value: "Running"},
{Name: "Node", Value: "minikube"},
{Name: "Containers", Value: "2/2"},
}, info.Info)
}
func TestGetPodInfoWithInitialContainer(t *testing.T) {
pod := strToUnstructured(`
apiVersion: v1
kind: Pod
metadata:
generateName: myapp-long-exist-56b7d8794d-
labels:
app: myapp-long-exist
name: myapp-long-exist-56b7d8794d-pbgrd
namespace: linghao
ownerReferences:
- apiVersion: apps/v1
kind: ReplicaSet
name: myapp-long-exist-56b7d8794d
spec:
containers:
- image: alpine:latest
imagePullPolicy: Always
name: myapp-long-exist
initContainers:
- image: alpine:latest
imagePullPolicy: Always
name: myapp-long-exist-logshipper
nodeName: minikube
status:
containerStatuses:
- image: alpine:latest
name: myapp-long-exist
ready: false
restartCount: 0
started: false
state:
waiting:
reason: PodInitializing
initContainerStatuses:
- image: alpine:latest
name: myapp-long-exist-logshipper
ready: false
restartCount: 0
started: true
state:
running:
startedAt: '2024-10-09T08:03:45Z'
phase: Pending
startTime: '2024-10-09T08:02:39Z'
`)
info := &ResourceInfo{}
populateNodeInfo(pod, info, []string{})
assert.Equal(t, []v1alpha1.InfoItem{
{Name: "Status Reason", Value: "Init:0/1"},
{Name: "Node", Value: "minikube"},
{Name: "Containers", Value: "0/1"},
}, info.Info)
}
// Test pod has 2 restartable init containers, the first one running but not started.
func TestGetPodInfoWithRestartableInitContainer(t *testing.T) {
pod := strToUnstructured(`
apiVersion: v1
kind: Pod
metadata:
name: test1
spec:
initContainers:
- name: restartable-init-1
restartPolicy: Always
- name: restartable-init-2
restartPolicy: Always
containers:
- name: container
nodeName: minikube
status:
phase: Pending
initContainerStatuses:
- name: restartable-init-1
ready: false
restartCount: 3
state:
running: {}
started: false
lastTerminationState:
terminated:
finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time
- name: restartable-init-2
ready: false
state:
waiting: {}
started: false
containerStatuses:
- ready: false
restartCount: 0
state:
waiting: {}
conditions:
- type: ContainersReady
status: "False"
- type: Initialized
status: "False"
`)
info := &ResourceInfo{}
populateNodeInfo(pod, info, []string{})
assert.Equal(t, []v1alpha1.InfoItem{
{Name: "Status Reason", Value: "Init:0/2"},
{Name: "Node", Value: "minikube"},
{Name: "Containers", Value: "0/3"},
{Name: "Restart Count", Value: "3"},
}, info.Info)
}
// Test pod has 2 restartable init containers, the first one started and the second one running but not started.
func TestGetPodInfoWithPartiallyStartedInitContainers(t *testing.T) {
pod := strToUnstructured(`
apiVersion: v1
kind: Pod
metadata:
name: test1
spec:
initContainers:
- name: restartable-init-1
restartPolicy: Always
- name: restartable-init-2
restartPolicy: Always
containers:
- name: container
nodeName: minikube
status:
phase: Pending
initContainerStatuses:
- name: restartable-init-1
ready: false
restartCount: 3
state:
running: {}
started: true
lastTerminationState:
terminated:
finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time
- name: restartable-init-2
ready: false
state:
running: {}
started: false
containerStatuses:
- ready: false
restartCount: 0
state:
waiting: {}
conditions:
- type: ContainersReady
status: "False"
- type: Initialized
status: "False"
`)
info := &ResourceInfo{}
populateNodeInfo(pod, info, []string{})
assert.Equal(t, []v1alpha1.InfoItem{
{Name: "Status Reason", Value: "Init:1/2"},
{Name: "Node", Value: "minikube"},
{Name: "Containers", Value: "0/3"},
{Name: "Restart Count", Value: "3"},
}, info.Info)
}
// Test pod has 2 restartable init containers started and 1 container running
func TestGetPodInfoWithStartedInitContainers(t *testing.T) {
pod := strToUnstructured(`
apiVersion: v1
kind: Pod
metadata:
name: test2
spec:
initContainers:
- name: restartable-init-1
restartPolicy: Always
- name: restartable-init-2
restartPolicy: Always
containers:
- name: container
nodeName: minikube
status:
phase: Running
initContainerStatuses:
- name: restartable-init-1
ready: false
restartCount: 3
state:
running: {}
started: true
lastTerminationState:
terminated:
finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time
- name: restartable-init-2
ready: false
state:
running: {}
started: true
containerStatuses:
- ready: true
restartCount: 4
state:
running: {}
lastTerminationState:
terminated:
finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time
conditions:
- type: ContainersReady
status: "False"
- type: Initialized
status: "True"
`)
info := &ResourceInfo{}
populateNodeInfo(pod, info, []string{})
assert.Equal(t, []v1alpha1.InfoItem{
{Name: "Status Reason", Value: "Running"},
{Name: "Node", Value: "minikube"},
{Name: "Containers", Value: "1/3"},
{Name: "Restart Count", Value: "7"},
}, info.Info)
}
// Test pod has 1 init container restarting and 1 container not running
func TestGetPodInfoWithNormalInitContainer(t *testing.T) {
pod := strToUnstructured(`
apiVersion: v1
kind: Pod
metadata:
name: test7
spec:
initContainers:
- name: init-container
containers:
- name: main-container
nodeName: minikube
status:
phase: podPhase
initContainerStatuses:
- ready: false
restartCount: 3
state:
running: {}
lastTerminationState:
terminated:
finishedAt: "2023-10-01T00:00:00Z" # Replace with the actual time
containerStatuses:
- ready: false
restartCount: 0
state:
waiting: {}
`)
info := &ResourceInfo{}
populateNodeInfo(pod, info, []string{})
assert.Equal(t, []v1alpha1.InfoItem{
{Name: "Status Reason", Value: "Init:0/1"},
{Name: "Node", Value: "minikube"},
{Name: "Containers", Value: "0/1"},
{Name: "Restart Count", Value: "3"},
}, info.Info)
}
// Test pod condition succeed
func TestPodConditionSucceeded(t *testing.T) {
pod := strToUnstructured(`
apiVersion: v1
kind: Pod
metadata:
name: test8
spec:
nodeName: minikube
containers:
- name: container
status:
phase: Succeeded
containerStatuses:
- ready: false
restartCount: 0
state:
terminated:
reason: Completed
exitCode: 0
`)
info := &ResourceInfo{}
populateNodeInfo(pod, info, []string{})
assert.Equal(t, []v1alpha1.InfoItem{
{Name: "Status Reason", Value: "Completed"},
{Name: "Node", Value: "minikube"},
{Name: "Containers", Value: "0/1"},
}, info.Info)
}
// Test pod condition failed
func TestPodConditionFailed(t *testing.T) {
pod := strToUnstructured(`
apiVersion: v1
kind: Pod
metadata:
name: test9
spec:
nodeName: minikube
containers:
- name: container
status:
phase: Failed
containerStatuses:
- ready: false
restartCount: 0
state:
terminated:
reason: Error
exitCode: 1
`)
info := &ResourceInfo{}
populateNodeInfo(pod, info, []string{})
assert.Equal(t, []v1alpha1.InfoItem{
{Name: "Status Reason", Value: "Error"},
{Name: "Node", Value: "minikube"},
{Name: "Containers", Value: "0/1"},
}, info.Info)
}
// Test pod condition succeed with deletion
func TestPodConditionSucceededWithDeletion(t *testing.T) {
pod := strToUnstructured(`
apiVersion: v1
kind: Pod
metadata:
name: test10
deletionTimestamp: "2023-10-01T00:00:00Z"
spec:
nodeName: minikube
containers:
- name: container
status:
phase: Succeeded
containerStatuses:
- ready: false
restartCount: 0
state:
terminated:
reason: Completed
exitCode: 0
`)
info := &ResourceInfo{}
populateNodeInfo(pod, info, []string{})
assert.Equal(t, []v1alpha1.InfoItem{
{Name: "Status Reason", Value: "Completed"},
{Name: "Node", Value: "minikube"},
{Name: "Containers", Value: "0/1"},
}, info.Info)
}
// Test pod condition running with deletion
func TestPodConditionRunningWithDeletion(t *testing.T) {
pod := strToUnstructured(`
apiVersion: v1
kind: Pod
metadata:
name: test11
deletionTimestamp: "2023-10-01T00:00:00Z"
spec:
nodeName: minikube
containers:
- name: container
status:
phase: Running
containerStatuses:
- ready: false
restartCount: 0
state:
running: {}
`)
info := &ResourceInfo{}
populateNodeInfo(pod, info, []string{})
assert.Equal(t, []v1alpha1.InfoItem{
{Name: "Status Reason", Value: "Terminating"},
{Name: "Node", Value: "minikube"},
{Name: "Containers", Value: "0/1"},
}, info.Info)
}
// Test pod condition pending with deletion
func TestPodConditionPendingWithDeletion(t *testing.T) {
pod := strToUnstructured(`
apiVersion: v1
kind: Pod
metadata:
name: test12
deletionTimestamp: "2023-10-01T00:00:00Z"
spec:
nodeName: minikube
containers:
- name: container
status:
phase: Pending
`)
info := &ResourceInfo{}
populateNodeInfo(pod, info, []string{})
assert.Equal(t, []v1alpha1.InfoItem{
{Name: "Status Reason", Value: "Terminating"},
{Name: "Node", Value: "minikube"},
{Name: "Containers", Value: "0/1"},
}, info.Info)
}
// Test PodScheduled condition with reason SchedulingGated
func TestPodScheduledWithSchedulingGated(t *testing.T) {
pod := strToUnstructured(`
apiVersion: v1
kind: Pod
metadata:
name: test13
spec:
nodeName: minikube
containers:
- name: container1
- name: container2
status:
phase: podPhase
conditions:
- type: PodScheduled
status: "False"
reason: SchedulingGated
`)
info := &ResourceInfo{}
populateNodeInfo(pod, info, []string{})
assert.Equal(t, []v1alpha1.InfoItem{
{Name: "Status Reason", Value: "SchedulingGated"},
{Name: "Node", Value: "minikube"},
{Name: "Containers", Value: "0/2"},
}, info.Info)
}
func TestGetNodeInfo(t *testing.T) {
node := strToUnstructured(`
apiVersion: v1

View File

@@ -98,6 +98,18 @@ func (ctrl *ApplicationController) executePostDeleteHooks(app *v1alpha1.Applicat
if err != nil {
return false, err
}
if hookHealth == nil {
logCtx.WithFields(log.Fields{
"group": obj.GroupVersionKind().Group,
"version": obj.GroupVersionKind().Version,
"kind": obj.GetKind(),
"name": obj.GetName(),
"namespace": obj.GetNamespace(),
}).Info("No health check defined for resource, considering it healthy")
hookHealth = &health.HealthStatus{
Status: health.HealthStatusHealthy,
}
}
if hookHealth.Status == health.HealthStatusProgressing {
progressingHooksCnt++
}
@@ -128,6 +140,11 @@ func (ctrl *ApplicationController) cleanupPostDeleteHooks(liveObjs map[kube.Reso
if err != nil {
return false, err
}
if hookHealth == nil {
hookHealth = &health.HealthStatus{
Status: health.HealthStatusHealthy,
}
}
if health.IsWorse(aggregatedHealth, hookHealth.Status) {
aggregatedHealth = hookHealth.Status
}

View File

@@ -160,6 +160,11 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp
return nil, nil, fmt.Errorf("failed to get Helm settings: %w", err)
}
installationID, err := m.settingsMgr.GetInstallationID()
if err != nil {
return nil, nil, fmt.Errorf("failed to get installation ID: %w", err)
}
ts.AddCheckpoint("build_options_ms")
serverVersion, apiResources, err := m.liveStateCache.GetVersionsInfo(app.Spec.Destination.Server)
if err != nil {
@@ -222,6 +227,7 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp
TrackingMethod: string(argo.GetTrackingMethod(m.settingsMgr)),
RefSources: refSources,
HasMultipleSources: app.Spec.HasMultipleSources(),
InstallationID: installationID,
})
if err != nil {
return nil, nil, fmt.Errorf("failed to compare revisions for source %d of %d: %w", i+1, len(sources), err)
@@ -252,6 +258,7 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp
RefSources: refSources,
ProjectName: proj.Name,
ProjectSourceRepos: proj.Spec.SourceRepos,
InstallationID: installationID,
})
if err != nil {
return nil, nil, fmt.Errorf("failed to generate manifest for source %d of %d: %w", i+1, len(sources), err)
@@ -329,20 +336,24 @@ func DeduplicateTargetObjects(
// getComparisonSettings will return the system level settings related to the
// diff/normalization process.
func (m *appStateManager) getComparisonSettings() (string, map[string]v1alpha1.ResourceOverride, *settings.ResourcesFilter, error) {
func (m *appStateManager) getComparisonSettings() (string, map[string]v1alpha1.ResourceOverride, *settings.ResourcesFilter, string, error) {
resourceOverrides, err := m.settingsMgr.GetResourceOverrides()
if err != nil {
return "", nil, nil, err
return "", nil, nil, "", err
}
appLabelKey, err := m.settingsMgr.GetAppInstanceLabelKey()
if err != nil {
return "", nil, nil, err
return "", nil, nil, "", err
}
resFilter, err := m.settingsMgr.GetResourcesFilter()
if err != nil {
return "", nil, nil, err
return "", nil, nil, "", err
}
return appLabelKey, resourceOverrides, resFilter, nil
installationID, err := m.settingsMgr.GetInstallationID()
if err != nil {
return "", nil, nil, "", err
}
return appLabelKey, resourceOverrides, resFilter, installationID, nil
}
// verifyGnuPGSignature verifies the result of a GnuPG operation for a given git
@@ -393,7 +404,7 @@ func isManagedNamespace(ns *unstructured.Unstructured, app *v1alpha1.Application
// revision and overrides in the app spec.
func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1alpha1.AppProject, revisions []string, sources []v1alpha1.ApplicationSource, noCache bool, noRevisionCache bool, localManifests []string, hasMultipleSources bool, rollback bool) (*comparisonResult, error) {
ts := stats.NewTimingStats()
appLabelKey, resourceOverrides, resFilter, err := m.getComparisonSettings()
appLabelKey, resourceOverrides, resFilter, installationID, err := m.getComparisonSettings()
ts.AddCheckpoint("settings_ms")
@@ -559,7 +570,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
for _, liveObj := range liveObjByKey {
if liveObj != nil {
appInstanceName := m.resourceTracking.GetAppName(liveObj, appLabelKey, trackingMethod)
appInstanceName := m.resourceTracking.GetAppName(liveObj, appLabelKey, trackingMethod, installationID)
if appInstanceName != "" && appInstanceName != app.InstanceName(m.namespace) {
fqInstanceName := strings.ReplaceAll(appInstanceName, "_", "/")
conditions = append(conditions, v1alpha1.ApplicationCondition{
@@ -698,7 +709,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
}
gvk := obj.GroupVersionKind()
isSelfReferencedObj := m.isSelfReferencedObj(liveObj, targetObj, app.GetName(), appLabelKey, trackingMethod)
isSelfReferencedObj := m.isSelfReferencedObj(liveObj, targetObj, app.GetName(), appLabelKey, trackingMethod, installationID)
resState := v1alpha1.ResourceStatus{
Namespace: obj.GetNamespace(),
@@ -897,9 +908,7 @@ func useDiffCache(noCache bool, manifestInfos []*apiclient.ManifestResponse, sou
return false
}
currentSpec := app.BuildComparedToStatus()
specChanged := !reflect.DeepEqual(app.Status.Sync.ComparedTo, currentSpec)
if specChanged {
if !specEqualsCompareTo(app.Spec, app.Status.Sync.ComparedTo) {
log.WithField("useDiffCache", "false").Debug("specChanged")
return false
}
@@ -908,6 +917,29 @@ func useDiffCache(noCache bool, manifestInfos []*apiclient.ManifestResponse, sou
return true
}
// specEqualsCompareTo compares the application spec to the comparedTo status. It normalizes the destination to match
// the comparedTo destination before comparing. It does not mutate the original spec or comparedTo.
func specEqualsCompareTo(spec v1alpha1.ApplicationSpec, comparedTo v1alpha1.ComparedTo) bool {
// Make a copy to be sure we don't mutate the original.
specCopy := spec.DeepCopy()
currentSpec := specCopy.BuildComparedToStatus()
// The spec might have been augmented to include both server and name, so change it to match the comparedTo before
// comparing.
if comparedTo.Destination.Server == "" {
currentSpec.Destination.Server = ""
}
if comparedTo.Destination.Name == "" {
currentSpec.Destination.Name = ""
}
// Set IsServerInferred to false on both, because that field is not important for comparison.
comparedTo.Destination.SetIsServerInferred(false)
currentSpec.Destination.SetIsServerInferred(false)
return reflect.DeepEqual(comparedTo, currentSpec)
}
func (m *appStateManager) persistRevisionHistory(
app *v1alpha1.Application,
revision string,
@@ -1002,7 +1034,7 @@ func NewAppStateManager(
// group and kind) match the properties of the live object, or if the tracking method
// used does not provide the required properties for matching.
// Reference: https://github.com/argoproj/argo-cd/issues/8683
func (m *appStateManager) isSelfReferencedObj(live, config *unstructured.Unstructured, appName, appLabelKey string, trackingMethod v1alpha1.TrackingMethod) bool {
func (m *appStateManager) isSelfReferencedObj(live, config *unstructured.Unstructured, appName, appLabelKey string, trackingMethod v1alpha1.TrackingMethod, installationID string) bool {
if live == nil {
return true
}
@@ -1035,7 +1067,7 @@ func (m *appStateManager) isSelfReferencedObj(live, config *unstructured.Unstruc
// to match the properties from the live object. Cluster scoped objects
// carry the app's destination namespace in the tracking annotation,
// but are unique in GVK + name combination.
appInstance := m.resourceTracking.GetAppInstance(live, appLabelKey, trackingMethod)
appInstance := m.resourceTracking.GetAppInstance(live, appLabelKey, trackingMethod, installationID)
if appInstance != nil {
return isSelfReferencedObj(live, *appInstance)
}

View File

@@ -1372,8 +1372,8 @@ func TestIsLiveResourceManaged(t *testing.T) {
configObj := managedObj.DeepCopy()
// then
assert.True(t, manager.isSelfReferencedObj(managedObj, configObj, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
assert.True(t, manager.isSelfReferencedObj(managedObj, configObj, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
assert.True(t, manager.isSelfReferencedObj(managedObj, configObj, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel, ""))
assert.True(t, manager.isSelfReferencedObj(managedObj, configObj, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation, ""))
})
t.Run("will return true if tracked with label", func(t *testing.T) {
// given
@@ -1381,43 +1381,43 @@ func TestIsLiveResourceManaged(t *testing.T) {
configObj := managedObjWithLabel.DeepCopy()
// then
assert.True(t, manager.isSelfReferencedObj(managedObjWithLabel, configObj, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
assert.True(t, manager.isSelfReferencedObj(managedObjWithLabel, configObj, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel, ""))
})
t.Run("will handle if trackingId has wrong resource name and config is nil", func(t *testing.T) {
// given
t.Parallel()
// then
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongName, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongName, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongName, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel, ""))
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongName, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation, ""))
})
t.Run("will handle if trackingId has wrong resource group and config is nil", func(t *testing.T) {
// given
t.Parallel()
// then
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongGroup, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongGroup, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongGroup, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel, ""))
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongGroup, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation, ""))
})
t.Run("will handle if trackingId has wrong kind and config is nil", func(t *testing.T) {
// given
t.Parallel()
// then
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongKind, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongKind, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongKind, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel, ""))
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongKind, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation, ""))
})
t.Run("will handle if trackingId has wrong namespace and config is nil", func(t *testing.T) {
// given
t.Parallel()
// then
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongNamespace, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongNamespace, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotationAndLabel))
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongNamespace, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel, ""))
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongNamespace, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotationAndLabel, ""))
})
t.Run("will return true if live is nil", func(t *testing.T) {
t.Parallel()
assert.True(t, manager.isSelfReferencedObj(nil, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
assert.True(t, manager.isSelfReferencedObj(nil, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation, ""))
})
t.Run("will handle upgrade in desired state APIGroup", func(t *testing.T) {
@@ -1427,11 +1427,13 @@ func TestIsLiveResourceManaged(t *testing.T) {
delete(config.GetAnnotations(), common.AnnotationKeyAppInstance)
// then
assert.True(t, manager.isSelfReferencedObj(managedWrongAPIGroup, config, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
assert.True(t, manager.isSelfReferencedObj(managedWrongAPIGroup, config, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation, ""))
})
}
func TestUseDiffCache(t *testing.T) {
t.Parallel()
type fixture struct {
testName string
noCache bool
@@ -1527,6 +1529,10 @@ func TestUseDiffCache(t *testing.T) {
t.Fatalf("error merging app: %s", err)
}
}
if app.Spec.Destination.Name != "" && app.Spec.Destination.Server != "" {
// Simulate the controller's process for populating both of these fields.
app.Spec.Destination.SetInferredServer(app.Spec.Destination.Server)
}
return app
}
@@ -1692,6 +1698,44 @@ func TestUseDiffCache(t *testing.T) {
expectedUseCache: false,
serverSideDiff: false,
},
{
// There are code paths that modify the ApplicationSpec and augment the destination field with both the
// destination server and name. Since both fields are populated in the app spec but not in the comparedTo,
// we need to make sure we correctly compare the fields and don't miss the cache.
testName: "will return true if the app spec destination contains both server and name, but otherwise matches comparedTo",
noCache: false,
manifestInfos: manifestInfos("rev1"),
sources: sources(),
app: app("httpbin", "rev1", false, &argoappv1.Application{
Spec: argoappv1.ApplicationSpec{
Destination: argoappv1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Name: "httpbin",
Namespace: "httpbin",
},
},
Status: argoappv1.ApplicationStatus{
Resources: []argoappv1.ResourceStatus{},
Sync: argoappv1.SyncStatus{
Status: argoappv1.SyncStatusCodeSynced,
ComparedTo: argoappv1.ComparedTo{
Destination: argoappv1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "httpbin",
},
},
Revision: "rev1",
},
ReconciledAt: &metav1.Time{
Time: time.Now().Add(-time.Hour),
},
},
}),
manifestRevisions: []string{"rev1"},
statusRefreshTimeout: time.Hour * 24,
expectedUseCache: true,
serverSideDiff: true,
},
}
for _, tc := range cases {

View File

@@ -167,12 +167,18 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
state.Phase = common.OperationError
state.Message = fmt.Sprintf("Failed to load application project: %v", err)
return
} else if syncWindowPreventsSync(app, proj) {
// If the operation is currently running, simply let the user know the sync is blocked by a current sync window
if state.Phase == common.OperationRunning {
state.Message = "Sync operation blocked by sync window"
} else {
isBlocked, err := syncWindowPreventsSync(app, proj)
if isBlocked {
// If the operation is currently running, simply let the user know the sync is blocked by a current sync window
if state.Phase == common.OperationRunning {
state.Message = "Sync operation blocked by sync window"
if err != nil {
state.Message = fmt.Sprintf("%s: %v", state.Message, err)
}
}
return
}
return
}
if !isMultiSourceRevision {
@@ -282,6 +288,11 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
log.Errorf("Could not get appInstanceLabelKey: %v", err)
return
}
installationID, err := m.settingsMgr.GetInstallationID()
if err != nil {
log.Errorf("Could not get installation ID: %v", err)
return
}
trackingMethod := argo.GetTrackingMethod(m.settingsMgr)
opts := []sync.SyncOpt{
@@ -311,7 +322,7 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
return (len(syncOp.Resources) == 0 ||
isPostDeleteHook(target) ||
argo.ContainsSyncResource(key.Name, key.Namespace, schema.GroupVersionKind{Kind: key.Kind, Group: key.Group}, syncOp.Resources)) &&
m.isSelfReferencedObj(live, target, app.GetName(), appLabelKey, trackingMethod)
m.isSelfReferencedObj(live, target, app.GetName(), appLabelKey, trackingMethod, installationID)
}),
sync.WithManifestValidation(!syncOp.SyncOptions.HasOption(common.SyncOptionsDisableValidation)),
sync.WithSyncWaveHook(delayBetweenSyncWaves),
@@ -528,11 +539,16 @@ func delayBetweenSyncWaves(phase common.SyncPhase, wave int, finalWave bool) err
return nil
}
func syncWindowPreventsSync(app *v1alpha1.Application, proj *v1alpha1.AppProject) bool {
func syncWindowPreventsSync(app *v1alpha1.Application, proj *v1alpha1.AppProject) (bool, error) {
window := proj.Spec.SyncWindows.Matches(app)
isManual := false
if app.Status.OperationState != nil {
isManual = !app.Status.OperationState.Operation.InitiatedBy.Automated
}
return !window.CanSync(isManual)
canSync, err := window.CanSync(isManual)
if err != nil {
// prevents sync because sync window has an error
return true, err
}
return !canSync, nil
}

View File

@@ -19,41 +19,119 @@ const observerCallback = function(mutationsList, observer) {
const observer = new MutationObserver(observerCallback);
observer.observe(targetNode, observerOptions);
function getCurrentVersion() {
const currentVersion = window.location.href.match(/\/en\/(release-(?:v\d+|[\d\.]+|\w+)|latest|stable)\//);
if (currentVersion && currentVersion.length > 1) {
return currentVersion[1];
}
return null;
}
function initializeVersionDropdown() {
const callbackName = 'callback_' + new Date().getTime();
window[callbackName] = function(response) {
const div = document.createElement('div');
div.innerHTML = response.html;
document.querySelector(".md-header__inner > .md-header__title").appendChild(div);
const headerTitle = document.querySelector(".md-header__inner > .md-header__title");
if (headerTitle) {
headerTitle.appendChild(div);
}
const container = div.querySelector('.rst-versions');
if (!container) return; // Exit if container not found
// Add caret icon
var caret = document.createElement('div');
caret.innerHTML = "<i class='fa fa-caret-down dropdown-caret'></i>";
caret.classList.add('dropdown-caret');
div.querySelector('.rst-current-version').appendChild(caret);
const currentVersionElem = div.querySelector('.rst-current-version');
if (currentVersionElem) {
currentVersionElem.appendChild(caret);
}
div.querySelector('.rst-current-version').addEventListener('click', function() {
container.classList.toggle('shift-up');
});
// Add click listener to toggle dropdown
if (currentVersionElem && container) {
currentVersionElem.addEventListener('click', function() {
container.classList.toggle('shift-up');
});
}
// Sorting Logic
sortVersionLinks(container);
};
// Load CSS
var CSSLink = document.createElement('link');
CSSLink.rel = 'stylesheet';
CSSLink.href = '/assets/versions.css';
document.getElementsByTagName('head')[0].appendChild(CSSLink);
// Load JSONP Script
var script = document.createElement('script');
const currentVersion = getCurrentVersion();
script.src = 'https://argo-cd.readthedocs.io/_/api/v2/footer_html/?' +
'callback=' + callbackName + '&project=argo-cd&page=&theme=mkdocs&format=jsonp&docroot=docs&source_suffix=.md&version=' + (window['READTHEDOCS_DATA'] || { version: 'latest' }).version;
'callback=' + callbackName + '&project=argo-cd&page=&theme=mkdocs&format=jsonp&docroot=docs&source_suffix=.md&version=' + (currentVersion || 'latest');
document.getElementsByTagName('head')[0].appendChild(script);
}
// Function to sort version links
function sortVersionLinks(container) {
// Find all <dl> elements within the container
const dlElements = container.querySelectorAll('dl');
dlElements.forEach(dl => {
const dt = dl.querySelector('dt');
if (dt && dt.textContent.trim().toLowerCase() === 'versions') {
// Found the Versions <dl>
const ddElements = Array.from(dl.querySelectorAll('dd'));
// Define sorting criteria
ddElements.sort((a, b) => {
const aText = a.textContent.trim().toLowerCase();
const bText = b.textContent.trim().toLowerCase();
// Prioritize 'latest' and 'stable'
if (aText === 'latest') return -1;
if (bText === 'latest') return 1;
if (aText === 'stable') return -1;
if (bText === 'stable') return 1;
// Extract version numbers (e.g., release-2.9)
const aVersionMatch = aText.match(/release-(\d+(\.\d+)*)/);
const bVersionMatch = bText.match(/release-(\d+(\.\d+)*)/);
if (aVersionMatch && bVersionMatch) {
const aVersion = aVersionMatch[1].split('.').map(Number);
const bVersion = bVersionMatch[1].split('.').map(Number);
for (let i = 0; i < Math.max(aVersion.length, bVersion.length); i++) {
const aNum = aVersion[i] || 0;
const bNum = bVersion[i] || 0;
if (aNum > bNum) return -1;
if (aNum < bNum) return 1;
}
return 0;
}
// Fallback to alphabetical order
return aText.localeCompare(bText);
});
// Remove existing <dd> elements
ddElements.forEach(dd => dl.removeChild(dd));
// Append sorted <dd> elements
ddElements.forEach(dd => dl.appendChild(dd));
}
});
}
// VERSION WARNINGS
window.addEventListener("DOMContentLoaded", function() {
var currentVersion = window.location.href.match(/\/en\/(release-(?:v\d+|\w+)|latest|stable)\//);
var margin = 30;
var headerHeight = document.getElementsByClassName("md-header")[0].offsetHeight;
if (currentVersion && currentVersion.length > 1) {
currentVersion = currentVersion[1];
const currentVersion = getCurrentVersion();
if (currentVersion) {
if (currentVersion === "latest") {
document.querySelector("div[data-md-component=announce]").innerHTML = "<div id='announce-msg'>You are viewing the docs for an unreleased version of Argo CD, <a href='https://argo-cd.readthedocs.io/en/stable/'>click here to go to the latest stable version.</a></div>";
var bannerHeight = document.getElementById('announce-msg').offsetHeight + margin;
@@ -72,4 +150,4 @@ window.addEventListener("DOMContentLoaded", function() {
"@media screen and (min-width: 60em){ .md-sidebar--secondary { height: 0; top:" + (bannerHeight + headerHeight) + "px !important; }}";
}
}
});
});

View File

@@ -42,8 +42,11 @@ In order for an application to be managed and reconciled outside the Argo CD's c
In order to enable this feature, the Argo CD administrator must reconfigure the `argocd-server` and `argocd-application-controller` workloads to add the `--application-namespaces` parameter to the container's startup command.
The `--application-namespaces` parameter takes a comma-separated list of namespaces where `Applications` are to be allowed in. Each entry of the list supports shell-style wildcards such as `*`, so for example the entry `app-team-*` would match `app-team-one` and `app-team-two`. To enable all namespaces on the cluster where Argo CD is running on, you can just specify `*`, i.e. `--application-namespaces=*`.
The `--application-namespaces` parameter takes a comma-separated list of namespaces where `Applications` are to be allowed in. Each entry of the list supports:
- shell-style wildcards such as `*`, so for example the entry `app-team-*` would match `app-team-one` and `app-team-two`. To enable all namespaces on the cluster where Argo CD is running on, you can just specify `*`, i.e. `--application-namespaces=*`.
- regex, requires wrapping the string in ```/```, example to allow all namespaces except a particular one: ```/^((?!not-allowed).)*$/```.
The startup parameters for both, the `argocd-server` and the `argocd-application-controller` can also be conveniently set up and kept in sync by specifying the `application.namespaces` settings in the `argocd-cmd-params-cm` ConfigMap _instead_ of changing the manifests for the respective workloads. For example:
```yaml

View File

@@ -7,6 +7,8 @@ The Git generator contains two subtypes: the Git directory generator, and Git fi
If the `project` field in your ApplicationSet is templated, developers may be able to create Applications under Projects with excessive permissions.
For ApplicationSets with a templated `project` field, [the source of truth _must_ be controlled by admins](./Security.md#templated-project-field)
- in the case of git generators, PRs must require admin approval.
- Git generator does not support Signature Verification For ApplicationSets with a templated `project` field.
## Git Generator: Directories
@@ -326,7 +328,7 @@ 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.path}}`: The path to the directory containing matching configuration file within the Git repository. Example: `/clusters/clusterA`, if the config file was `/clusters/clusterA/config.json`
- `{{index .path n}}`: The path to the matching configuration file within the Git repository, split into array elements (`n` - array index). Example: `index .path 0: clusters`, `index .path 1: clusterA`
- `{{index .path.segments n}}`: The path to the matching configuration file within the Git repository, split into array elements (`n` - array index). Example: `index .path.segments 0: clusters`, `index .path.segments 1: clusterA`
- `{{.path.basename}}`: Basename of the path to the directory containing the configuration file (e.g. `clusterA`, with the above example.)
- `{{.path.basenameNormalized}}`: 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).
- `{{.path.filename}}`: The matched filename. e.g., `config.json` in the above example.
@@ -360,7 +362,7 @@ spec:
files:
- path: "applicationset/examples/git-generator-files-discovery/cluster-config/**/config.json"
values:
base_dir: "{{index .path 0}}/{{index .path 1}}/{{index .path 2}}"
base_dir: "{{index .path.segments 0}}/{{index .path.segments 1}}/{{index .path.segments 2}}"
template:
metadata:
name: '{{.cluster.name}}-guestbook'

View File

@@ -100,6 +100,17 @@ possible with Go text templates:
- name: throw-away
value: "{{end}}"
- Signature verification is not supported for the templated `project` field when using the Git generator.
::yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
spec:
goTemplate: true
template:
spec:
project: {{.project}}
## Migration guide

View File

@@ -40,6 +40,7 @@ Note:
- Referenced clusters must already be defined in Argo CD, for the ApplicationSet controller to use them
- Only **one** of `name` or `server` may be specified: if both are specified, an error is returned.
- Signature Verification does not work with the templated `project` field when using git generator.
The `metadata` field of template may also be used to set an Application `name`, or to add labels or annotations to the Application.

View File

@@ -279,6 +279,9 @@ data:
# - annotation+label : Also uses an annotation for tracking, but additionally labels the resource with the application name
application.resourceTrackingMethod: annotation
# Optional installation id. Allows to have multiple installations of Argo CD in the same cluster.
installationID: "my-unique-id"
# disables admin user. Admin is enabled by default
admin.enabled: "false"
# add an additional local user with apiKey and login capabilities
@@ -420,3 +423,5 @@ data:
cluster:
name: some-cluster
server: https://some-cluster
# The maximum size of the payload that can be sent to the webhook server.
webhook.maxPayloadSizeMB: 1024

View File

@@ -47,8 +47,11 @@ data:
controller.log.level: "info"
# Prometheus metrics cache expiration (disabled by default. e.g. 24h0m0s)
controller.metrics.cache.expiration: "24h0m0s"
# Specifies timeout between application self heal attempts (default 5)
controller.self.heal.timeout.seconds: "5"
# Specifies exponential backoff timeout parameters between application self heal attempts
controller.self.heal.timeout.seconds: "2"
controller.self.heal.backoff.factor: "3"
controller.self.heal.backoff.cap.seconds: "300"
# Cache expiration for app state (default 1h0m0s)
controller.app.state.cache.expiration: "1h0m0s"
# Specifies if resource health should be persisted in app CRD (default true)

View File

@@ -122,9 +122,19 @@ To do so, when the action if performed on an application's resource, the `<actio
For instance, to grant access to `example-user` to only delete Pods in the `prod-app` Application, the policy could be:
```csv
p, example-user, applications, delete/*/Pod/*, default/prod-app, allow
p, example-user, applications, delete/*/Pod/*/*, default/prod-app, allow
```
!!!warning "Understand glob pattern behavior"
Argo CD RBAC does not use `/` as a separator when evaluating glob patterns. So the pattern `delete/*/kind/*`
will match `delete/<group>/kind/<namespace>/<name>` but also `delete/<group>/<kind>/kind/<name>`.
The fact that both of these match will generally not be a problem, because resource kinds generally contain capital
letters, and namespaces cannot contain capital letters. However, it is possible for a resource kind to be lowercase.
So it is better to just always include all the parts of the resource in the pattern (in other words, always use four
slashes).
If we want to grant access to the user to update all resources of an application, but not the application itself:
```csv
@@ -135,7 +145,7 @@ If we want to explicitly deny delete of the application, but allow the user to d
```csv
p, example-user, applications, delete, default/prod-app, deny
p, example-user, applications, delete/*/Pod/*, default/prod-app, allow
p, example-user, applications, delete/*/Pod/*/*, default/prod-app, allow
```
!!! note
@@ -145,7 +155,7 @@ p, example-user, applications, delete/*/Pod/*, default/prod-app, allow
```csv
p, example-user, applications, delete, default/prod-app, allow
p, example-user, applications, delete/*/Pod/*, default/prod-app, deny
p, example-user, applications, delete/*/Pod/*/*, default/prod-app, deny
```
#### The `action` action

View File

@@ -65,7 +65,10 @@ argocd-application-controller [flags]
--repo-server-strict-tls Whether to use strict validation of the TLS cert presented by the repo server
--repo-server-timeout-seconds int Repo server RPC call timeout seconds. (default 60)
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
--self-heal-timeout-seconds int Specifies timeout between application self heal attempts (default 5)
--self-heal-backoff-cap-seconds int Specifies max timeout of exponential backoff between application self heal attempts (default 300)
--self-heal-backoff-factor int Specifies factor of exponential timeout between application self heal attempts (default 3)
--self-heal-backoff-timeout-seconds int Specifies initial timeout of exponential backoff between self heal attempts (default 2)
--self-heal-timeout-seconds int Specifies timeout between application self heal attempts
--sentinel stringArray Redis sentinel hostname and port (e.g. argocd-redis-ha-announce-0:6379).
--sentinelmaster string Redis sentinel master group name. (default "master")
--server string The address and port of the Kubernetes API server

View File

@@ -1,6 +1,5 @@
| Argo CD version | Kubernetes versions |
|-----------------|---------------------|
| 2.7 | v1.26, v1.25, v1.24, v1.23 |
| 2.6 | v1.24, v1.23, v1.22 |
| 2.5 | v1.24, v1.23, v1.22 |
| 2.12 | v1.29, v1.28, v1.27, v1.26 |
| 2.11 | v1.29, v1.28, v1.27, v1.26, v1.25 |
| 2.10 | v1.28, v1.27, v1.26, v1.25 |

View File

@@ -1,5 +1,18 @@
# v2.11 to 2.12
## Cluster secret scoping changes
From Argo CD 2.12, there have been some changes to the use of cluster secrets where a `project` is a non-empty value.
Previously, an `Application` or `ApplicationSet` would use any cluster secret matching the URL of the `repoUrl` field.
From 2.12, we now check to see whether the project field of an application _also_ matches the project field of the cluster
secret. What this means is that if you have a cluster secret scoped to `project-a`, an application scoped to `project-b`
can no longer make use of the secret. If you have a cluster secret that's intended to be used by applications in multiple
projects, you need to **unset** the `project` field.
This also applies when using the Git generator in applicationsets; since an applicationset is not scoped to a particular
project any cluster secrets it makes use of also needs to be globally scoped (i.e. any secret needs to have an unset
`project`).
## Upgraded Helm Version
Note that bundled Helm version has been upgraded from 3.14.4 to 3.15.2.

View File

@@ -19,6 +19,8 @@ URL configured in the Git provider should use the `/api/webhook` endpoint of you
(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.
To prevent DDoS attacks with unauthenticated webhook events (the `/api/webhook` endpoint currently lacks rate limiting protection), it is recommended to limit the payload size. You can achieve this by configuring the `argocd-cm` ConfigMap with the `webhook.maxPayloadSizeMB` attribute. The default value is 1GB.
## Github
![Add Webhook](../assets/webhook-config.png "Add Webhook")

View File

@@ -29,6 +29,11 @@ not possible using Helm repositories.
trust models, and it is not necessary (nor possible) to sign the public keys
you are going to import into ArgoCD.
!!!note Limitations
Signature verification is not supported for the templated `project` field when
using the Git generator.
## Signature verification targets
If signature verification is enforced, ArgoCD will verify the signature using

View File

@@ -319,6 +319,11 @@ stringData:
password: ****
```
!!! warning
Please keep in mind when using a project-scoped repository, only applications from the same project can make use of
it. When using applicationsets with the Git generator, only non-scoped repositories can be used (i.e. repositories that
do _not_ have a `project` set).
All the examples above talk about Git repositories, but the same principles apply to clusters as well.
```yaml

View File

@@ -65,6 +65,14 @@ metadata:
The advantages of using the tracking id annotation is that there are no clashes any
more with other Kubernetes tools and Argo CD is never confused about the owner of a resource. The `annotation+label` can also be used if you want other tools to understand resources managed by Argo CD.
### Installation ID
If you are managing one cluster using multiple Argo CD instances, you will need to set `installationID` in the Argo CD ConfigMap. This will prevent conflicts between
the different Argo CD instances:
* Each managed resource will have the annotation `argocd.argoproj.io/tracking-id: <installation-id>`
* It is possible to have applications with the same name in Argo CD instances without causing conflicts.
### Non self-referencing annotations
When using the tracking method `annotation` or `annotation+label`, Argo CD will consider the resource properties in the annotation (name, namespace, group and kind) to determine whether the resource should be compared against the desired state. If the tracking annotation does not reference the resource it is applied to, the resource will neither affect the application's sync status nor be marked for pruning.

9
go.mod
View File

@@ -11,7 +11,7 @@ require (
github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d
github.com/alicebob/miniredis/v2 v2.30.4
github.com/antonmedv/expr v1.15.2
github.com/argoproj/gitops-engine v0.7.1-0.20240615185936-83ce6ca8cedc
github.com/argoproj/gitops-engine v0.7.1-0.20240714153147-adb68bcaab73
github.com/argoproj/notifications-engine v0.4.1-0.20240606074338-0802cd427621
github.com/argoproj/pkg v0.13.7-0.20230626144333-d56162821bd1
github.com/aws/aws-sdk-go v1.50.8
@@ -52,14 +52,14 @@ require (
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/hashicorp/go-retryablehttp v0.7.4
github.com/hashicorp/go-retryablehttp v0.7.7
github.com/imdario/mergo v0.3.16
github.com/improbable-eng/grpc-web v0.15.0
github.com/itchyny/gojq v0.12.13
github.com/jeremywohl/flatten v1.0.1
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/ktrysmt/go-bitbucket v0.9.67
github.com/mattn/go-isatty v0.0.19
github.com/mattn/go-isatty v0.0.20
github.com/mattn/go-zglob v0.0.4
github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1
@@ -187,6 +187,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.11.2
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/evanphx/json-patch/v5 v5.8.0 // indirect
@@ -249,7 +250,7 @@ require (
github.com/opsgenie/opsgenie-go-sdk-v2 v1.0.5 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/errors v0.9.1
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.5.0
github.com/prometheus/common v0.45.0 // indirect

22
go.sum
View File

@@ -695,8 +695,8 @@ github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU=
github.com/appscode/go v0.0.0-20191119085241-0887d8ec2ecc/go.mod h1:OawnOmAL4ZX3YaPdN+8HTNwBveT1jMsqP74moa9XUbE=
github.com/argoproj/gitops-engine v0.7.1-0.20240615185936-83ce6ca8cedc h1:J7LJp2Gh9A9/eQN7Lg74JW+YOVO5NEjq5/cudGAiOwk=
github.com/argoproj/gitops-engine v0.7.1-0.20240615185936-83ce6ca8cedc/go.mod h1:ByLmH5B1Gs361tgI5x5f8oSFuBEXDYENYpG3zFDWtHU=
github.com/argoproj/gitops-engine v0.7.1-0.20240714153147-adb68bcaab73 h1:7kyTgFsPjvb6noafslp2pr7fBCS9s8OJ759LdLzrOro=
github.com/argoproj/gitops-engine v0.7.1-0.20240714153147-adb68bcaab73/go.mod h1:xMIbuLg9Qj2e0egTy+8NcukbhRaVmWwK9vm3aAQZoi4=
github.com/argoproj/notifications-engine v0.4.1-0.20240606074338-0802cd427621 h1:Yg1nt+D2uDK1SL2jSlfukA4yc7db184TTN7iWy3voRE=
github.com/argoproj/notifications-engine v0.4.1-0.20240606074338-0802cd427621/go.mod h1:N0A4sEws2soZjEpY4hgZpQS8mRIEw6otzwfkgc3g9uQ=
github.com/argoproj/pkg v0.13.7-0.20230626144333-d56162821bd1 h1:qsHwwOJ21K2Ao0xPju1sNuqphyMnMYkyB3ZLoLtxWpo=
@@ -843,6 +843,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.11.2 h1:/u628IuisSTwri5/UKloiIsH8+qF2Pu7xEQX+yIKg68=
github.com/dlclark/regexp2 v1.11.2/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
@@ -892,6 +894,8 @@ github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+ne
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
@@ -1242,14 +1246,14 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-retryablehttp v0.5.1/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA=
github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
@@ -1383,13 +1387,15 @@ github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsI
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=

6
hack/update-supported-versions.sh Normal file → Executable file
View File

@@ -11,7 +11,11 @@ for n in 0 1 2; do
minor_version_num=$((argocd_minor_version_num - n))
minor_version="${argocd_major_version_num}.${minor_version_num}"
git checkout "release-$minor_version" > /dev/null || exit 1
line=$(yq '.jobs["test-e2e"].strategy.matrix["k3s-version"][]' .github/workflows/ci-build.yaml | \
line=$(yq '.jobs["test-e2e"].strategy.matrix |
# k3s-version was an array prior to 2.12. This checks for the old format first and then falls back to the new format.
(.["k3s-version"] // (.k3s | map(.version))) |
.[]' .github/workflows/ci-build.yaml | \
jq --arg minor_version "$minor_version" --raw-input --slurp --raw-output \
'split("\n")[:-1] | map(sub("\\.[0-9]+$"; "")) | join(", ") | "| \($minor_version) | \(.) |"')
out+="$line\n"

View File

@@ -97,6 +97,24 @@ spec:
name: argocd-cmd-params-cm
key: controller.self.heal.timeout.seconds
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_TIMEOUT_SECONDS
valueFrom:
configMapKeyRef:
name: argocd-cmd-params-cm
key: controller.self.heal.backoff.timeout.seconds
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_FACTOR
valueFrom:
configMapKeyRef:
name: argocd-cmd-params-cm
key: controller.self.heal.backoff.factor
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_CAP_SECONDS
valueFrom:
configMapKeyRef:
name: argocd-cmd-params-cm
key: controller.self.heal.backoff.cap.seconds
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT
valueFrom:
configMapKeyRef:

View File

@@ -100,6 +100,24 @@ spec:
name: argocd-cmd-params-cm
key: controller.self.heal.timeout.seconds
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_TIMEOUT_SECONDS
valueFrom:
configMapKeyRef:
name: argocd-cmd-params-cm
key: controller.self.heal.backoff.timeout.seconds
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_FACTOR
valueFrom:
configMapKeyRef:
name: argocd-cmd-params-cm
key: controller.self.heal.backoff.factor
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_CAP_SECONDS
valueFrom:
configMapKeyRef:
name: argocd-cmd-params-cm
key: controller.self.heal.backoff.cap.seconds
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT
valueFrom:
configMapKeyRef:

View File

@@ -27,6 +27,8 @@ rules:
- appprojects
verbs:
- get
- list
- watch
- apiGroups:
- argoproj.io
resources:
@@ -62,4 +64,4 @@ rules:
verbs:
- get
- list
- watch
- watch

View File

@@ -5,7 +5,7 @@ kind: Kustomization
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: latest
newTag: v2.12.7
resources:
- ./application-controller
- ./dex

View File

@@ -35,6 +35,8 @@ rules:
- appprojects
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:

View File

@@ -115,6 +115,11 @@ spec:
sync:
description: Sync contains parameters for the operation
properties:
autoHealAttemptsCount:
description: SelfHealAttemptsCount contains the number of auto-heal
attempts
format: int64
type: integer
dryRun:
description: DryRun specifies to perform a `kubectl apply --dry-run`
without actually performing the sync
@@ -2537,6 +2542,11 @@ spec:
sync:
description: Sync contains parameters for the operation
properties:
autoHealAttemptsCount:
description: SelfHealAttemptsCount contains the number
of auto-heal attempts
format: int64
type: integer
dryRun:
description: DryRun specifies to perform a `kubectl apply
--dry-run` without actually performing the sync
@@ -20822,6 +20832,8 @@ rules:
- appprojects
verbs:
- get
- list
- watch
- apiGroups:
- argoproj.io
resources:
@@ -21268,7 +21280,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -21386,7 +21398,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -21639,7 +21651,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -21691,7 +21703,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -21855,6 +21867,24 @@ spec:
key: controller.self.heal.timeout.seconds
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_TIMEOUT_SECONDS
valueFrom:
configMapKeyRef:
key: controller.self.heal.backoff.timeout.seconds
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_FACTOR
valueFrom:
configMapKeyRef:
key: controller.self.heal.backoff.factor
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_CAP_SECONDS
valueFrom:
configMapKeyRef:
key: controller.self.heal.backoff.cap.seconds
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT
valueFrom:
configMapKeyRef:
@@ -21963,7 +21993,7 @@ spec:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -12,4 +12,4 @@ resources:
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: latest
newTag: v2.12.7

View File

@@ -114,6 +114,11 @@ spec:
sync:
description: Sync contains parameters for the operation
properties:
autoHealAttemptsCount:
description: SelfHealAttemptsCount contains the number of auto-heal
attempts
format: int64
type: integer
dryRun:
description: DryRun specifies to perform a `kubectl apply --dry-run`
without actually performing the sync
@@ -2536,6 +2541,11 @@ spec:
sync:
description: Sync contains parameters for the operation
properties:
autoHealAttemptsCount:
description: SelfHealAttemptsCount contains the number
of auto-heal attempts
format: int64
type: integer
dryRun:
description: DryRun specifies to perform a `kubectl apply
--dry-run` without actually performing the sync

View File

@@ -12,7 +12,7 @@ patches:
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: latest
newTag: v2.12.7
resources:
- ../../base/application-controller
- ../../base/applicationset-controller

View File

@@ -115,6 +115,11 @@ spec:
sync:
description: Sync contains parameters for the operation
properties:
autoHealAttemptsCount:
description: SelfHealAttemptsCount contains the number of auto-heal
attempts
format: int64
type: integer
dryRun:
description: DryRun specifies to perform a `kubectl apply --dry-run`
without actually performing the sync
@@ -2537,6 +2542,11 @@ spec:
sync:
description: Sync contains parameters for the operation
properties:
autoHealAttemptsCount:
description: SelfHealAttemptsCount contains the number
of auto-heal attempts
format: int64
type: integer
dryRun:
description: DryRun specifies to perform a `kubectl apply
--dry-run` without actually performing the sync
@@ -20860,6 +20870,8 @@ rules:
- appprojects
verbs:
- get
- list
- watch
- apiGroups:
- argoproj.io
resources:
@@ -21108,6 +21120,8 @@ rules:
- appprojects
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
@@ -22609,7 +22623,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -22732,7 +22746,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -22814,7 +22828,7 @@ spec:
key: notificationscontroller.selfservice.enabled
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -22933,7 +22947,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -23214,7 +23228,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -23266,7 +23280,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -23590,7 +23604,7 @@ spec:
key: server.api.content.types
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -23781,6 +23795,24 @@ spec:
key: controller.self.heal.timeout.seconds
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_TIMEOUT_SECONDS
valueFrom:
configMapKeyRef:
key: controller.self.heal.backoff.timeout.seconds
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_FACTOR
valueFrom:
configMapKeyRef:
key: controller.self.heal.backoff.factor
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_CAP_SECONDS
valueFrom:
configMapKeyRef:
key: controller.self.heal.backoff.cap.seconds
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT
valueFrom:
configMapKeyRef:
@@ -23889,7 +23921,7 @@ spec:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -149,6 +149,8 @@ rules:
- appprojects
verbs:
- get
- list
- watch
- apiGroups:
- argoproj.io
resources:
@@ -1686,7 +1688,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -1809,7 +1811,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -1891,7 +1893,7 @@ spec:
key: notificationscontroller.selfservice.enabled
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -2010,7 +2012,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -2291,7 +2293,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -2343,7 +2345,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -2667,7 +2669,7 @@ spec:
key: server.api.content.types
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2858,6 +2860,24 @@ spec:
key: controller.self.heal.timeout.seconds
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_TIMEOUT_SECONDS
valueFrom:
configMapKeyRef:
key: controller.self.heal.backoff.timeout.seconds
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_FACTOR
valueFrom:
configMapKeyRef:
key: controller.self.heal.backoff.factor
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_CAP_SECONDS
valueFrom:
configMapKeyRef:
key: controller.self.heal.backoff.cap.seconds
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT
valueFrom:
configMapKeyRef:
@@ -2966,7 +2986,7 @@ spec:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -115,6 +115,11 @@ spec:
sync:
description: Sync contains parameters for the operation
properties:
autoHealAttemptsCount:
description: SelfHealAttemptsCount contains the number of auto-heal
attempts
format: int64
type: integer
dryRun:
description: DryRun specifies to perform a `kubectl apply --dry-run`
without actually performing the sync
@@ -2537,6 +2542,11 @@ spec:
sync:
description: Sync contains parameters for the operation
properties:
autoHealAttemptsCount:
description: SelfHealAttemptsCount contains the number
of auto-heal attempts
format: int64
type: integer
dryRun:
description: DryRun specifies to perform a `kubectl apply
--dry-run` without actually performing the sync
@@ -20849,6 +20859,8 @@ rules:
- appprojects
verbs:
- get
- list
- watch
- apiGroups:
- argoproj.io
resources:
@@ -21075,6 +21087,8 @@ rules:
- appprojects
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
@@ -21726,7 +21740,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -21849,7 +21863,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -21931,7 +21945,7 @@ spec:
key: notificationscontroller.selfservice.enabled
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -22031,7 +22045,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -22284,7 +22298,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -22336,7 +22350,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -22658,7 +22672,7 @@ spec:
key: server.api.content.types
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -22849,6 +22863,24 @@ spec:
key: controller.self.heal.timeout.seconds
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_TIMEOUT_SECONDS
valueFrom:
configMapKeyRef:
key: controller.self.heal.backoff.timeout.seconds
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_FACTOR
valueFrom:
configMapKeyRef:
key: controller.self.heal.backoff.factor
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_CAP_SECONDS
valueFrom:
configMapKeyRef:
key: controller.self.heal.backoff.cap.seconds
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT
valueFrom:
configMapKeyRef:
@@ -22957,7 +22989,7 @@ spec:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -138,6 +138,8 @@ rules:
- appprojects
verbs:
- get
- list
- watch
- apiGroups:
- argoproj.io
resources:
@@ -803,7 +805,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -926,7 +928,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -1008,7 +1010,7 @@ spec:
key: notificationscontroller.selfservice.enabled
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -1108,7 +1110,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -1361,7 +1363,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -1413,7 +1415,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -1735,7 +1737,7 @@ spec:
key: server.api.content.types
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -1926,6 +1928,24 @@ spec:
key: controller.self.heal.timeout.seconds
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_TIMEOUT_SECONDS
valueFrom:
configMapKeyRef:
key: controller.self.heal.backoff.timeout.seconds
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_FACTOR
valueFrom:
configMapKeyRef:
key: controller.self.heal.backoff.factor
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_CAP_SECONDS
valueFrom:
configMapKeyRef:
key: controller.self.heal.backoff.cap.seconds
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT
valueFrom:
configMapKeyRef:
@@ -2034,7 +2054,7 @@ spec:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:latest
image: quay.io/argoproj/argocd:v2.12.7
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -122,7 +122,7 @@ func NewController(
// Check if app is not in the namespace where the controller is in, and also app is not in one of the applicationNamespaces
func checkAppNotInAdditionalNamespaces(app *unstructured.Unstructured, namespace string, applicationNamespaces []string) bool {
return namespace != app.GetNamespace() && !glob.MatchStringInList(applicationNamespaces, app.GetNamespace(), false)
return namespace != app.GetNamespace() && !glob.MatchStringInList(applicationNamespaces, app.GetNamespace(), glob.REGEXP)
}
func (c *notificationController) alterDestinations(obj v1.Object, destinations services.Destinations, cfg api.Config) services.Destinations {
@@ -151,7 +151,7 @@ func newInformer(resClient dynamic.ResourceInterface, controllerNamespace string
}
newItems := []unstructured.Unstructured{}
for _, res := range appList.Items {
if controllerNamespace == res.GetNamespace() || glob.MatchStringInList(applicationNamespaces, res.GetNamespace(), false) {
if controllerNamespace == res.GetNamespace() || glob.MatchStringInList(applicationNamespaces, res.GetNamespace(), glob.REGEXP) {
newItems = append(newItems, res)
}
}

View File

@@ -101,6 +101,7 @@ type Settings struct {
ExecEnabled bool `protobuf:"varint,22,opt,name=execEnabled,proto3" json:"execEnabled,omitempty"`
ControllerNamespace string `protobuf:"bytes,23,opt,name=controllerNamespace,proto3" json:"controllerNamespace,omitempty"`
AppsInAnyNamespaceEnabled bool `protobuf:"varint,24,opt,name=appsInAnyNamespaceEnabled,proto3" json:"appsInAnyNamespaceEnabled,omitempty"`
InstallationID string `protobuf:"bytes,26,opt,name=installationID,proto3" json:"installationID,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@@ -307,6 +308,13 @@ func (m *Settings) GetAppsInAnyNamespaceEnabled() bool {
return false
}
func (m *Settings) GetInstallationID() string {
if m != nil {
return m.InstallationID
}
return ""
}
type GoogleAnalyticsConfig struct {
TrackingID string `protobuf:"bytes,1,opt,name=trackingID,proto3" json:"trackingID,omitempty"`
AnonymizeUsers bool `protobuf:"varint,2,opt,name=anonymizeUsers,proto3" json:"anonymizeUsers,omitempty"`
@@ -740,83 +748,84 @@ func init() {
func init() { proto.RegisterFile("server/settings/settings.proto", fileDescriptor_a480d494da040caa) }
var fileDescriptor_a480d494da040caa = []byte{
// 1215 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0x4f, 0x6f, 0x1b, 0x45,
0x14, 0xd7, 0xd6, 0x69, 0x62, 0x3f, 0x37, 0x75, 0x32, 0x6d, 0xd3, 0xad, 0x55, 0x12, 0xe3, 0x43,
0x65, 0x10, 0xac, 0x9b, 0x54, 0x08, 0x54, 0x51, 0x41, 0x6d, 0x57, 0xad, 0x69, 0xda, 0x86, 0x69,
0xd3, 0x03, 0x97, 0x6a, 0xb2, 0x7e, 0xac, 0x97, 0xac, 0x67, 0x56, 0x33, 0xb3, 0xa6, 0xee, 0x91,
0x0f, 0xc0, 0x05, 0x3e, 0x0b, 0x07, 0xee, 0x08, 0x8e, 0x48, 0xdc, 0x23, 0x64, 0xf1, 0x41, 0xd0,
0xce, 0xfe, 0xc9, 0x66, 0xed, 0x14, 0xa4, 0xde, 0x66, 0x7e, 0xbf, 0xf7, 0x6f, 0xde, 0xbc, 0x37,
0xf3, 0x60, 0x5b, 0xa1, 0x9c, 0xa2, 0xec, 0x2a, 0xd4, 0xda, 0xe7, 0x9e, 0xca, 0x17, 0x4e, 0x28,
0x85, 0x16, 0x64, 0xcd, 0x0d, 0x22, 0xa5, 0x51, 0x36, 0xaf, 0x7a, 0xc2, 0x13, 0x06, 0xeb, 0xc6,
0xab, 0x84, 0x6e, 0xde, 0xf4, 0x84, 0xf0, 0x02, 0xec, 0xb2, 0xd0, 0xef, 0x32, 0xce, 0x85, 0x66,
0xda, 0x17, 0x3c, 0x55, 0x6e, 0xee, 0x7b, 0xbe, 0x1e, 0x47, 0x47, 0x8e, 0x2b, 0x26, 0x5d, 0x26,
0x8d, 0xfa, 0x77, 0x66, 0xf1, 0xb1, 0x3b, 0xea, 0x4e, 0xf7, 0xba, 0xe1, 0xb1, 0x17, 0x6b, 0xaa,
0x2e, 0x0b, 0xc3, 0xc0, 0x77, 0x8d, 0x6e, 0x77, 0xba, 0xcb, 0x82, 0x70, 0xcc, 0x76, 0xbb, 0x1e,
0x72, 0x94, 0x4c, 0xe3, 0x28, 0xb5, 0xf6, 0xe5, 0x7f, 0x58, 0x2b, 0x9f, 0x44, 0xf8, 0x23, 0xb7,
0xeb, 0x06, 0xcc, 0x9f, 0xa4, 0xf1, 0xb4, 0x1b, 0xb0, 0xfe, 0x3c, 0x65, 0xbf, 0x8e, 0x50, 0xce,
0xda, 0xbf, 0xd4, 0xa1, 0x9a, 0x21, 0xe4, 0x06, 0x54, 0x22, 0x19, 0xd8, 0x56, 0xcb, 0xea, 0xd4,
0x7a, 0x6b, 0xf3, 0x93, 0x9d, 0xca, 0x21, 0xdd, 0xa7, 0x31, 0x46, 0x6e, 0x43, 0x6d, 0x84, 0xaf,
0xfb, 0x82, 0x7f, 0xeb, 0x7b, 0xf6, 0x85, 0x96, 0xd5, 0xa9, 0xef, 0x11, 0x27, 0xcd, 0x8c, 0x33,
0xc8, 0x18, 0x7a, 0x2a, 0x44, 0xfa, 0x00, 0xb1, 0xff, 0x54, 0xa5, 0x62, 0x54, 0xae, 0xe4, 0x2a,
0xcf, 0x86, 0x83, 0x7e, 0x42, 0xf5, 0x2e, 0xcf, 0x4f, 0x76, 0xe0, 0x74, 0x4f, 0x0b, 0x6a, 0xa4,
0x05, 0x75, 0x16, 0x86, 0xfb, 0xec, 0x08, 0x83, 0xc7, 0x38, 0xb3, 0x57, 0xe2, 0xc8, 0x68, 0x11,
0x22, 0x2f, 0x61, 0x53, 0xa2, 0x12, 0x91, 0x74, 0xf1, 0xd9, 0x14, 0xa5, 0xf4, 0x47, 0xa8, 0xec,
0x8b, 0xad, 0x4a, 0xa7, 0xbe, 0xd7, 0xc9, 0xbd, 0x65, 0x27, 0x74, 0x68, 0x59, 0xf4, 0x01, 0xd7,
0x72, 0x46, 0x17, 0x4d, 0x10, 0x07, 0x88, 0xd2, 0x4c, 0x47, 0xaa, 0xc7, 0x46, 0x1e, 0x3e, 0xe0,
0xec, 0x28, 0xc0, 0x91, 0xbd, 0xda, 0xb2, 0x3a, 0x55, 0xba, 0x84, 0x21, 0x8f, 0xa0, 0x91, 0x54,
0xc2, 0x7d, 0xce, 0x82, 0x99, 0xf6, 0x5d, 0x65, 0xaf, 0x99, 0x33, 0x6f, 0xe7, 0x51, 0x3c, 0x3c,
0xcb, 0xa7, 0xc7, 0x2d, 0xab, 0x91, 0x37, 0xb0, 0x71, 0x1c, 0x29, 0x2d, 0x26, 0xfe, 0x1b, 0x7c,
0x16, 0x9a, 0x6a, 0xb2, 0xab, 0xc6, 0xd4, 0x53, 0xe7, 0xb4, 0x00, 0x9c, 0xac, 0x00, 0xcc, 0xe2,
0x95, 0x3b, 0x72, 0xa6, 0x7b, 0x4e, 0x78, 0xec, 0x39, 0x71, 0x39, 0x39, 0x85, 0x72, 0x72, 0xb2,
0x72, 0x72, 0x1e, 0x97, 0xac, 0xd2, 0x05, 0x3f, 0xe4, 0x7d, 0x58, 0x19, 0x63, 0x10, 0xda, 0x35,
0xe3, 0x6f, 0x3d, 0x0f, 0xfd, 0x11, 0x06, 0x21, 0x35, 0x14, 0xf9, 0x00, 0xd6, 0xc2, 0x20, 0xf2,
0x7c, 0xae, 0x6c, 0x30, 0x69, 0x6e, 0xe4, 0x52, 0x07, 0x06, 0xa7, 0x19, 0x1f, 0xe7, 0x30, 0x52,
0x28, 0xf7, 0x45, 0xbc, 0x1b, 0xf8, 0x2a, 0xc9, 0x61, 0x3d, 0xc9, 0xe1, 0x22, 0x43, 0x7e, 0xb4,
0xe0, 0xba, 0x6b, 0xb2, 0xf2, 0x84, 0x71, 0xe6, 0xe1, 0x04, 0xb9, 0x3e, 0x48, 0x7d, 0x5d, 0x32,
0xbe, 0x5e, 0xbc, 0x5b, 0x06, 0xfa, 0x4b, 0x8d, 0xd3, 0xf3, 0x9c, 0x92, 0x8f, 0x60, 0x33, 0x4f,
0xd1, 0x4b, 0x94, 0xca, 0xdc, 0xc5, 0x7a, 0xab, 0xd2, 0xa9, 0xd1, 0x45, 0x82, 0x34, 0xa1, 0x1a,
0xf9, 0x7d, 0xa5, 0x0e, 0xe9, 0xbe, 0x7d, 0xd9, 0x54, 0x6a, 0xbe, 0x27, 0x1d, 0x68, 0x44, 0x7e,
0x8f, 0x71, 0x8e, 0xb2, 0x2f, 0xb8, 0x46, 0xae, 0xed, 0x86, 0x11, 0x29, 0xc3, 0x71, 0xc9, 0x67,
0x50, 0x6c, 0x68, 0x23, 0x29, 0xf9, 0x02, 0x14, 0xdb, 0x0a, 0x99, 0x52, 0xdf, 0x0b, 0x39, 0x3a,
0x60, 0x5a, 0xa3, 0xe4, 0xf6, 0x66, 0x62, 0xab, 0x04, 0x93, 0x5b, 0x70, 0x59, 0x4b, 0xe6, 0x1e,
0xfb, 0xdc, 0x7b, 0x82, 0x7a, 0x2c, 0x46, 0x36, 0x31, 0x82, 0x25, 0x34, 0x3e, 0x67, 0xe6, 0xe0,
0x00, 0xe5, 0x84, 0xf1, 0x38, 0xbe, 0x2b, 0xe6, 0x9e, 0x16, 0x09, 0xf2, 0x21, 0x6c, 0xe4, 0xa0,
0x50, 0x7e, 0x9c, 0x62, 0xfb, 0xaa, 0xb1, 0xbb, 0x80, 0x97, 0xda, 0x88, 0x0a, 0xa1, 0x0f, 0x65,
0x60, 0x5f, 0x33, 0xd2, 0x4b, 0x98, 0xf8, 0xf4, 0xf8, 0x1a, 0xdd, 0xac, 0xdf, 0xb6, 0x4c, 0x0c,
0x45, 0x88, 0xdc, 0x86, 0x2b, 0xae, 0xe0, 0x5a, 0x8a, 0x20, 0x40, 0xf9, 0x94, 0x4d, 0x50, 0x85,
0xcc, 0x45, 0xfb, 0xba, 0x31, 0xb9, 0x8c, 0x22, 0x9f, 0xc3, 0x0d, 0x16, 0x86, 0x6a, 0xc8, 0xef,
0xf3, 0x59, 0x8e, 0x66, 0x1e, 0x6c, 0xe3, 0xe1, 0x7c, 0x81, 0xe6, 0xcf, 0x16, 0x6c, 0x2d, 0x7f,
0x36, 0xc8, 0x06, 0x54, 0x8e, 0x71, 0x96, 0xbc, 0x97, 0x34, 0x5e, 0x92, 0x11, 0x5c, 0x9c, 0xb2,
0x20, 0xc2, 0xf4, 0x89, 0x7c, 0xc7, 0x86, 0x2d, 0xbb, 0xa5, 0x89, 0xf1, 0xbb, 0x17, 0x3e, 0xb3,
0xda, 0xaf, 0xe0, 0xda, 0xd2, 0xf7, 0x84, 0x6c, 0x03, 0x64, 0xb7, 0x3b, 0x1c, 0xa4, 0xb1, 0x15,
0x90, 0xb8, 0x26, 0x18, 0x17, 0x7c, 0x16, 0x97, 0xee, 0xa1, 0x42, 0xa9, 0x4c, 0xac, 0x55, 0x5a,
0x42, 0xdb, 0x03, 0xb8, 0x9e, 0x3d, 0x9b, 0x69, 0x3b, 0x50, 0x54, 0xa1, 0xe0, 0x0a, 0x8b, 0x4f,
0x80, 0xf5, 0xf6, 0x27, 0xa0, 0xfd, 0xab, 0x05, 0x2b, 0xf1, 0xe3, 0x41, 0x6c, 0x58, 0x73, 0xc7,
0xcc, 0xdc, 0x7e, 0x12, 0x53, 0xb6, 0x8d, 0xdb, 0x26, 0x5e, 0xbe, 0xc0, 0xd7, 0xda, 0x84, 0x52,
0xa3, 0xf9, 0x9e, 0xdc, 0x03, 0x38, 0xf2, 0x39, 0x93, 0xb3, 0x43, 0x19, 0x28, 0xbb, 0x62, 0x9c,
0xbd, 0x77, 0xe6, 0x55, 0x72, 0x7a, 0x39, 0x9f, 0xbc, 0xe5, 0x05, 0x85, 0xe6, 0x3d, 0x68, 0x94,
0xe8, 0x25, 0x77, 0x76, 0xb5, 0x78, 0x67, 0xb5, 0x62, 0x8e, 0x6f, 0xc2, 0x6a, 0x72, 0x1e, 0x42,
0x60, 0x85, 0xb3, 0x09, 0xa6, 0x6a, 0x66, 0xdd, 0xfe, 0x02, 0x6a, 0xf9, 0xc7, 0x47, 0xf6, 0x00,
0x5c, 0xc1, 0x39, 0xba, 0x5a, 0xc8, 0x2c, 0x2b, 0xa7, 0x1f, 0x64, 0x3f, 0xa3, 0x68, 0x41, 0xaa,
0x7d, 0x07, 0x6a, 0x39, 0xb1, 0xcc, 0x43, 0x8c, 0xe9, 0x59, 0x98, 0x05, 0x66, 0xd6, 0xed, 0xdf,
0x2a, 0x50, 0xf8, 0x2c, 0x97, 0xaa, 0x6d, 0xc1, 0xaa, 0xaf, 0x54, 0x84, 0x32, 0x55, 0x4c, 0x77,
0xa4, 0x03, 0x55, 0x37, 0xf0, 0x91, 0xeb, 0xe1, 0xc0, 0xfc, 0xc7, 0xb5, 0xde, 0xa5, 0xf9, 0xc9,
0x4e, 0xb5, 0x9f, 0x62, 0x34, 0x67, 0xc9, 0x2e, 0xd4, 0xdd, 0xc0, 0xcf, 0x88, 0xe4, 0xdb, 0xed,
0x35, 0xe6, 0x27, 0x3b, 0xf5, 0xfe, 0xfe, 0x30, 0x97, 0x2f, 0xca, 0xc4, 0x4e, 0x95, 0x2b, 0xc2,
0xf4, 0xf3, 0xad, 0xd1, 0x74, 0x47, 0x5e, 0xc1, 0xba, 0x3f, 0x7a, 0x21, 0x8e, 0x91, 0xf7, 0xcd,
0x20, 0x62, 0xaf, 0x9a, 0xdc, 0xdc, 0x5a, 0x32, 0x09, 0x38, 0xc3, 0xa2, 0xa0, 0xb9, 0xae, 0xde,
0xe6, 0xfc, 0x64, 0x67, 0x7d, 0x38, 0x28, 0xe0, 0xf4, 0xac, 0x3d, 0x72, 0x17, 0x6c, 0x34, 0xad,
0x7a, 0xf0, 0xb8, 0xff, 0xe0, 0x7e, 0xa4, 0xc7, 0xc8, 0x75, 0xda, 0x49, 0xe6, 0x07, 0xae, 0xd2,
0x73, 0xf9, 0xe6, 0x0c, 0xc8, 0xa2, 0xcf, 0x25, 0x25, 0xf2, 0xe4, 0x6c, 0x5b, 0x7f, 0xfa, 0xd6,
0xb6, 0x4e, 0xa6, 0x30, 0x27, 0x1f, 0x23, 0xe3, 0x71, 0xc6, 0x31, 0xf6, 0x0b, 0xb5, 0xb5, 0xf7,
0xbb, 0x05, 0x8d, 0xac, 0xbf, 0x9e, 0xa3, 0x9c, 0xfa, 0x2e, 0x92, 0xaf, 0xa0, 0xf2, 0x10, 0x35,
0xd9, 0x5a, 0x98, 0x5b, 0xcc, 0xac, 0xd6, 0xdc, 0x5c, 0xc0, 0xdb, 0xf6, 0x0f, 0x7f, 0xfd, 0xf3,
0xd3, 0x05, 0x42, 0x36, 0xcc, 0xfc, 0x39, 0xdd, 0xcd, 0x67, 0x3f, 0x32, 0x06, 0x78, 0x88, 0xf9,
0x47, 0x76, 0x9e, 0xc9, 0xd6, 0x02, 0x5e, 0xea, 0xf5, 0x76, 0xcb, 0x78, 0x68, 0x12, 0xbb, 0xec,
0xa1, 0x9b, 0xb6, 0x78, 0xaf, 0xff, 0xc7, 0x7c, 0xdb, 0xfa, 0x73, 0xbe, 0x6d, 0xfd, 0x3d, 0xdf,
0xb6, 0xbe, 0xf9, 0xe4, 0xff, 0x4d, 0xbc, 0x49, 0xa9, 0xe5, 0xc6, 0x8e, 0x56, 0xcd, 0x7c, 0x7a,
0xe7, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf1, 0x4f, 0xb0, 0x2d, 0x8e, 0x0b, 0x00, 0x00,
// 1231 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0xcf, 0x6f, 0x1b, 0xc5,
0x17, 0xd7, 0xd6, 0x69, 0x62, 0x3f, 0x37, 0x75, 0x32, 0x6d, 0xd3, 0xad, 0xd5, 0x6f, 0xe2, 0xaf,
0x0f, 0x95, 0x41, 0xb0, 0x6e, 0x52, 0x21, 0x50, 0x45, 0x05, 0xb5, 0x5d, 0xb5, 0xa6, 0x69, 0x1b,
0xb6, 0x4d, 0x0f, 0x5c, 0xaa, 0xc9, 0xfa, 0xb1, 0x5e, 0xb2, 0x9e, 0x59, 0xcd, 0xcc, 0x9a, 0xb8,
0x47, 0xfe, 0x00, 0x2e, 0xf0, 0xd7, 0x70, 0x47, 0x70, 0x44, 0xe2, 0x1e, 0x21, 0x8b, 0x3f, 0x04,
0xcd, 0xec, 0x8f, 0x6c, 0xd6, 0x4e, 0x41, 0xea, 0x6d, 0xe6, 0xf3, 0x79, 0xbf, 0xe6, 0xcd, 0x7b,
0x33, 0x0f, 0xb6, 0x25, 0x8a, 0x29, 0x8a, 0xae, 0x44, 0xa5, 0x02, 0xe6, 0xcb, 0x7c, 0xe1, 0x44,
0x82, 0x2b, 0x4e, 0xd6, 0xbc, 0x30, 0x96, 0x0a, 0x45, 0xf3, 0xba, 0xcf, 0x7d, 0x6e, 0xb0, 0xae,
0x5e, 0x25, 0x74, 0xf3, 0xb6, 0xcf, 0xb9, 0x1f, 0x62, 0x97, 0x46, 0x41, 0x97, 0x32, 0xc6, 0x15,
0x55, 0x01, 0x67, 0xa9, 0x72, 0x73, 0xdf, 0x0f, 0xd4, 0x38, 0x3e, 0x72, 0x3c, 0x3e, 0xe9, 0x52,
0x61, 0xd4, 0xbf, 0x33, 0x8b, 0x8f, 0xbd, 0x51, 0x77, 0xba, 0xd7, 0x8d, 0x8e, 0x7d, 0xad, 0x29,
0xbb, 0x34, 0x8a, 0xc2, 0xc0, 0x33, 0xba, 0xdd, 0xe9, 0x2e, 0x0d, 0xa3, 0x31, 0xdd, 0xed, 0xfa,
0xc8, 0x50, 0x50, 0x85, 0xa3, 0xd4, 0xda, 0x97, 0xff, 0x62, 0xad, 0x7c, 0x12, 0x1e, 0x8c, 0xbc,
0xae, 0x17, 0xd2, 0x60, 0x92, 0xc6, 0xd3, 0x6e, 0xc0, 0xfa, 0xcb, 0x94, 0xfd, 0x3a, 0x46, 0x31,
0x6b, 0x9f, 0xd6, 0xa1, 0x9a, 0x21, 0xe4, 0x16, 0x54, 0x62, 0x11, 0xda, 0x56, 0xcb, 0xea, 0xd4,
0x7a, 0x6b, 0xf3, 0xd3, 0x9d, 0xca, 0xa1, 0xbb, 0xef, 0x6a, 0x8c, 0xdc, 0x85, 0xda, 0x08, 0x4f,
0xfa, 0x9c, 0x7d, 0x1b, 0xf8, 0xf6, 0xa5, 0x96, 0xd5, 0xa9, 0xef, 0x11, 0x27, 0xcd, 0x8c, 0x33,
0xc8, 0x18, 0xf7, 0x4c, 0x88, 0xf4, 0x01, 0xb4, 0xff, 0x54, 0xa5, 0x62, 0x54, 0xae, 0xe5, 0x2a,
0x2f, 0x86, 0x83, 0x7e, 0x42, 0xf5, 0xae, 0xce, 0x4f, 0x77, 0xe0, 0x6c, 0xef, 0x16, 0xd4, 0x48,
0x0b, 0xea, 0x34, 0x8a, 0xf6, 0xe9, 0x11, 0x86, 0x4f, 0x71, 0x66, 0xaf, 0xe8, 0xc8, 0xdc, 0x22,
0x44, 0x5e, 0xc3, 0xa6, 0x40, 0xc9, 0x63, 0xe1, 0xe1, 0x8b, 0x29, 0x0a, 0x11, 0x8c, 0x50, 0xda,
0x97, 0x5b, 0x95, 0x4e, 0x7d, 0xaf, 0x93, 0x7b, 0xcb, 0x4e, 0xe8, 0xb8, 0x65, 0xd1, 0x47, 0x4c,
0x89, 0x99, 0xbb, 0x68, 0x82, 0x38, 0x40, 0xa4, 0xa2, 0x2a, 0x96, 0x3d, 0x3a, 0xf2, 0xf1, 0x11,
0xa3, 0x47, 0x21, 0x8e, 0xec, 0xd5, 0x96, 0xd5, 0xa9, 0xba, 0x4b, 0x18, 0xf2, 0x04, 0x1a, 0x49,
0x25, 0x3c, 0x64, 0x34, 0x9c, 0xa9, 0xc0, 0x93, 0xf6, 0x9a, 0x39, 0xf3, 0x76, 0x1e, 0xc5, 0xe3,
0xf3, 0x7c, 0x7a, 0xdc, 0xb2, 0x1a, 0x79, 0x0b, 0x1b, 0xc7, 0xb1, 0x54, 0x7c, 0x12, 0xbc, 0xc5,
0x17, 0x91, 0xa9, 0x26, 0xbb, 0x6a, 0x4c, 0x3d, 0x77, 0xce, 0x0a, 0xc0, 0xc9, 0x0a, 0xc0, 0x2c,
0xde, 0x78, 0x23, 0x67, 0xba, 0xe7, 0x44, 0xc7, 0xbe, 0xa3, 0xcb, 0xc9, 0x29, 0x94, 0x93, 0x93,
0x95, 0x93, 0xf3, 0xb4, 0x64, 0xd5, 0x5d, 0xf0, 0x43, 0xfe, 0x0f, 0x2b, 0x63, 0x0c, 0x23, 0xbb,
0x66, 0xfc, 0xad, 0xe7, 0xa1, 0x3f, 0xc1, 0x30, 0x72, 0x0d, 0x45, 0x3e, 0x80, 0xb5, 0x28, 0x8c,
0xfd, 0x80, 0x49, 0x1b, 0x4c, 0x9a, 0x1b, 0xb9, 0xd4, 0x81, 0xc1, 0xdd, 0x8c, 0xd7, 0x39, 0x8c,
0x25, 0x8a, 0x7d, 0xae, 0x77, 0x83, 0x40, 0x26, 0x39, 0xac, 0x27, 0x39, 0x5c, 0x64, 0xc8, 0x8f,
0x16, 0xdc, 0xf4, 0x4c, 0x56, 0x9e, 0x51, 0x46, 0x7d, 0x9c, 0x20, 0x53, 0x07, 0xa9, 0xaf, 0x2b,
0xc6, 0xd7, 0xab, 0xf7, 0xcb, 0x40, 0x7f, 0xa9, 0x71, 0xf7, 0x22, 0xa7, 0xe4, 0x23, 0xd8, 0xcc,
0x53, 0xf4, 0x1a, 0x85, 0x34, 0x77, 0xb1, 0xde, 0xaa, 0x74, 0x6a, 0xee, 0x22, 0x41, 0x9a, 0x50,
0x8d, 0x83, 0xbe, 0x94, 0x87, 0xee, 0xbe, 0x7d, 0xd5, 0x54, 0x6a, 0xbe, 0x27, 0x1d, 0x68, 0xc4,
0x41, 0x8f, 0x32, 0x86, 0xa2, 0xcf, 0x99, 0x42, 0xa6, 0xec, 0x86, 0x11, 0x29, 0xc3, 0xba, 0xe4,
0x33, 0x48, 0x1b, 0xda, 0x48, 0x4a, 0xbe, 0x00, 0x69, 0x5b, 0x11, 0x95, 0xf2, 0x7b, 0x2e, 0x46,
0x07, 0x54, 0x29, 0x14, 0xcc, 0xde, 0x4c, 0x6c, 0x95, 0x60, 0x72, 0x07, 0xae, 0x2a, 0x41, 0xbd,
0xe3, 0x80, 0xf9, 0xcf, 0x50, 0x8d, 0xf9, 0xc8, 0x26, 0x46, 0xb0, 0x84, 0xea, 0x73, 0x66, 0x0e,
0x0e, 0x50, 0x4c, 0x28, 0xd3, 0xf1, 0x5d, 0x33, 0xf7, 0xb4, 0x48, 0x90, 0x0f, 0x61, 0x23, 0x07,
0xb9, 0x0c, 0x74, 0x8a, 0xed, 0xeb, 0xc6, 0xee, 0x02, 0x5e, 0x6a, 0x23, 0x97, 0x73, 0x75, 0x28,
0x42, 0xfb, 0x86, 0x91, 0x5e, 0xc2, 0xe8, 0xd3, 0xe3, 0x09, 0x7a, 0x59, 0xbf, 0x6d, 0x99, 0x18,
0x8a, 0x10, 0xb9, 0x0b, 0xd7, 0x3c, 0xce, 0x94, 0xe0, 0x61, 0x88, 0xe2, 0x39, 0x9d, 0xa0, 0x8c,
0xa8, 0x87, 0xf6, 0x4d, 0x63, 0x72, 0x19, 0x45, 0x3e, 0x87, 0x5b, 0x34, 0x8a, 0xe4, 0x90, 0x3d,
0x64, 0xb3, 0x1c, 0xcd, 0x3c, 0xd8, 0xc6, 0xc3, 0xc5, 0x02, 0x3a, 0x87, 0x01, 0x93, 0x8a, 0x86,
0xa1, 0x29, 0xa6, 0xe1, 0xc0, 0x6e, 0x26, 0x39, 0x3c, 0x8f, 0x36, 0x7f, 0xb6, 0x60, 0x6b, 0xf9,
0xf3, 0x42, 0x36, 0xa0, 0x72, 0x8c, 0xb3, 0xe4, 0x5d, 0x75, 0xf5, 0x92, 0x8c, 0xe0, 0xf2, 0x94,
0x86, 0x31, 0xa6, 0x4f, 0xe9, 0x7b, 0x36, 0x76, 0xd9, 0xad, 0x9b, 0x18, 0xbf, 0x7f, 0xe9, 0x33,
0xab, 0xfd, 0x06, 0x6e, 0x2c, 0x7d, 0x77, 0xc8, 0x36, 0x40, 0x56, 0x05, 0xc3, 0x41, 0x1a, 0x5b,
0x01, 0xd1, 0xe7, 0xa6, 0x8c, 0xb3, 0x99, 0x2e, 0xf1, 0x43, 0x89, 0x42, 0x9a, 0x58, 0xab, 0x6e,
0x09, 0x6d, 0x0f, 0xe0, 0x66, 0xf6, 0xbc, 0xa6, 0x6d, 0xe3, 0xa2, 0x8c, 0x38, 0x93, 0x58, 0x7c,
0x2a, 0xac, 0x77, 0x3f, 0x15, 0xed, 0x5f, 0x2c, 0x58, 0xd1, 0x8f, 0x0c, 0xb1, 0x61, 0xcd, 0x1b,
0x53, 0x53, 0x25, 0x49, 0x4c, 0xd9, 0x56, 0xb7, 0x97, 0x5e, 0xbe, 0xc2, 0x13, 0x65, 0x42, 0xa9,
0xb9, 0xf9, 0x9e, 0x3c, 0x00, 0x38, 0x0a, 0x18, 0x15, 0xb3, 0x43, 0x11, 0x4a, 0xbb, 0x62, 0x9c,
0xfd, 0xef, 0xdc, 0xeb, 0xe5, 0xf4, 0x72, 0x3e, 0x79, 0xf3, 0x0b, 0x0a, 0xcd, 0x07, 0xd0, 0x28,
0xd1, 0x4b, 0xee, 0xec, 0x7a, 0xf1, 0xce, 0x6a, 0xc5, 0x1c, 0xdf, 0x86, 0xd5, 0xe4, 0x3c, 0x84,
0xc0, 0x0a, 0xa3, 0x13, 0x4c, 0xd5, 0xcc, 0xba, 0xfd, 0x05, 0xd4, 0xf2, 0x0f, 0x92, 0xec, 0x01,
0x78, 0x9c, 0x31, 0xf4, 0x14, 0x17, 0x59, 0x56, 0xce, 0x3e, 0xd2, 0x7e, 0x46, 0xb9, 0x05, 0xa9,
0xf6, 0x3d, 0xa8, 0xe5, 0xc4, 0x32, 0x0f, 0x1a, 0x53, 0xb3, 0x28, 0x0b, 0xcc, 0xac, 0xdb, 0xbf,
0x56, 0xa0, 0xf0, 0xa9, 0x2e, 0x55, 0xdb, 0x82, 0xd5, 0x40, 0xca, 0x18, 0x45, 0xaa, 0x98, 0xee,
0x48, 0x07, 0xaa, 0x5e, 0x18, 0x20, 0x53, 0xc3, 0x81, 0xf9, 0xb7, 0x6b, 0xbd, 0x2b, 0xf3, 0xd3,
0x9d, 0x6a, 0x3f, 0xc5, 0xdc, 0x9c, 0x25, 0xbb, 0x50, 0xf7, 0xc2, 0x20, 0x23, 0x92, 0xef, 0xb9,
0xd7, 0x98, 0x9f, 0xee, 0xd4, 0xfb, 0xfb, 0xc3, 0x5c, 0xbe, 0x28, 0xa3, 0x9d, 0x4a, 0x8f, 0x47,
0xe9, 0x27, 0x5d, 0x73, 0xd3, 0x1d, 0x79, 0x03, 0xeb, 0xc1, 0xe8, 0x15, 0x3f, 0x46, 0xd6, 0x37,
0x03, 0x8b, 0xbd, 0x6a, 0x72, 0x73, 0x67, 0xc9, 0xc4, 0xe0, 0x0c, 0x8b, 0x82, 0xe6, 0xba, 0x7a,
0x9b, 0xf3, 0xd3, 0x9d, 0xf5, 0xe1, 0xa0, 0x80, 0xbb, 0xe7, 0xed, 0x91, 0xfb, 0x60, 0xa3, 0x69,
0xe9, 0x83, 0xa7, 0xfd, 0x47, 0x0f, 0x63, 0x35, 0x46, 0xa6, 0xd2, 0x4e, 0x32, 0x3f, 0x75, 0xd5,
0xbd, 0x90, 0x6f, 0xce, 0x80, 0x2c, 0xfa, 0x5c, 0x52, 0x22, 0xcf, 0xce, 0xb7, 0xf5, 0xa7, 0xef,
0x6c, 0xeb, 0x64, 0x5a, 0x73, 0xf2, 0x71, 0x53, 0x8f, 0x3d, 0x8e, 0xb1, 0x5f, 0xa8, 0xad, 0xbd,
0xdf, 0x2c, 0x68, 0x64, 0xfd, 0xf5, 0x12, 0xc5, 0x34, 0xf0, 0x90, 0x7c, 0x05, 0x95, 0xc7, 0xa8,
0xc8, 0xd6, 0xc2, 0x7c, 0x63, 0x66, 0xba, 0xe6, 0xe6, 0x02, 0xde, 0xb6, 0x7f, 0xf8, 0xf3, 0xef,
0x9f, 0x2e, 0x11, 0xb2, 0x61, 0xe6, 0xd4, 0xe9, 0x6e, 0x3e, 0x23, 0x92, 0x31, 0xc0, 0x63, 0xcc,
0x3f, 0xbc, 0x8b, 0x4c, 0xb6, 0x16, 0xf0, 0x52, 0xaf, 0xb7, 0x5b, 0xc6, 0x43, 0x93, 0xd8, 0x65,
0x0f, 0xdd, 0xb4, 0xc5, 0x7b, 0xfd, 0xdf, 0xe7, 0xdb, 0xd6, 0x1f, 0xf3, 0x6d, 0xeb, 0xaf, 0xf9,
0xb6, 0xf5, 0xcd, 0x27, 0xff, 0x6d, 0x32, 0x4e, 0x4a, 0x2d, 0x37, 0x76, 0xb4, 0x6a, 0xe6, 0xd8,
0x7b, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x1d, 0x4a, 0xd6, 0x7b, 0xb6, 0x0b, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@@ -990,6 +999,15 @@ func (m *Settings) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if len(m.InstallationID) > 0 {
i -= len(m.InstallationID)
copy(dAtA[i:], m.InstallationID)
i = encodeVarintSettings(dAtA, i, uint64(len(m.InstallationID)))
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0xd2
}
if m.AppsInAnyNamespaceEnabled {
i--
if m.AppsInAnyNamespaceEnabled {
@@ -1750,6 +1768,10 @@ func (m *Settings) Size() (n int) {
if m.AppsInAnyNamespaceEnabled {
n += 3
}
l = len(m.InstallationID)
if l > 0 {
n += 2 + l + sovSettings(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@@ -2840,6 +2862,38 @@ func (m *Settings) Unmarshal(dAtA []byte) error {
}
}
m.AppsInAnyNamespaceEnabled = bool(v != 0)
case 26:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field InstallationID", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowSettings
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthSettings
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthSettings
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.InstallationID = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipSettings(dAtA[iNdEx:])

View File

@@ -562,5 +562,5 @@ func (p AppProject) IsAppNamespacePermitted(app *Application, controllerNs strin
return true
}
return glob.MatchStringInList(p.Spec.SourceNamespaces, app.Namespace, false)
return glob.MatchStringInList(p.Spec.SourceNamespaces, app.Namespace, glob.REGEXP)
}

File diff suppressed because it is too large Load Diff

View File

@@ -2174,6 +2174,9 @@ message SyncOperation {
// Revisions is the list of revision (Git) or chart version (Helm) which to sync each source in sources field for the application to
// If omitted, will use the revision specified in app spec.
repeated string revisions = 11;
// SelfHealAttemptsCount contains the number of auto-heal attempts
optional int64 autoHealAttemptsCount = 12;
}
// SyncOperationResource contains resources to sync.
@@ -2241,7 +2244,6 @@ message SyncStatus {
optional string status = 1;
// ComparedTo contains information about what has been compared
// +patchStrategy=replace
optional ComparedTo comparedTo = 2;
// Revision contains information about the revision the comparison has been performed to

View File

@@ -7758,11 +7758,6 @@ func schema_pkg_apis_application_v1alpha1_SyncStatus(ref common.ReferenceCallbac
},
},
"comparedTo": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-patch-strategy": "replace",
},
},
SchemaProps: spec.SchemaProps{
Description: "ComparedTo contains information about what has been compared",
Default: map[string]interface{}{},

View File

@@ -3,6 +3,7 @@ package v1alpha1
import (
"fmt"
"net/url"
"strings"
"github.com/argoproj/argo-cd/v2/util/cert"
"github.com/argoproj/argo-cd/v2/util/git"
@@ -227,21 +228,22 @@ func getCAPath(repoURL string) string {
}
hostname := ""
// url.Parse() will happily parse most things thrown at it. When the URL
// is either https or oci, we use the parsed hostname to retrieve the cert,
// otherwise we'll use the parsed path (OCI repos are often specified as
// hostname, without protocol).
parsedURL, err := url.Parse(repoURL)
var parsedURL *url.URL
var err error
// Without schema in url, url.Parse() treats the url as differently
// and may incorrectly parses the hostname if url contains a path or port.
// To ensure proper parsing, prepend a dummy schema.
if !strings.Contains(repoURL, "://") {
parsedURL, err = url.Parse("protocol://" + repoURL)
} else {
parsedURL, err = url.Parse(repoURL)
}
if err != nil {
log.Warnf("Could not parse repo URL '%s': %v", repoURL, err)
return ""
}
if parsedURL.Scheme == "https" || parsedURL.Scheme == "oci" {
hostname = parsedURL.Host
} else if parsedURL.Scheme == "" {
hostname = parsedURL.Path
}
hostname = parsedURL.Hostname()
if hostname == "" {
log.Warnf("Could not get hostname for repository '%s'", repoURL)
return ""

View File

@@ -938,6 +938,12 @@ type ApplicationDestination struct {
isServerInferred bool `json:"-"`
}
// SetIsServerInferred sets the isServerInferred flag. This is used to allow comparison between two destinations where
// one server is inferred and the other is not.
func (d *ApplicationDestination) SetIsServerInferred(inferred bool) {
d.isServerInferred = inferred
}
type ResourceHealthLocation string
var (
@@ -992,15 +998,15 @@ func (a *ApplicationStatus) GetRevisions() []string {
// BuildComparedToStatus will build a ComparedTo object based on the current
// Application state.
func (app *Application) BuildComparedToStatus() ComparedTo {
func (spec *ApplicationSpec) BuildComparedToStatus() ComparedTo {
ct := ComparedTo{
Destination: app.Spec.Destination,
IgnoreDifferences: app.Spec.IgnoreDifferences,
Destination: spec.Destination,
IgnoreDifferences: spec.IgnoreDifferences,
}
if app.Spec.HasMultipleSources() {
ct.Sources = app.Spec.Sources
if spec.HasMultipleSources() {
ct.Sources = spec.Sources
} else {
ct.Source = app.Spec.GetSource()
ct.Source = spec.GetSource()
}
return ct
}
@@ -1110,6 +1116,8 @@ type SyncOperation struct {
// Revisions is the list of revision (Git) or chart version (Helm) which to sync each source in sources field for the application to
// If omitted, will use the revision specified in app spec.
Revisions []string `json:"revisions,omitempty" protobuf:"bytes,11,opt,name=revisions"`
// SelfHealAttemptsCount contains the number of auto-heal attempts
SelfHealAttemptsCount int64 `json:"autoHealAttemptsCount,omitempty" protobuf:"bytes,12,opt,name=autoHealAttemptsCount"`
}
// IsApplyStrategy returns true if the sync strategy is "apply"
@@ -1519,8 +1527,7 @@ type SyncStatus struct {
// Status is the sync state of the comparison
Status SyncStatusCode `json:"status" protobuf:"bytes,1,opt,name=status,casttype=SyncStatusCode"`
// ComparedTo contains information about what has been compared
// +patchStrategy=replace
ComparedTo ComparedTo `json:"comparedTo,omitempty" protobuf:"bytes,2,opt,name=comparedTo" patchStrategy:"replace"`
ComparedTo ComparedTo `json:"comparedTo,omitempty" protobuf:"bytes,2,opt,name=comparedTo"`
// Revision contains information about the revision the comparison has been performed to
Revision string `json:"revision,omitempty" protobuf:"bytes,3,opt,name=revision"`
// Revisions contains information about the revisions of multiple sources the comparison has been performed to
@@ -2073,6 +2080,8 @@ var validActions = map[string]bool{
var validActionPatterns = []*regexp.Regexp{
regexp.MustCompile("action/.*"),
regexp.MustCompile("update/.*"),
regexp.MustCompile("delete/.*"),
}
func isValidAction(action string) bool {
@@ -2260,11 +2269,11 @@ func (s *SyncWindows) HasWindows() bool {
}
// Active returns a list of sync windows that are currently active
func (s *SyncWindows) Active() *SyncWindows {
func (s *SyncWindows) Active() (*SyncWindows, error) {
return s.active(time.Now())
}
func (s *SyncWindows) active(currentTime time.Time) *SyncWindows {
func (s *SyncWindows) active(currentTime time.Time) (*SyncWindows, error) {
// If SyncWindows.Active() is called outside of a UTC locale, it should be
// first converted to UTC before we scan through the SyncWindows.
currentTime = currentTime.In(time.UTC)
@@ -2273,8 +2282,14 @@ func (s *SyncWindows) active(currentTime time.Time) *SyncWindows {
var active SyncWindows
specParser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
for _, w := range *s {
schedule, _ := specParser.Parse(w.Schedule)
duration, _ := time.ParseDuration(w.Duration)
schedule, sErr := specParser.Parse(w.Schedule)
if sErr != nil {
return nil, fmt.Errorf("cannot parse schedule '%s': %w", w.Schedule, sErr)
}
duration, dErr := time.ParseDuration(w.Duration)
if dErr != nil {
return nil, fmt.Errorf("cannot parse duration '%s': %w", w.Duration, dErr)
}
// Offset the nextWindow time to consider the timeZone of the sync window
timeZoneOffsetDuration := w.scheduleOffsetByTimeZone()
@@ -2284,20 +2299,20 @@ func (s *SyncWindows) active(currentTime time.Time) *SyncWindows {
}
}
if len(active) > 0 {
return &active
return &active, nil
}
}
return nil
return nil, nil
}
// InactiveAllows will iterate over the SyncWindows and return all inactive allow windows
// for the current time. If the current time is in an inactive allow window, syncs will
// be denied.
func (s *SyncWindows) InactiveAllows() *SyncWindows {
func (s *SyncWindows) InactiveAllows() (*SyncWindows, error) {
return s.inactiveAllows(time.Now())
}
func (s *SyncWindows) inactiveAllows(currentTime time.Time) *SyncWindows {
func (s *SyncWindows) inactiveAllows(currentTime time.Time) (*SyncWindows, error) {
// If SyncWindows.InactiveAllows() is called outside of a UTC locale, it should be
// first converted to UTC before we scan through the SyncWindows.
currentTime = currentTime.In(time.UTC)
@@ -2308,21 +2323,27 @@ func (s *SyncWindows) inactiveAllows(currentTime time.Time) *SyncWindows {
for _, w := range *s {
if w.Kind == "allow" {
schedule, sErr := specParser.Parse(w.Schedule)
if sErr != nil {
return nil, fmt.Errorf("cannot parse schedule '%s': %w", w.Schedule, sErr)
}
duration, dErr := time.ParseDuration(w.Duration)
if dErr != nil {
return nil, fmt.Errorf("cannot parse duration '%s': %w", w.Duration, dErr)
}
// Offset the nextWindow time to consider the timeZone of the sync window
timeZoneOffsetDuration := w.scheduleOffsetByTimeZone()
nextWindow := schedule.Next(currentTime.Add(timeZoneOffsetDuration - duration))
if !nextWindow.Before(currentTime.Add(timeZoneOffsetDuration)) && sErr == nil && dErr == nil {
if !nextWindow.Before(currentTime.Add(timeZoneOffsetDuration)) {
inactive = append(inactive, w)
}
}
}
if len(inactive) > 0 {
return &inactive
return &inactive, nil
}
}
return nil
return nil, nil
}
func (w *SyncWindow) scheduleOffsetByTimeZone() time.Duration {
@@ -2426,36 +2447,42 @@ func (w *SyncWindows) Matches(app *Application) *SyncWindows {
}
// CanSync returns true if a sync window currently allows a sync. isManual indicates whether the sync has been triggered manually.
func (w *SyncWindows) CanSync(isManual bool) bool {
func (w *SyncWindows) CanSync(isManual bool) (bool, error) {
if !w.HasWindows() {
return true
return true, nil
}
active := w.Active()
active, err := w.Active()
if err != nil {
return false, fmt.Errorf("invalid sync windows: %w", err)
}
hasActiveDeny, manualEnabled := active.hasDeny()
if hasActiveDeny {
if isManual && manualEnabled {
return true
return true, nil
} else {
return false
return false, nil
}
}
if active.hasAllow() {
return true
return true, nil
}
inactiveAllows := w.InactiveAllows()
inactiveAllows, err := w.InactiveAllows()
if err != nil {
return false, fmt.Errorf("invalid sync windows: %w", err)
}
if inactiveAllows.HasWindows() {
if isManual && inactiveAllows.manualEnabled() {
return true
return true, nil
} else {
return false
return false, nil
}
}
return true
return true, nil
}
// hasDeny will iterate over the SyncWindows and return if a deny window is found and if
@@ -2510,24 +2537,30 @@ func (w *SyncWindows) manualEnabled() bool {
}
// Active returns true if the sync window is currently active
func (w SyncWindow) Active() bool {
func (w SyncWindow) Active() (bool, error) {
return w.active(time.Now())
}
func (w SyncWindow) active(currentTime time.Time) bool {
func (w SyncWindow) active(currentTime time.Time) (bool, error) {
// If SyncWindow.Active() is called outside of a UTC locale, it should be
// first converted to UTC before search
currentTime = currentTime.UTC()
specParser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
schedule, _ := specParser.Parse(w.Schedule)
duration, _ := time.ParseDuration(w.Duration)
schedule, sErr := specParser.Parse(w.Schedule)
if sErr != nil {
return false, fmt.Errorf("cannot parse schedule '%s': %w", w.Schedule, sErr)
}
duration, dErr := time.ParseDuration(w.Duration)
if dErr != nil {
return false, fmt.Errorf("cannot parse duration '%s': %w", w.Duration, dErr)
}
// Offset the nextWindow time to consider the timeZone of the sync window
timeZoneOffsetDuration := w.scheduleOffsetByTimeZone()
nextWindow := schedule.Next(currentTime.Add(timeZoneOffsetDuration - duration))
return nextWindow.Before(currentTime.Add(timeZoneOffsetDuration))
return nextWindow.Before(currentTime.Add(timeZoneOffsetDuration)), nil
}
// Update updates a sync window's settings with the given parameter

View File

@@ -11,10 +11,7 @@ import (
"testing"
"time"
"github.com/argoproj/gitops-engine/pkg/diff"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/utils/ptr"
argocdcommon "github.com/argoproj/argo-cd/v2/common"
@@ -827,8 +824,12 @@ func TestAppProject_ValidPolicyRules(t *testing.T) {
"p, proj:my-proj:my-role, applications, *, my-proj/foo, allow",
"p, proj:my-proj:my-role, applications, create, my-proj/foo, allow",
"p, proj:my-proj:my-role, applications, update, my-proj/foo, allow",
"p, proj:my-proj:my-role, applications, update/*, my-proj/foo, allow",
"p, proj:my-proj:my-role, applications, update/*/Pod/*, my-proj/foo, allow",
"p, proj:my-proj:my-role, applications, sync, my-proj/foo, allow",
"p, proj:my-proj:my-role, applications, delete, my-proj/foo, allow",
"p, proj:my-proj:my-role, applications, delete/*, my-proj/foo, allow",
"p, proj:my-proj:my-role, applications, delete/*/Pod/*, my-proj/foo, allow",
"p, proj:my-proj:my-role, applications, action/*, my-proj/foo, allow",
"p, proj:my-proj:my-role, applications, action/apps/Deployment/restart, my-proj/foo, allow",
}
@@ -1630,7 +1631,9 @@ func TestSyncWindows_HasWindows(t *testing.T) {
func TestSyncWindows_Active(t *testing.T) {
t.Run("WithTestProject", func(t *testing.T) {
proj := newTestProjectWithSyncWindows()
assert.Len(t, *proj.Spec.SyncWindows.Active(), 1)
activeWindows, err := proj.Spec.SyncWindows.Active()
require.NoError(t, err)
assert.Len(t, *activeWindows, 1)
})
syncWindow := func(kind string, schedule string, duration string, timeZone string) *SyncWindow {
@@ -1657,6 +1660,7 @@ func TestSyncWindows_Active(t *testing.T) {
currentTime time.Time
matchingIndex int
expectedLength int
isErr bool
}{
{
name: "MatchFirst",
@@ -1764,11 +1768,36 @@ func TestSyncWindows_Active(t *testing.T) {
matchingIndex: 0,
expectedLength: 1,
},
{
name: "MatchNone-InvalidSchedule",
syncWindow: SyncWindows{
syncWindow("allow", "* 10 * * 7", "3h", ""),
syncWindow("allow", "* 11 * * 7", "3h", ""),
},
currentTime: timeWithHour(12, time.UTC),
expectedLength: 0,
isErr: true,
},
{
name: "MatchNone-InvalidDuration",
syncWindow: SyncWindows{
syncWindow("allow", "* 10 * * *", "3a", ""),
syncWindow("allow", "* 11 * * *", "3a", ""),
},
currentTime: timeWithHour(12, time.UTC),
expectedLength: 0,
isErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.syncWindow.active(tt.currentTime)
result, err := tt.syncWindow.active(tt.currentTime)
if tt.isErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
if result == nil {
result = &SyncWindows{}
}
@@ -1785,7 +1814,9 @@ func TestSyncWindows_InactiveAllows(t *testing.T) {
t.Run("WithTestProject", func(t *testing.T) {
proj := newTestProjectWithSyncWindows()
proj.Spec.SyncWindows[0].Schedule = "0 0 1 1 1"
assert.Len(t, *proj.Spec.SyncWindows.InactiveAllows(), 1)
inactiveAllowWindows, err := proj.Spec.SyncWindows.InactiveAllows()
require.NoError(t, err)
assert.Len(t, *inactiveAllowWindows, 1)
})
syncWindow := func(kind string, schedule string, duration string, timeZone string) *SyncWindow {
@@ -1812,6 +1843,7 @@ func TestSyncWindows_InactiveAllows(t *testing.T) {
currentTime time.Time
matchingIndex int
expectedLength int
isErr bool
}{
{
name: "MatchFirst",
@@ -1937,11 +1969,34 @@ func TestSyncWindows_InactiveAllows(t *testing.T) {
matchingIndex: 0,
expectedLength: 1,
},
{
name: "MatchNone-InvalidSchedule",
syncWindow: SyncWindows{
syncWindow("allow", "* 10 * * 7", "2h", ""),
},
currentTime: timeWithHour(17, time.UTC),
expectedLength: 0,
isErr: true,
},
{
name: "MatchNone-InvalidDuration",
syncWindow: SyncWindows{
syncWindow("allow", "* 10 * * *", "2a", ""),
},
currentTime: timeWithHour(17, time.UTC),
expectedLength: 0,
isErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.syncWindow.inactiveAllows(tt.currentTime)
result, err := tt.syncWindow.inactiveAllows(tt.currentTime)
if tt.isErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
if result == nil {
result = &SyncWindows{}
}
@@ -2052,9 +2107,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
proj := newProjectBuilder().withInactiveDenyWindow(true).build()
// when
canSync := proj.Spec.SyncWindows.CanSync(true)
canSync, err := proj.Spec.SyncWindows.CanSync(true)
// then
require.NoError(t, err)
assert.True(t, canSync)
})
t.Run("will allow manual sync if inactive-deny-window set with manual false", func(t *testing.T) {
@@ -2063,9 +2119,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
proj := newProjectBuilder().withInactiveDenyWindow(false).build()
// when
canSync := proj.Spec.SyncWindows.CanSync(true)
canSync, err := proj.Spec.SyncWindows.CanSync(true)
// then
require.NoError(t, err)
assert.True(t, canSync)
})
t.Run("will deny manual sync if one inactive-allow-windows set with manual false", func(t *testing.T) {
@@ -2077,9 +2134,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(true)
canSync, err := proj.Spec.SyncWindows.CanSync(true)
// then
require.NoError(t, err)
assert.False(t, canSync)
})
t.Run("will allow manual sync if on active-allow-window set with manual true", func(t *testing.T) {
@@ -2090,9 +2148,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(true)
canSync, err := proj.Spec.SyncWindows.CanSync(true)
// then
require.NoError(t, err)
assert.True(t, canSync)
})
t.Run("will allow manual sync if on active-allow-window set with manual false", func(t *testing.T) {
@@ -2103,9 +2162,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(true)
canSync, err := proj.Spec.SyncWindows.CanSync(true)
// then
require.NoError(t, err)
assert.True(t, canSync)
})
t.Run("will allow auto sync if on active-allow-window", func(t *testing.T) {
@@ -2116,9 +2176,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(false)
canSync, err := proj.Spec.SyncWindows.CanSync(false)
// then
require.NoError(t, err)
assert.True(t, canSync)
})
t.Run("will allow manual sync active-allow and inactive-deny", func(t *testing.T) {
@@ -2130,9 +2191,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(true)
canSync, err := proj.Spec.SyncWindows.CanSync(true)
// then
require.NoError(t, err)
assert.True(t, canSync)
})
t.Run("will allow auto sync active-allow and inactive-deny", func(t *testing.T) {
@@ -2144,9 +2206,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(false)
canSync, err := proj.Spec.SyncWindows.CanSync(false)
// then
require.NoError(t, err)
assert.True(t, canSync)
})
t.Run("will deny manual sync inactive-allow", func(t *testing.T) {
@@ -2157,9 +2220,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(true)
canSync, err := proj.Spec.SyncWindows.CanSync(true)
// then
require.NoError(t, err)
assert.False(t, canSync)
})
t.Run("will deny auto sync inactive-allow", func(t *testing.T) {
@@ -2170,9 +2234,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(false)
canSync, err := proj.Spec.SyncWindows.CanSync(false)
// then
require.NoError(t, err)
assert.False(t, canSync)
})
t.Run("will allow manual sync inactive-allow with ManualSync enabled", func(t *testing.T) {
@@ -2183,9 +2248,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(true)
canSync, err := proj.Spec.SyncWindows.CanSync(true)
// then
require.NoError(t, err)
assert.True(t, canSync)
})
t.Run("will deny auto sync inactive-allow with ManualSync enabled", func(t *testing.T) {
@@ -2196,9 +2262,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(false)
canSync, err := proj.Spec.SyncWindows.CanSync(false)
// then
require.NoError(t, err)
assert.False(t, canSync)
})
t.Run("will deny manual sync with inactive-allow and inactive-deny", func(t *testing.T) {
@@ -2210,9 +2277,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(true)
canSync, err := proj.Spec.SyncWindows.CanSync(true)
// then
require.NoError(t, err)
assert.False(t, canSync)
})
t.Run("will deny auto sync with inactive-allow and inactive-deny", func(t *testing.T) {
@@ -2224,9 +2292,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(false)
canSync, err := proj.Spec.SyncWindows.CanSync(false)
// then
require.NoError(t, err)
assert.False(t, canSync)
})
t.Run("will allow auto sync with active-allow and inactive-allow", func(t *testing.T) {
@@ -2238,9 +2307,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(false)
canSync, err := proj.Spec.SyncWindows.CanSync(false)
// then
require.NoError(t, err)
assert.True(t, canSync)
})
t.Run("will deny manual sync with active-deny", func(t *testing.T) {
@@ -2251,9 +2321,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(true)
canSync, err := proj.Spec.SyncWindows.CanSync(true)
// then
require.NoError(t, err)
assert.False(t, canSync)
})
t.Run("will deny auto sync with active-deny", func(t *testing.T) {
@@ -2264,9 +2335,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(false)
canSync, err := proj.Spec.SyncWindows.CanSync(false)
// then
require.NoError(t, err)
assert.False(t, canSync)
})
t.Run("will allow manual sync with active-deny with ManualSync enabled", func(t *testing.T) {
@@ -2277,9 +2349,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(true)
canSync, err := proj.Spec.SyncWindows.CanSync(true)
// then
require.NoError(t, err)
assert.True(t, canSync)
})
t.Run("will deny auto sync with active-deny with ManualSync enabled", func(t *testing.T) {
@@ -2290,9 +2363,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(false)
canSync, err := proj.Spec.SyncWindows.CanSync(false)
// then
require.NoError(t, err)
assert.False(t, canSync)
})
t.Run("will deny manual sync with many active-deny having one with ManualSync disabled", func(t *testing.T) {
@@ -2306,9 +2380,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(true)
canSync, err := proj.Spec.SyncWindows.CanSync(true)
// then
require.NoError(t, err)
assert.False(t, canSync)
})
t.Run("will deny auto sync with many active-deny having one with ManualSync disabled", func(t *testing.T) {
@@ -2322,9 +2397,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(false)
canSync, err := proj.Spec.SyncWindows.CanSync(false)
// then
require.NoError(t, err)
assert.False(t, canSync)
})
t.Run("will deny manual sync with active-deny and active-allow windows with ManualSync disabled", func(t *testing.T) {
@@ -2336,9 +2412,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(true)
canSync, err := proj.Spec.SyncWindows.CanSync(true)
// then
require.NoError(t, err)
assert.False(t, canSync)
})
t.Run("will allow manual sync with active-deny and active-allow windows with ManualSync enabled", func(t *testing.T) {
@@ -2350,9 +2427,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(true)
canSync, err := proj.Spec.SyncWindows.CanSync(true)
// then
require.NoError(t, err)
assert.True(t, canSync)
})
t.Run("will deny auto sync with active-deny and active-allow windows with ManualSync enabled", func(t *testing.T) {
@@ -2364,9 +2442,24 @@ func TestSyncWindows_CanSync(t *testing.T) {
build()
// when
canSync := proj.Spec.SyncWindows.CanSync(false)
canSync, err := proj.Spec.SyncWindows.CanSync(false)
// then
require.NoError(t, err)
assert.False(t, canSync)
})
t.Run("will deny and return error with invalid windows", func(t *testing.T) {
// given
t.Parallel()
proj := newProjectBuilder().
withInvalidWindows().
build()
// when
canSync, err := proj.Spec.SyncWindows.CanSync(false)
// then
require.Error(t, err)
assert.False(t, canSync)
})
}
@@ -2416,8 +2509,9 @@ func TestSyncWindows_hasAllow(t *testing.T) {
func TestSyncWindow_Active(t *testing.T) {
window := &SyncWindow{Schedule: "* * * * *", Duration: "1h"}
t.Run("ActiveWindow", func(t *testing.T) {
window.Active()
assert.True(t, window.Active())
isActive, err := window.Active()
require.NoError(t, err)
assert.True(t, isActive)
})
syncWindow := func(kind string, schedule string, duration string) SyncWindow {
@@ -2442,6 +2536,7 @@ func TestSyncWindow_Active(t *testing.T) {
syncWindow SyncWindow
currentTime time.Time
expectedResult bool
isErr bool
}{
{
name: "Allow-active",
@@ -2491,11 +2586,44 @@ func TestSyncWindow_Active(t *testing.T) {
currentTime: timeWithHour(13-4, utcM4Zone),
expectedResult: false,
},
{
name: "Allow-inactive-InvalidSchedule",
syncWindow: syncWindow("allow", "* 10 * * 7", "2h"),
currentTime: timeWithHour(11, time.UTC),
expectedResult: false,
isErr: true,
},
{
name: "Deny-inactive-InvalidSchedule",
syncWindow: syncWindow("deny", "* 10 * * 7", "2h"),
currentTime: timeWithHour(11, time.UTC),
expectedResult: false,
isErr: true,
},
{
name: "Allow-inactive-InvalidDuration",
syncWindow: syncWindow("allow", "* 10 * * *", "2a"),
currentTime: timeWithHour(11, time.UTC),
expectedResult: false,
isErr: true,
},
{
name: "Deny-inactive-InvalidDuration",
syncWindow: syncWindow("deny", "* 10 * * *", "2a"),
currentTime: timeWithHour(11, time.UTC),
expectedResult: false,
isErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.syncWindow.active(tt.currentTime)
result, err := tt.syncWindow.active(tt.currentTime)
if tt.isErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
assert.Equal(t, tt.expectedResult, result)
})
}
@@ -2607,6 +2735,16 @@ func (b *projectBuilder) withInactiveDenyWindow(allowManual bool) *projectBuilde
return b
}
func (b *projectBuilder) withInvalidWindows() *projectBuilder {
b.proj.Spec.SyncWindows = append(b.proj.Spec.SyncWindows,
newSyncWindow("allow", "* 10 * * 7", false),
newSyncWindow("deny", "* 10 * * 7", false),
newSyncWindow("allow", "* 10 * * 7", true),
newSyncWindow("deny", "* 10 * * 7", true),
)
return b
}
func inactiveCronSchedule() string {
hourPlus10, _, _ := time.Now().Add(10 * time.Hour).Clock()
return fmt.Sprintf("0 %d * * *", hourPlus10)
@@ -3089,7 +3227,7 @@ func TestOrphanedResourcesMonitorSettings_IsWarn(t *testing.T) {
assert.True(t, settings.IsWarn())
}
func Test_isValidPolicy(t *testing.T) {
func Test_isValidPolicyObject(t *testing.T) {
policyTests := []struct {
name string
policy string
@@ -3239,18 +3377,25 @@ func TestGetCAPath(t *testing.T) {
"https://foo.example.com",
"oci://foo.example.com",
"foo.example.com",
"foo.example.com/charts",
"https://foo.example.com:5000",
"foo.example.com:5000",
"foo.example.com:5000/charts",
"ssh://foo.example.com",
}
invalidpath := []string{
"https://bar.example.com",
"oci://bar.example.com",
"bar.example.com",
"ssh://foo.example.com",
"git@example.com:organization/reponame.git",
"ssh://bar.example.com",
"git@foo.example.com:organization/reponame.git",
"ssh://git@foo.example.com:organization/reponame.git",
"/some/invalid/thing",
"../another/invalid/thing",
"./also/invalid",
"$invalid/as/well",
"..",
"://invalid",
}
for _, str := range validcert {
@@ -3734,35 +3879,3 @@ func TestApplicationSpec_GetSourcePtrByIndex(t *testing.T) {
})
}
}
func TestHelmValuesObjectHasReplaceStrategy(t *testing.T) {
app := Application{
Status: ApplicationStatus{Sync: SyncStatus{ComparedTo: ComparedTo{
Source: ApplicationSource{
Helm: &ApplicationSourceHelm{
ValuesObject: &runtime.RawExtension{
Object: &unstructured.Unstructured{Object: map[string]interface{}{"key": []string{"value"}}},
},
},
},
}}},
}
appModified := Application{
Status: ApplicationStatus{Sync: SyncStatus{ComparedTo: ComparedTo{
Source: ApplicationSource{
Helm: &ApplicationSourceHelm{
ValuesObject: &runtime.RawExtension{
Object: &unstructured.Unstructured{Object: map[string]interface{}{"key": []string{"value-modified1"}}},
},
},
},
}}},
}
patch, _, err := diff.CreateTwoWayMergePatch(
app,
appModified, Application{})
require.NoError(t, err)
assert.Equal(t, `{"status":{"sync":{"comparedTo":{"destination":{},"source":{"helm":{"valuesObject":{"key":["value-modified1"]}},"repoURL":""}}}}}`, string(patch))
}

View File

@@ -57,7 +57,9 @@ type ManifestRequest struct {
// This is used to surface "source not permitted" errors for Helm repositories
ProjectSourceRepos []string `protobuf:"bytes,24,rep,name=projectSourceRepos,proto3" json:"projectSourceRepos,omitempty"`
// This is used to surface "source not permitted" errors for Helm repositories
ProjectName string `protobuf:"bytes,25,opt,name=projectName,proto3" json:"projectName,omitempty"`
ProjectName string `protobuf:"bytes,25,opt,name=projectName,proto3" json:"projectName,omitempty"`
// Holds instance installation id
InstallationID string `protobuf:"bytes,27,opt,name=installationID,proto3" json:"installationID,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@@ -250,6 +252,13 @@ func (m *ManifestRequest) GetProjectName() string {
return ""
}
func (m *ManifestRequest) GetInstallationID() string {
if m != nil {
return m.InstallationID
}
return ""
}
type ManifestRequestWithFiles struct {
// Types that are valid to be assigned to Part:
// *ManifestRequestWithFiles_Request
@@ -2196,6 +2205,7 @@ type UpdateRevisionForPathsRequest struct {
SyncedRevision string `protobuf:"bytes,11,opt,name=syncedRevision,proto3" json:"syncedRevision,omitempty"`
Revision string `protobuf:"bytes,12,opt,name=revision,proto3" json:"revision,omitempty"`
Paths []string `protobuf:"bytes,13,rep,name=paths,proto3" json:"paths,omitempty"`
InstallationID string `protobuf:"bytes,15,opt,name=installationID,proto3" json:"installationID,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@@ -2325,6 +2335,13 @@ func (m *UpdateRevisionForPathsRequest) GetPaths() []string {
return nil
}
func (m *UpdateRevisionForPathsRequest) GetInstallationID() string {
if m != nil {
return m.InstallationID
}
return ""
}
type UpdateRevisionForPathsResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
@@ -2414,151 +2431,152 @@ func init() {
}
var fileDescriptor_dd8723cfcc820480 = []byte{
// 2298 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x5a, 0xcd, 0x73, 0x1c, 0x47,
0x15, 0xd7, 0x7e, 0x6a, 0xf7, 0x49, 0xd6, 0x47, 0xdb, 0x96, 0xc7, 0x1b, 0x5b, 0xa5, 0x0c, 0xd8,
0xe5, 0xd8, 0xc9, 0x6e, 0x59, 0xae, 0xc4, 0xe0, 0x84, 0x50, 0x8a, 0x62, 0x4b, 0x8e, 0x2d, 0x5b,
0x8c, 0x1d, 0x28, 0x83, 0x81, 0xea, 0x9d, 0xed, 0xdd, 0x9d, 0xec, 0x7c, 0xb4, 0x67, 0x7a, 0x14,
0xd6, 0x55, 0x9c, 0xa0, 0xb8, 0x70, 0xe7, 0xc0, 0x95, 0x7f, 0x80, 0x0b, 0xc5, 0x91, 0x03, 0xc5,
0xc7, 0x91, 0xe2, 0xc2, 0x11, 0xca, 0x47, 0xfe, 0x0a, 0xaa, 0x3f, 0xe6, 0x73, 0x67, 0xd7, 0x0a,
0x6b, 0x2b, 0x90, 0x8b, 0x34, 0xfd, 0xba, 0xfb, 0xbd, 0xd7, 0xaf, 0xdf, 0x7b, 0xfd, 0x7b, 0xdd,
0x0b, 0x97, 0x7d, 0x42, 0xbd, 0x80, 0xf8, 0x47, 0xc4, 0xef, 0x88, 0x4f, 0x8b, 0x79, 0xfe, 0x38,
0xf5, 0xd9, 0xa6, 0xbe, 0xc7, 0x3c, 0x04, 0x09, 0xa5, 0x75, 0x7f, 0x60, 0xb1, 0x61, 0xd8, 0x6d,
0x9b, 0x9e, 0xd3, 0xc1, 0xfe, 0xc0, 0xa3, 0xbe, 0xf7, 0x99, 0xf8, 0x78, 0xc7, 0xec, 0x75, 0x8e,
0xb6, 0x3b, 0x74, 0x34, 0xe8, 0x60, 0x6a, 0x05, 0x1d, 0x4c, 0xa9, 0x6d, 0x99, 0x98, 0x59, 0x9e,
0xdb, 0x39, 0xba, 0x8e, 0x6d, 0x3a, 0xc4, 0xd7, 0x3b, 0x03, 0xe2, 0x12, 0x1f, 0x33, 0xd2, 0x93,
0x9c, 0x5b, 0x6f, 0x0c, 0x3c, 0x6f, 0x60, 0x93, 0x8e, 0x68, 0x75, 0xc3, 0x7e, 0x87, 0x38, 0x94,
0x29, 0xb1, 0xfa, 0xbf, 0x97, 0x61, 0xf5, 0x00, 0xbb, 0x56, 0x9f, 0x04, 0xcc, 0x20, 0xcf, 0x42,
0x12, 0x30, 0xf4, 0x14, 0xaa, 0x5c, 0x19, 0xad, 0xb4, 0x55, 0xba, 0xb2, 0xb4, 0xbd, 0xdf, 0x4e,
0xb4, 0x69, 0x47, 0xda, 0x88, 0x8f, 0x1f, 0x9b, 0xbd, 0xf6, 0xd1, 0x76, 0x9b, 0x8e, 0x06, 0x6d,
0xae, 0x4d, 0x3b, 0xa5, 0x4d, 0x3b, 0xd2, 0xa6, 0x6d, 0xc4, 0xcb, 0x32, 0x04, 0x57, 0xd4, 0x82,
0x86, 0x4f, 0x8e, 0xac, 0xc0, 0xf2, 0x5c, 0xad, 0xbc, 0x55, 0xba, 0xd2, 0x34, 0xe2, 0x36, 0xd2,
0x60, 0xd1, 0xf5, 0x76, 0xb1, 0x39, 0x24, 0x5a, 0x65, 0xab, 0x74, 0xa5, 0x61, 0x44, 0x4d, 0xb4,
0x05, 0x4b, 0x98, 0xd2, 0xfb, 0xb8, 0x4b, 0xec, 0x7b, 0x64, 0xac, 0x55, 0xc5, 0xc4, 0x34, 0x89,
0xcf, 0xc5, 0x94, 0x3e, 0xc0, 0x0e, 0xd1, 0x6a, 0xa2, 0x37, 0x6a, 0xa2, 0x0b, 0xd0, 0x74, 0xb1,
0x43, 0x02, 0x8a, 0x4d, 0xa2, 0x35, 0x44, 0x5f, 0x42, 0x40, 0x3f, 0x85, 0xf5, 0x94, 0xe2, 0x8f,
0xbc, 0xd0, 0x37, 0x89, 0x06, 0x62, 0xe9, 0x0f, 0xe7, 0x5b, 0xfa, 0x4e, 0x9e, 0xad, 0x31, 0x29,
0x09, 0xfd, 0x08, 0x6a, 0x62, 0xe7, 0xb5, 0xa5, 0xad, 0xca, 0x2b, 0xb5, 0xb6, 0x64, 0x8b, 0x5c,
0x58, 0xa4, 0x76, 0x38, 0xb0, 0xdc, 0x40, 0x5b, 0x16, 0x12, 0x1e, 0xcf, 0x27, 0x61, 0xd7, 0x73,
0xfb, 0xd6, 0xe0, 0x00, 0xbb, 0x78, 0x40, 0x1c, 0xe2, 0xb2, 0x43, 0xc1, 0xdc, 0x88, 0x84, 0xa0,
0xe7, 0xb0, 0x36, 0x0a, 0x03, 0xe6, 0x39, 0xd6, 0x73, 0xf2, 0x90, 0xf2, 0xb9, 0x81, 0x76, 0x4a,
0x58, 0xf3, 0xc1, 0x7c, 0x82, 0xef, 0xe5, 0xb8, 0x1a, 0x13, 0x72, 0xb8, 0x93, 0x8c, 0xc2, 0x2e,
0xf9, 0x2e, 0xf1, 0x85, 0x77, 0xad, 0x48, 0x27, 0x49, 0x91, 0xa4, 0x1b, 0x59, 0xaa, 0x15, 0x68,
0xab, 0x5b, 0x15, 0xe9, 0x46, 0x31, 0x09, 0x5d, 0x81, 0xd5, 0x23, 0xe2, 0x5b, 0xfd, 0xf1, 0x23,
0x6b, 0xe0, 0x62, 0x16, 0xfa, 0x44, 0x5b, 0x13, 0xae, 0x98, 0x27, 0x23, 0x07, 0x4e, 0x0d, 0x89,
0xed, 0x70, 0x93, 0xef, 0xfa, 0xa4, 0x17, 0x68, 0xeb, 0xc2, 0xbe, 0x7b, 0xf3, 0xef, 0xa0, 0x60,
0x67, 0x64, 0xb9, 0x73, 0xc5, 0x5c, 0xcf, 0x50, 0x91, 0x22, 0x63, 0x04, 0x49, 0xc5, 0x72, 0x64,
0x74, 0x19, 0x56, 0x98, 0x8f, 0xcd, 0x91, 0xe5, 0x0e, 0x0e, 0x08, 0x1b, 0x7a, 0x3d, 0xed, 0xb4,
0xb0, 0x44, 0x8e, 0x8a, 0x4c, 0x40, 0xc4, 0xc5, 0x5d, 0x9b, 0xf4, 0xa4, 0x2f, 0x3e, 0x1e, 0x53,
0x12, 0x68, 0x67, 0xc4, 0x2a, 0x6e, 0xb4, 0x53, 0x19, 0x2a, 0x97, 0x20, 0xda, 0xb7, 0x27, 0x66,
0xdd, 0x76, 0x99, 0x3f, 0x36, 0x0a, 0xd8, 0xa1, 0x11, 0x2c, 0xf1, 0x75, 0x44, 0xae, 0x70, 0x56,
0xb8, 0xc2, 0xdd, 0xf9, 0x6c, 0xb4, 0x9f, 0x30, 0x34, 0xd2, 0xdc, 0x51, 0x1b, 0xd0, 0x10, 0x07,
0x07, 0xa1, 0xcd, 0x2c, 0x6a, 0x13, 0xa9, 0x46, 0xa0, 0x6d, 0x08, 0x33, 0x15, 0xf4, 0xa0, 0x7b,
0x00, 0x3e, 0xe9, 0x47, 0xe3, 0xce, 0x89, 0x95, 0x5f, 0x9b, 0xb5, 0x72, 0x23, 0x1e, 0x2d, 0x57,
0x9c, 0x9a, 0xce, 0x85, 0xf3, 0x65, 0x10, 0x93, 0xa9, 0x68, 0x17, 0x61, 0xad, 0x09, 0x17, 0x2b,
0xe8, 0xe1, 0xbe, 0xa8, 0xa8, 0x22, 0x69, 0x9d, 0x97, 0xde, 0x9a, 0x22, 0xb5, 0x6e, 0xc3, 0xb9,
0x29, 0xa6, 0x46, 0x6b, 0x50, 0x19, 0x91, 0xb1, 0x48, 0xd1, 0x4d, 0x83, 0x7f, 0xa2, 0x33, 0x50,
0x3b, 0xc2, 0x76, 0x48, 0x44, 0x52, 0x6d, 0x18, 0xb2, 0x71, 0xab, 0xfc, 0x8d, 0x52, 0xeb, 0x17,
0x25, 0x58, 0xcd, 0x29, 0x5e, 0x30, 0xff, 0x87, 0xe9, 0xf9, 0xaf, 0xc0, 0x8d, 0xfb, 0x8f, 0xb1,
0x3f, 0x20, 0x2c, 0xa5, 0x88, 0xfe, 0xf7, 0x12, 0x68, 0x39, 0x8b, 0x7e, 0xcf, 0x62, 0xc3, 0x3b,
0x96, 0x4d, 0x02, 0x74, 0x13, 0x16, 0x7d, 0x49, 0x53, 0x07, 0xcf, 0x1b, 0x33, 0x36, 0x62, 0x7f,
0xc1, 0x88, 0x46, 0xa3, 0x0f, 0xa1, 0xe1, 0x10, 0x86, 0x7b, 0x98, 0x61, 0xa5, 0xfb, 0x56, 0xd1,
0x4c, 0x2e, 0xe5, 0x40, 0x8d, 0xdb, 0x5f, 0x30, 0xe2, 0x39, 0xe8, 0x5d, 0xa8, 0x99, 0xc3, 0xd0,
0x1d, 0x89, 0x23, 0x67, 0x69, 0xfb, 0xe2, 0xb4, 0xc9, 0xbb, 0x7c, 0xd0, 0xfe, 0x82, 0x21, 0x47,
0x7f, 0x54, 0x87, 0x2a, 0xc5, 0x3e, 0xd3, 0xef, 0xc0, 0x99, 0x22, 0x11, 0xfc, 0x9c, 0x33, 0x87,
0xc4, 0x1c, 0x05, 0xa1, 0xa3, 0xcc, 0x1c, 0xb7, 0x11, 0x82, 0x6a, 0x60, 0x3d, 0x97, 0xa6, 0xae,
0x18, 0xe2, 0x5b, 0x7f, 0x0b, 0xd6, 0x27, 0xa4, 0xf1, 0x4d, 0x95, 0xba, 0x71, 0x0e, 0xcb, 0x4a,
0xb4, 0x1e, 0xc2, 0xd9, 0xc7, 0xc2, 0x16, 0x71, 0xb2, 0x3f, 0x89, 0x93, 0x5b, 0xdf, 0x87, 0x8d,
0xbc, 0xd8, 0x80, 0x7a, 0x6e, 0x40, 0xb8, 0xeb, 0x8b, 0xec, 0x68, 0x91, 0x5e, 0xd2, 0x2b, 0xb4,
0x68, 0x18, 0x05, 0x3d, 0xfa, 0x6f, 0xca, 0xb0, 0x61, 0x90, 0xc0, 0xb3, 0x8f, 0x48, 0x94, 0xba,
0x4e, 0x06, 0x7c, 0xfc, 0x00, 0x2a, 0x98, 0x52, 0xe5, 0x26, 0x77, 0x5f, 0xd9, 0xf1, 0x6e, 0x70,
0xae, 0xe8, 0x6d, 0x58, 0xc7, 0x4e, 0xd7, 0x1a, 0x84, 0x5e, 0x18, 0x44, 0xcb, 0x12, 0x4e, 0xd5,
0x34, 0x26, 0x3b, 0x78, 0xf8, 0x07, 0x22, 0x22, 0xef, 0xba, 0x3d, 0xf2, 0x13, 0x81, 0x68, 0x2a,
0x46, 0x9a, 0xa4, 0x9b, 0x70, 0x6e, 0xc2, 0x48, 0xca, 0xe0, 0x69, 0x10, 0x55, 0xca, 0x81, 0xa8,
0x42, 0x35, 0xca, 0x53, 0xd4, 0xd0, 0xff, 0x5c, 0x82, 0xb5, 0x24, 0xb8, 0x14, 0xfb, 0x0b, 0xd0,
0x74, 0x14, 0x2d, 0xd0, 0x4a, 0x22, 0x83, 0x25, 0x84, 0x2c, 0x9e, 0x2a, 0xe7, 0xf1, 0xd4, 0x06,
0xd4, 0x25, 0xdc, 0x55, 0x4b, 0x57, 0xad, 0x8c, 0xca, 0xd5, 0x9c, 0xca, 0x9b, 0x00, 0x41, 0x9c,
0xe1, 0xb4, 0xba, 0xe8, 0x4d, 0x51, 0x90, 0x0e, 0xcb, 0xf2, 0xf4, 0x35, 0x48, 0x10, 0xda, 0x4c,
0x5b, 0x14, 0x23, 0x32, 0x34, 0xdd, 0x83, 0xd5, 0xfb, 0x16, 0x5f, 0x43, 0x3f, 0x38, 0x99, 0x70,
0x78, 0x0f, 0xaa, 0x5c, 0x18, 0x5f, 0x58, 0xd7, 0xc7, 0xae, 0x39, 0x24, 0x91, 0xad, 0xe2, 0x36,
0x0f, 0x74, 0x86, 0x07, 0x81, 0x56, 0x16, 0x74, 0xf1, 0xad, 0xff, 0xbe, 0x2c, 0x35, 0xdd, 0xa1,
0x34, 0xf8, 0xf2, 0x21, 0x77, 0x31, 0x08, 0xa8, 0x4c, 0x82, 0x80, 0x9c, 0xca, 0x5f, 0x04, 0x04,
0xbc, 0xa2, 0x83, 0x4c, 0x0f, 0x61, 0x71, 0x87, 0x52, 0xae, 0x08, 0xba, 0x0e, 0x55, 0x4c, 0xa9,
0x34, 0x78, 0x2e, 0x67, 0xab, 0x21, 0xfc, 0xbf, 0x52, 0x49, 0x0c, 0x6d, 0xdd, 0x84, 0x66, 0x4c,
0x7a, 0x99, 0xd8, 0x66, 0x5a, 0xec, 0x16, 0x80, 0x44, 0xb9, 0x77, 0xdd, 0xbe, 0xc7, 0xb7, 0x94,
0x3b, 0xbb, 0x9a, 0x2a, 0xbe, 0xf5, 0x5b, 0xd1, 0x08, 0xa1, 0xdb, 0xdb, 0x50, 0xb3, 0x18, 0x71,
0x22, 0xe5, 0x36, 0xd2, 0xca, 0x25, 0x8c, 0x0c, 0x39, 0x48, 0xff, 0x4b, 0x03, 0xce, 0xf3, 0x1d,
0x7b, 0x24, 0xc2, 0x64, 0x87, 0xd2, 0x8f, 0x09, 0xc3, 0x96, 0x1d, 0x7c, 0x27, 0x24, 0xfe, 0xf8,
0x35, 0x3b, 0xc6, 0x00, 0xea, 0x32, 0xca, 0x54, 0x46, 0x7c, 0xe5, 0x05, 0x8f, 0x62, 0x9f, 0x54,
0x39, 0x95, 0xd7, 0x53, 0xe5, 0x14, 0x55, 0x1d, 0xd5, 0x13, 0xaa, 0x3a, 0xa6, 0x17, 0x9e, 0xa9,
0x72, 0xb6, 0x9e, 0x2d, 0x67, 0x0b, 0xc0, 0xfc, 0xe2, 0x71, 0xc1, 0x7c, 0xa3, 0x10, 0xcc, 0x3b,
0x85, 0x71, 0xdc, 0x14, 0xe6, 0xfe, 0x56, 0xda, 0x03, 0xa7, 0xfa, 0xda, 0x3c, 0xb0, 0x1e, 0x5e,
0x2b, 0xac, 0xff, 0x34, 0x03, 0xd3, 0x65, 0xa1, 0xfc, 0xee, 0xf1, 0xd6, 0x34, 0x03, 0xb0, 0x7f,
0xe5, 0xe0, 0xf5, 0xcf, 0x05, 0xaa, 0xa2, 0x5e, 0x62, 0x83, 0xf8, 0x40, 0xe7, 0xe7, 0x10, 0x3f,
0x5a, 0x55, 0xd2, 0xe2, 0xdf, 0xe8, 0x1a, 0x54, 0xb9, 0x91, 0x15, 0xec, 0x3d, 0x97, 0xb6, 0x27,
0xdf, 0x89, 0x1d, 0x4a, 0x1f, 0x51, 0x62, 0x1a, 0x62, 0x10, 0xba, 0x05, 0xcd, 0xd8, 0xf1, 0x55,
0x64, 0x5d, 0x48, 0xcf, 0x88, 0xe3, 0x24, 0x9a, 0x96, 0x0c, 0xe7, 0x73, 0x7b, 0x96, 0x4f, 0x4c,
0x01, 0x0a, 0x6b, 0x93, 0x73, 0x3f, 0x8e, 0x3a, 0xe3, 0xb9, 0xf1, 0x70, 0x74, 0x1d, 0xea, 0xf2,
0x66, 0x41, 0x44, 0xd0, 0xd2, 0xf6, 0xf9, 0xc9, 0x64, 0x1a, 0xcd, 0x52, 0x03, 0xf5, 0x3f, 0x95,
0xe0, 0xcd, 0xc4, 0x21, 0xa2, 0x68, 0x8a, 0x70, 0xf9, 0x97, 0x7f, 0xe2, 0x5e, 0x86, 0x15, 0x51,
0x08, 0x24, 0x17, 0x0c, 0xf2, 0xae, 0x2b, 0x47, 0xd5, 0x7f, 0x57, 0x82, 0x4b, 0x93, 0xeb, 0xd8,
0x1d, 0x62, 0x9f, 0xc5, 0xdb, 0x7b, 0x12, 0x6b, 0x89, 0x0e, 0xbc, 0x72, 0x72, 0xe0, 0x65, 0xd6,
0x57, 0xc9, 0xae, 0x4f, 0xff, 0x43, 0x19, 0x96, 0x52, 0x0e, 0x54, 0x74, 0x60, 0x72, 0xc0, 0x27,
0xfc, 0x56, 0x94, 0x7e, 0xe2, 0x50, 0x68, 0x1a, 0x29, 0x0a, 0x1a, 0x01, 0x50, 0xec, 0x63, 0x87,
0x30, 0xe2, 0xf3, 0x4c, 0xce, 0x23, 0xfe, 0xde, 0xfc, 0xd9, 0xe5, 0x30, 0xe2, 0x69, 0xa4, 0xd8,
0x73, 0xc4, 0x2a, 0x44, 0x07, 0x2a, 0x7f, 0xab, 0x16, 0xfa, 0x1c, 0x56, 0xfa, 0x96, 0x4d, 0x0e,
0x13, 0x45, 0xea, 0x42, 0x91, 0x87, 0xf3, 0x2b, 0x72, 0x27, 0xcd, 0xd7, 0xc8, 0x89, 0xd1, 0xaf,
0xc2, 0x5a, 0x3e, 0x9e, 0xb8, 0x92, 0x96, 0x83, 0x07, 0xb1, 0xb5, 0x54, 0x4b, 0x47, 0xb0, 0x96,
0x8f, 0x1f, 0xfd, 0x9f, 0x65, 0x38, 0x1b, 0xb3, 0xdb, 0x71, 0x5d, 0x2f, 0x74, 0x4d, 0x71, 0x59,
0x57, 0xb8, 0x17, 0x67, 0xa0, 0xc6, 0x2c, 0x66, 0xc7, 0xc0, 0x47, 0x34, 0xf8, 0xd9, 0xc5, 0x3c,
0xcf, 0x66, 0x16, 0x55, 0x1b, 0x1c, 0x35, 0xe5, 0xde, 0x3f, 0x0b, 0x2d, 0x9f, 0xf4, 0x44, 0x26,
0x68, 0x18, 0x71, 0x9b, 0xf7, 0x71, 0x54, 0x23, 0x60, 0xbc, 0x34, 0x66, 0xdc, 0x16, 0x7e, 0xef,
0xd9, 0x36, 0x31, 0xb9, 0x39, 0x52, 0x40, 0x3f, 0x47, 0x15, 0x05, 0x04, 0xf3, 0x2d, 0x77, 0xa0,
0x60, 0xbe, 0x6a, 0x71, 0x3d, 0xb1, 0xef, 0xe3, 0xb1, 0xd6, 0x10, 0x06, 0x90, 0x0d, 0xf4, 0x01,
0x54, 0x1c, 0x4c, 0xd5, 0x41, 0x77, 0x35, 0x93, 0x1d, 0x8a, 0x2c, 0xd0, 0x3e, 0xc0, 0x54, 0x9e,
0x04, 0x7c, 0x5a, 0xeb, 0x3d, 0x68, 0x44, 0x84, 0x2f, 0x04, 0x09, 0x3f, 0x83, 0x53, 0x99, 0xe4,
0x83, 0x9e, 0xc0, 0x46, 0xe2, 0x51, 0x69, 0x81, 0x0a, 0x04, 0xbe, 0xf9, 0x52, 0xcd, 0x8c, 0x29,
0x0c, 0xf4, 0x67, 0xb0, 0xce, 0x5d, 0x46, 0x04, 0xfe, 0x09, 0x95, 0x36, 0xef, 0x43, 0x33, 0x16,
0x59, 0xe8, 0x33, 0x2d, 0x68, 0x1c, 0x45, 0x97, 0xa8, 0xb2, 0xb6, 0x89, 0xdb, 0xfa, 0x0e, 0xa0,
0xb4, 0xbe, 0xea, 0x04, 0xba, 0x96, 0x05, 0xc5, 0x67, 0xf3, 0xc7, 0x8d, 0x18, 0x1e, 0x61, 0xe2,
0x7f, 0x94, 0x61, 0x75, 0xcf, 0x12, 0xf7, 0x20, 0x27, 0x94, 0xe4, 0xae, 0xc2, 0x5a, 0x10, 0x76,
0x1d, 0xaf, 0x17, 0xda, 0x44, 0x81, 0x02, 0x75, 0xd2, 0x4f, 0xd0, 0x67, 0x25, 0x3f, 0x6e, 0x2c,
0x8a, 0xd9, 0x50, 0x55, 0xb8, 0xe2, 0x1b, 0x7d, 0x00, 0xe7, 0x1f, 0x90, 0xcf, 0xd5, 0x7a, 0xf6,
0x6c, 0xaf, 0xdb, 0xb5, 0xdc, 0x41, 0x24, 0xa4, 0x26, 0x84, 0x4c, 0x1f, 0x50, 0x04, 0x15, 0xeb,
0xc5, 0x50, 0x31, 0xae, 0x92, 0x77, 0x3d, 0xc7, 0xb1, 0x98, 0x42, 0x94, 0x19, 0x9a, 0xfe, 0xb3,
0x12, 0xac, 0x25, 0x96, 0x55, 0x7b, 0x73, 0x53, 0xc6, 0x90, 0xdc, 0x99, 0x4b, 0xe9, 0x9d, 0xc9,
0x0f, 0xfd, 0xef, 0xc3, 0x67, 0x39, 0x1d, 0x3e, 0xbf, 0x2c, 0xc3, 0xd9, 0x3d, 0x8b, 0x45, 0x89,
0xcb, 0xfa, 0x7f, 0xdb, 0xe5, 0x82, 0x3d, 0xa9, 0x1e, 0x6f, 0x4f, 0x6a, 0x05, 0x7b, 0xd2, 0x86,
0x8d, 0xbc, 0x31, 0xd4, 0xc6, 0x9c, 0x81, 0x1a, 0xf7, 0xa0, 0xe8, 0x5e, 0x41, 0x36, 0xf4, 0xdf,
0xd6, 0xe1, 0xe2, 0xa7, 0xb4, 0x87, 0x59, 0x7c, 0x2f, 0x74, 0xc7, 0xf3, 0x0f, 0x79, 0xd7, 0xc9,
0x58, 0x31, 0xf7, 0x16, 0x57, 0x9e, 0xf9, 0x16, 0x57, 0x99, 0xf1, 0x16, 0x57, 0x3d, 0xd6, 0x5b,
0x5c, 0xed, 0xc4, 0xde, 0xe2, 0x26, 0x6b, 0xad, 0x7a, 0x61, 0xad, 0xf5, 0x24, 0x53, 0x8f, 0x2c,
0x8a, 0xb0, 0xf9, 0x66, 0x3a, 0x6c, 0x66, 0xee, 0xce, 0xcc, 0x47, 0x84, 0xdc, 0x13, 0x56, 0xe3,
0xa5, 0x4f, 0x58, 0xcd, 0xc9, 0x27, 0xac, 0xe2, 0x57, 0x10, 0x98, 0xfa, 0x0a, 0x72, 0x19, 0x56,
0x82, 0xb1, 0x6b, 0x92, 0x5e, 0x7c, 0x5b, 0xb8, 0x24, 0x97, 0x9d, 0xa5, 0x66, 0x22, 0x62, 0x39,
0x17, 0x11, 0xb1, 0xa7, 0x9e, 0x4a, 0x79, 0xea, 0xff, 0x4e, 0x69, 0xb4, 0x05, 0x9b, 0xd3, 0xf6,
0x44, 0x86, 0xda, 0xf6, 0x1f, 0x01, 0xd6, 0x13, 0xb4, 0xcd, 0xff, 0x5a, 0x26, 0x41, 0x0f, 0x61,
0x6d, 0x4f, 0x3d, 0xa7, 0x47, 0x97, 0xa4, 0x68, 0xd6, 0xbb, 0x44, 0xeb, 0x42, 0x71, 0xa7, 0x14,
0xa2, 0x2f, 0x20, 0x13, 0xce, 0xe7, 0x19, 0x26, 0x4f, 0x20, 0x5f, 0x9f, 0xc1, 0x39, 0x1e, 0xf5,
0x32, 0x11, 0x57, 0x4a, 0xe8, 0x09, 0xac, 0x64, 0x2f, 0xea, 0x51, 0x06, 0x7e, 0x14, 0xbe, 0x1d,
0xb4, 0xf4, 0x59, 0x43, 0x62, 0xfd, 0x9f, 0xf2, 0x0d, 0xcd, 0xdc, 0x49, 0x23, 0x3d, 0x5b, 0x89,
0x17, 0xdd, 0xea, 0xb7, 0xbe, 0x36, 0x73, 0x4c, 0xcc, 0xfd, 0x7d, 0x68, 0x44, 0x77, 0xb8, 0x59,
0x33, 0xe7, 0x6e, 0x76, 0x5b, 0x6b, 0x59, 0x7e, 0xfd, 0x40, 0x5f, 0x40, 0x1f, 0xca, 0xc9, 0x3b,
0x94, 0x16, 0x4c, 0x4e, 0xdd, 0x5c, 0xb6, 0x4e, 0x17, 0xdc, 0x16, 0xea, 0x0b, 0xe8, 0xdb, 0xb0,
0xc4, 0xbf, 0x0e, 0xd5, 0x43, 0xf6, 0x46, 0x5b, 0xfe, 0x6e, 0xa2, 0x1d, 0xfd, 0x6e, 0xa2, 0x7d,
0xdb, 0xa1, 0x6c, 0xdc, 0x2a, 0xb8, 0xce, 0x53, 0x0c, 0x9e, 0xc2, 0xa9, 0x3d, 0xc2, 0x92, 0xea,
0x1b, 0x5d, 0x3a, 0xd6, 0x1d, 0x45, 0x4b, 0xcf, 0x0f, 0x9b, 0x2c, 0xe0, 0xf5, 0x05, 0xf4, 0xab,
0x12, 0x9c, 0xde, 0x23, 0x2c, 0x5f, 0xcf, 0xa2, 0x77, 0x8a, 0x85, 0x4c, 0xa9, 0x7b, 0x5b, 0x0f,
0xe6, 0x8d, 0xae, 0x2c, 0x5b, 0x7d, 0x01, 0xfd, 0xba, 0x04, 0xe7, 0x52, 0x8a, 0xa5, 0x0b, 0x54,
0x74, 0x7d, 0xb6, 0x72, 0x05, 0xc5, 0x6c, 0xeb, 0x93, 0x39, 0x7f, 0x9f, 0x90, 0x62, 0xa9, 0x2f,
0xa0, 0x43, 0xb1, 0x27, 0x09, 0x1e, 0x45, 0x17, 0x0b, 0x81, 0x67, 0x2c, 0x7d, 0x73, 0x5a, 0x77,
0xbc, 0x0f, 0x9f, 0xc0, 0xd2, 0x1e, 0x61, 0x11, 0x30, 0xca, 0x7a, 0x5a, 0x0e, 0xb3, 0x66, 0x43,
0x35, 0x8f, 0xa5, 0x84, 0xc7, 0xac, 0x4b, 0x5e, 0xa9, 0xc3, 0x3f, 0x1b, 0xab, 0x85, 0x28, 0x29,
0xeb, 0x31, 0xc5, 0xd8, 0x41, 0x5f, 0x40, 0xcf, 0x60, 0xa3, 0x38, 0xe9, 0xa1, 0xb7, 0x8e, 0x7d,
0x58, 0xb5, 0xae, 0x1e, 0x67, 0x68, 0x24, 0xf2, 0xa3, 0x9d, 0xbf, 0xbe, 0xd8, 0x2c, 0xfd, 0xed,
0xc5, 0x66, 0xe9, 0x5f, 0x2f, 0x36, 0x4b, 0xdf, 0xbf, 0xf1, 0x92, 0xdf, 0x31, 0xa5, 0x7e, 0x1a,
0x85, 0xa9, 0x65, 0xda, 0x16, 0x71, 0x59, 0xb7, 0x2e, 0xe2, 0xed, 0xc6, 0x7f, 0x02, 0x00, 0x00,
0xff, 0xff, 0xb7, 0x8d, 0xc3, 0x0e, 0x39, 0x25, 0x00, 0x00,
// 2319 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x5a, 0xcb, 0x73, 0x1c, 0x47,
0x19, 0xd7, 0x3e, 0xb5, 0xfb, 0xc9, 0x7a, 0xb5, 0x6d, 0x79, 0xbc, 0xb6, 0x55, 0xca, 0x80, 0x5d,
0x8e, 0x9d, 0xac, 0xca, 0x72, 0x25, 0x06, 0x27, 0x84, 0x52, 0x64, 0x5b, 0x72, 0x6c, 0xd9, 0x62,
0xec, 0x40, 0x19, 0x0c, 0x54, 0xef, 0x6c, 0x6b, 0xb7, 0xa3, 0x79, 0xb4, 0x67, 0x7a, 0x14, 0xe4,
0x2a, 0x4e, 0x50, 0x5c, 0xb8, 0x71, 0xe0, 0xc0, 0x95, 0xbf, 0x81, 0xe2, 0xc8, 0x81, 0xe2, 0x71,
0xa4, 0xb8, 0xc0, 0x0d, 0xca, 0x7f, 0x09, 0xd5, 0x8f, 0x79, 0xee, 0xec, 0x5a, 0x41, 0xb2, 0x02,
0xb9, 0x48, 0xd3, 0x5f, 0x77, 0x7f, 0xdf, 0xd7, 0xdf, 0xa3, 0xfb, 0xf7, 0x75, 0x2f, 0x5c, 0x09,
0x08, 0xf3, 0x43, 0x12, 0xec, 0x93, 0x60, 0x55, 0x7e, 0x52, 0xee, 0x07, 0x07, 0x99, 0xcf, 0x2e,
0x0b, 0x7c, 0xee, 0x23, 0x48, 0x29, 0x9d, 0x87, 0x03, 0xca, 0x87, 0x51, 0xaf, 0x6b, 0xfb, 0xee,
0x2a, 0x0e, 0x06, 0x3e, 0x0b, 0xfc, 0xcf, 0xe4, 0xc7, 0xbb, 0x76, 0x7f, 0x75, 0x7f, 0x6d, 0x95,
0xed, 0x0d, 0x56, 0x31, 0xa3, 0xe1, 0x2a, 0x66, 0xcc, 0xa1, 0x36, 0xe6, 0xd4, 0xf7, 0x56, 0xf7,
0x6f, 0x60, 0x87, 0x0d, 0xf1, 0x8d, 0xd5, 0x01, 0xf1, 0x48, 0x80, 0x39, 0xe9, 0x2b, 0xce, 0x9d,
0x0b, 0x03, 0xdf, 0x1f, 0x38, 0x64, 0x55, 0xb6, 0x7a, 0xd1, 0xee, 0x2a, 0x71, 0x19, 0xd7, 0x62,
0xcd, 0x5f, 0xcd, 0xc2, 0xfc, 0x36, 0xf6, 0xe8, 0x2e, 0x09, 0xb9, 0x45, 0x5e, 0x44, 0x24, 0xe4,
0xe8, 0x39, 0xd4, 0x85, 0x32, 0x46, 0x65, 0xa5, 0x72, 0x75, 0x66, 0x6d, 0xab, 0x9b, 0x6a, 0xd3,
0x8d, 0xb5, 0x91, 0x1f, 0x3f, 0xb6, 0xfb, 0xdd, 0xfd, 0xb5, 0x2e, 0xdb, 0x1b, 0x74, 0x85, 0x36,
0xdd, 0x8c, 0x36, 0xdd, 0x58, 0x9b, 0xae, 0x95, 0x2c, 0xcb, 0x92, 0x5c, 0x51, 0x07, 0x5a, 0x01,
0xd9, 0xa7, 0x21, 0xf5, 0x3d, 0xa3, 0xba, 0x52, 0xb9, 0xda, 0xb6, 0x92, 0x36, 0x32, 0x60, 0xda,
0xf3, 0x37, 0xb0, 0x3d, 0x24, 0x46, 0x6d, 0xa5, 0x72, 0xb5, 0x65, 0xc5, 0x4d, 0xb4, 0x02, 0x33,
0x98, 0xb1, 0x87, 0xb8, 0x47, 0x9c, 0x07, 0xe4, 0xc0, 0xa8, 0xcb, 0x89, 0x59, 0x92, 0x98, 0x8b,
0x19, 0x7b, 0x84, 0x5d, 0x62, 0x34, 0x64, 0x6f, 0xdc, 0x44, 0x17, 0xa1, 0xed, 0x61, 0x97, 0x84,
0x0c, 0xdb, 0xc4, 0x68, 0xc9, 0xbe, 0x94, 0x80, 0x7e, 0x0a, 0x8b, 0x19, 0xc5, 0x9f, 0xf8, 0x51,
0x60, 0x13, 0x03, 0xe4, 0xd2, 0x1f, 0x1f, 0x6d, 0xe9, 0xeb, 0x45, 0xb6, 0xd6, 0xa8, 0x24, 0xf4,
0x23, 0x68, 0x48, 0xcf, 0x1b, 0x33, 0x2b, 0xb5, 0x63, 0xb5, 0xb6, 0x62, 0x8b, 0x3c, 0x98, 0x66,
0x4e, 0x34, 0xa0, 0x5e, 0x68, 0x9c, 0x92, 0x12, 0x9e, 0x1e, 0x4d, 0xc2, 0x86, 0xef, 0xed, 0xd2,
0xc1, 0x36, 0xf6, 0xf0, 0x80, 0xb8, 0xc4, 0xe3, 0x3b, 0x92, 0xb9, 0x15, 0x0b, 0x41, 0x2f, 0x61,
0x61, 0x2f, 0x0a, 0xb9, 0xef, 0xd2, 0x97, 0xe4, 0x31, 0x13, 0x73, 0x43, 0x63, 0x56, 0x5a, 0xf3,
0xd1, 0xd1, 0x04, 0x3f, 0x28, 0x70, 0xb5, 0x46, 0xe4, 0x88, 0x20, 0xd9, 0x8b, 0x7a, 0xe4, 0xbb,
0x24, 0x90, 0xd1, 0x35, 0xa7, 0x82, 0x24, 0x43, 0x52, 0x61, 0x44, 0x75, 0x2b, 0x34, 0xe6, 0x57,
0x6a, 0x2a, 0x8c, 0x12, 0x12, 0xba, 0x0a, 0xf3, 0xfb, 0x24, 0xa0, 0xbb, 0x07, 0x4f, 0xe8, 0xc0,
0xc3, 0x3c, 0x0a, 0x88, 0xb1, 0x20, 0x43, 0xb1, 0x48, 0x46, 0x2e, 0xcc, 0x0e, 0x89, 0xe3, 0x0a,
0x93, 0x6f, 0x04, 0xa4, 0x1f, 0x1a, 0x8b, 0xd2, 0xbe, 0x9b, 0x47, 0xf7, 0xa0, 0x64, 0x67, 0xe5,
0xb9, 0x0b, 0xc5, 0x3c, 0xdf, 0xd2, 0x99, 0xa2, 0x72, 0x04, 0x29, 0xc5, 0x0a, 0x64, 0x74, 0x05,
0xe6, 0x78, 0x80, 0xed, 0x3d, 0xea, 0x0d, 0xb6, 0x09, 0x1f, 0xfa, 0x7d, 0xe3, 0xb4, 0xb4, 0x44,
0x81, 0x8a, 0x6c, 0x40, 0xc4, 0xc3, 0x3d, 0x87, 0xf4, 0x55, 0x2c, 0x3e, 0x3d, 0x60, 0x24, 0x34,
0xce, 0xc8, 0x55, 0xdc, 0xec, 0x66, 0x76, 0xa8, 0xc2, 0x06, 0xd1, 0xbd, 0x3b, 0x32, 0xeb, 0xae,
0xc7, 0x83, 0x03, 0xab, 0x84, 0x1d, 0xda, 0x83, 0x19, 0xb1, 0x8e, 0x38, 0x14, 0xce, 0xca, 0x50,
0xb8, 0x7f, 0x34, 0x1b, 0x6d, 0xa5, 0x0c, 0xad, 0x2c, 0x77, 0xd4, 0x05, 0x34, 0xc4, 0xe1, 0x76,
0xe4, 0x70, 0xca, 0x1c, 0xa2, 0xd4, 0x08, 0x8d, 0x25, 0x69, 0xa6, 0x92, 0x1e, 0xf4, 0x00, 0x20,
0x20, 0xbb, 0xf1, 0xb8, 0x73, 0x72, 0xe5, 0xd7, 0x27, 0xad, 0xdc, 0x4a, 0x46, 0xab, 0x15, 0x67,
0xa6, 0x0b, 0xe1, 0x62, 0x19, 0xc4, 0xe6, 0x3a, 0xdb, 0x65, 0x5a, 0x1b, 0x32, 0xc4, 0x4a, 0x7a,
0x44, 0x2c, 0x6a, 0xaa, 0xdc, 0xb4, 0xce, 0xab, 0x68, 0xcd, 0x90, 0x84, 0x23, 0xa9, 0x17, 0x72,
0xec, 0x38, 0xd2, 0x00, 0xf7, 0xef, 0x18, 0x17, 0x94, 0x23, 0xf3, 0xd4, 0xce, 0x5d, 0x38, 0x37,
0xc6, 0x25, 0x68, 0x01, 0x6a, 0x7b, 0xe4, 0x40, 0x6e, 0xe5, 0x6d, 0x4b, 0x7c, 0xa2, 0x33, 0xd0,
0xd8, 0xc7, 0x4e, 0x44, 0xe4, 0xe6, 0xdb, 0xb2, 0x54, 0xe3, 0x76, 0xf5, 0x1b, 0x95, 0xce, 0x2f,
0x2a, 0x30, 0x5f, 0x58, 0x60, 0xc9, 0xfc, 0x1f, 0x66, 0xe7, 0x1f, 0x43, 0xb8, 0xef, 0x3e, 0xc5,
0xc1, 0x80, 0xf0, 0x8c, 0x22, 0xe6, 0xdf, 0x2b, 0x60, 0x14, 0x2c, 0xff, 0x3d, 0xca, 0x87, 0xf7,
0xa8, 0x43, 0x42, 0x74, 0x0b, 0xa6, 0x03, 0x45, 0xd3, 0x07, 0xd4, 0x85, 0x09, 0x0e, 0xdb, 0x9a,
0xb2, 0xe2, 0xd1, 0xe8, 0x23, 0x68, 0xb9, 0x84, 0xe3, 0x3e, 0xe6, 0x58, 0xeb, 0xbe, 0x52, 0x36,
0x53, 0x48, 0xd9, 0xd6, 0xe3, 0xb6, 0xa6, 0xac, 0x64, 0x0e, 0x7a, 0x0f, 0x1a, 0xf6, 0x30, 0xf2,
0xf6, 0xe4, 0xd1, 0x34, 0xb3, 0x76, 0x69, 0xdc, 0xe4, 0x0d, 0x31, 0x68, 0x6b, 0xca, 0x52, 0xa3,
0x3f, 0x6e, 0x42, 0x9d, 0xe1, 0x80, 0x9b, 0xf7, 0xe0, 0x4c, 0x99, 0x08, 0x71, 0x1e, 0xda, 0x43,
0x62, 0xef, 0x85, 0x91, 0xab, 0xcd, 0x9c, 0xb4, 0x11, 0x82, 0x7a, 0x48, 0x5f, 0x2a, 0x53, 0xd7,
0x2c, 0xf9, 0x6d, 0xbe, 0x0d, 0x8b, 0x23, 0xd2, 0x84, 0x53, 0x95, 0x6e, 0x82, 0xc3, 0x29, 0x2d,
0xda, 0x8c, 0xe0, 0xec, 0x53, 0x69, 0x8b, 0xe4, 0x50, 0x38, 0x89, 0x13, 0xde, 0xdc, 0x82, 0xa5,
0xa2, 0xd8, 0x90, 0xf9, 0x5e, 0x48, 0x44, 0x8a, 0xc8, 0x5d, 0x94, 0x92, 0x7e, 0xda, 0x2b, 0xb5,
0x68, 0x59, 0x25, 0x3d, 0xe6, 0x6f, 0xab, 0xb0, 0x64, 0x91, 0xd0, 0x77, 0xf6, 0x49, 0xbc, 0xc5,
0x9d, 0x0c, 0x48, 0xf9, 0x01, 0xd4, 0x30, 0x63, 0x3a, 0x4c, 0xee, 0x1f, 0x1b, 0x0c, 0xb0, 0x04,
0x57, 0xf4, 0x0e, 0x2c, 0x62, 0xb7, 0x47, 0x07, 0x91, 0x1f, 0x85, 0xf1, 0xb2, 0x64, 0x50, 0xb5,
0xad, 0xd1, 0x0e, 0xb1, 0x4d, 0x84, 0x32, 0x23, 0xef, 0x7b, 0x7d, 0xf2, 0x13, 0x89, 0x7c, 0x6a,
0x56, 0x96, 0x64, 0xda, 0x70, 0x6e, 0xc4, 0x48, 0xda, 0xe0, 0x59, 0xb0, 0x55, 0x29, 0x80, 0xad,
0x52, 0x35, 0xaa, 0x63, 0xd4, 0x30, 0xff, 0x5c, 0x81, 0x85, 0x34, 0xb9, 0x34, 0xfb, 0x8b, 0xd0,
0x76, 0x35, 0x2d, 0x34, 0x2a, 0x72, 0xa7, 0x4b, 0x09, 0x79, 0xdc, 0x55, 0x2d, 0xe2, 0xae, 0x25,
0x68, 0x2a, 0x58, 0xac, 0x97, 0xae, 0x5b, 0x39, 0x95, 0xeb, 0x05, 0x95, 0x97, 0x01, 0xc2, 0x64,
0x87, 0x33, 0x9a, 0xb2, 0x37, 0x43, 0x41, 0x26, 0x9c, 0x52, 0xa7, 0xb4, 0x45, 0xc2, 0xc8, 0xe1,
0xc6, 0xb4, 0x1c, 0x91, 0xa3, 0x99, 0x3e, 0xcc, 0x3f, 0xa4, 0x62, 0x0d, 0xbb, 0xe1, 0xc9, 0xa4,
0xc3, 0xfb, 0x50, 0x17, 0xc2, 0xc4, 0xc2, 0x7a, 0x01, 0xf6, 0xec, 0x21, 0x89, 0x6d, 0x95, 0xb4,
0x45, 0xa2, 0x73, 0x3c, 0x08, 0x8d, 0xaa, 0xa4, 0xcb, 0x6f, 0xf3, 0xf7, 0x55, 0xa5, 0xe9, 0x3a,
0x63, 0xe1, 0x97, 0x0f, 0xcd, 0xcb, 0xc1, 0x42, 0x6d, 0x14, 0x2c, 0x14, 0x54, 0xfe, 0x22, 0x60,
0xe1, 0x98, 0x0e, 0x32, 0x33, 0x82, 0xe9, 0x75, 0xc6, 0x84, 0x22, 0xe8, 0x06, 0xd4, 0x31, 0x63,
0xca, 0xe0, 0x85, 0x3d, 0x5b, 0x0f, 0x11, 0xff, 0xb5, 0x4a, 0x72, 0x68, 0xe7, 0x16, 0xb4, 0x13,
0xd2, 0xeb, 0xc4, 0xb6, 0xb3, 0x62, 0x57, 0x00, 0x14, 0x1a, 0xbe, 0xef, 0xed, 0xfa, 0xc2, 0xa5,
0x22, 0xd8, 0xf5, 0x54, 0xf9, 0x6d, 0xde, 0x8e, 0x47, 0x48, 0xdd, 0xde, 0x81, 0x06, 0xe5, 0xc4,
0x8d, 0x95, 0x5b, 0xca, 0x2a, 0x97, 0x32, 0xb2, 0xd4, 0x20, 0xf3, 0x2f, 0x2d, 0x38, 0x2f, 0x3c,
0xf6, 0x44, 0xa6, 0xc9, 0x3a, 0x63, 0x77, 0x08, 0xc7, 0xd4, 0x09, 0xbf, 0x13, 0x91, 0xe0, 0xe0,
0x0d, 0x07, 0xc6, 0x00, 0x9a, 0x2a, 0xcb, 0xf4, 0x8e, 0x78, 0xec, 0x85, 0x91, 0x66, 0x9f, 0x56,
0x43, 0xb5, 0x37, 0x53, 0x0d, 0x95, 0x55, 0x27, 0xf5, 0x13, 0xaa, 0x4e, 0xc6, 0x17, 0xa8, 0x99,
0xb2, 0xb7, 0x99, 0x2f, 0x7b, 0x4b, 0x40, 0xff, 0xf4, 0x61, 0x41, 0x7f, 0xab, 0x14, 0xf4, 0xbb,
0xa5, 0x79, 0xdc, 0x96, 0xe6, 0xfe, 0x56, 0x36, 0x02, 0xc7, 0xc6, 0xda, 0x51, 0xe0, 0x3f, 0xbc,
0x51, 0xf8, 0xff, 0x69, 0x0e, 0xce, 0xab, 0x82, 0xfa, 0xbd, 0xc3, 0xad, 0x69, 0x02, 0xb0, 0xff,
0xca, 0xc1, 0xeb, 0x9f, 0x4b, 0x54, 0xc5, 0xfc, 0xd4, 0x06, 0xc9, 0x81, 0x2e, 0xce, 0x21, 0x71,
0xb4, 0xea, 0x4d, 0x4b, 0x7c, 0xa3, 0xeb, 0x50, 0x17, 0x46, 0xd6, 0xb0, 0xf7, 0x5c, 0xd6, 0x9e,
0xc2, 0x13, 0xeb, 0x8c, 0x3d, 0x61, 0xc4, 0xb6, 0xe4, 0x20, 0x74, 0x1b, 0xda, 0x49, 0xe0, 0xeb,
0xcc, 0xba, 0x98, 0x9d, 0x91, 0xe4, 0x49, 0x3c, 0x2d, 0x1d, 0x2e, 0xe6, 0xf6, 0x69, 0x40, 0x6c,
0x09, 0x0a, 0x1b, 0xa3, 0x73, 0xef, 0xc4, 0x9d, 0xc9, 0xdc, 0x64, 0x38, 0xba, 0x01, 0x4d, 0x75,
0x03, 0x21, 0x33, 0x68, 0x66, 0xed, 0xfc, 0xe8, 0x66, 0x1a, 0xcf, 0xd2, 0x03, 0xcd, 0x3f, 0x55,
0xe0, 0xad, 0x34, 0x20, 0xe2, 0x6c, 0x8a, 0x71, 0xf9, 0x97, 0x7f, 0xe2, 0x5e, 0x81, 0x39, 0x59,
0x08, 0xa4, 0x17, 0x11, 0xea, 0x4e, 0xac, 0x40, 0x35, 0x7f, 0x57, 0x81, 0xcb, 0xa3, 0xeb, 0xd8,
0x18, 0xe2, 0x80, 0x27, 0xee, 0x3d, 0x89, 0xb5, 0xc4, 0x07, 0x5e, 0x35, 0x3d, 0xf0, 0x72, 0xeb,
0xab, 0xe5, 0xd7, 0x67, 0xfe, 0xa1, 0x0a, 0x33, 0x99, 0x00, 0x2a, 0x3b, 0x30, 0x05, 0xe0, 0x93,
0x71, 0x2b, 0x4b, 0x3f, 0x79, 0x28, 0xb4, 0xad, 0x0c, 0x05, 0xed, 0x01, 0x30, 0x1c, 0x60, 0x97,
0x70, 0x12, 0x88, 0x9d, 0x5c, 0x64, 0xfc, 0x83, 0xa3, 0xef, 0x2e, 0x3b, 0x31, 0x4f, 0x2b, 0xc3,
0x5e, 0x20, 0x56, 0x29, 0x3a, 0xd4, 0xfb, 0xb7, 0x6e, 0xa1, 0xcf, 0x61, 0x6e, 0x97, 0x3a, 0x64,
0x27, 0x55, 0xa4, 0x29, 0x15, 0x79, 0x7c, 0x74, 0x45, 0xee, 0x65, 0xf9, 0x5a, 0x05, 0x31, 0xe6,
0x35, 0x58, 0x28, 0xe6, 0x93, 0x50, 0x92, 0xba, 0x78, 0x90, 0x58, 0x4b, 0xb7, 0x4c, 0x04, 0x0b,
0xc5, 0xfc, 0x31, 0xff, 0x55, 0x85, 0xb3, 0x09, 0xbb, 0x75, 0xcf, 0xf3, 0x23, 0xcf, 0x96, 0x97,
0x7a, 0xa5, 0xbe, 0x38, 0x03, 0x0d, 0x4e, 0xb9, 0x93, 0x00, 0x1f, 0xd9, 0x10, 0x67, 0x17, 0xf7,
0x7d, 0x87, 0x53, 0xa6, 0x1d, 0x1c, 0x37, 0x95, 0xef, 0x5f, 0x44, 0x34, 0x20, 0x7d, 0xb9, 0x13,
0xb4, 0xac, 0xa4, 0x2d, 0xfa, 0x04, 0xaa, 0x91, 0x30, 0x5e, 0x19, 0x33, 0x69, 0xcb, 0xb8, 0xf7,
0x1d, 0x87, 0xd8, 0xc2, 0x1c, 0x19, 0xa0, 0x5f, 0xa0, 0xca, 0x02, 0x82, 0x07, 0xd4, 0x1b, 0x68,
0x98, 0xaf, 0x5b, 0x42, 0x4f, 0x1c, 0x04, 0xf8, 0xc0, 0x68, 0x49, 0x03, 0xa8, 0x06, 0xfa, 0x10,
0x6a, 0x2e, 0x66, 0xfa, 0xa0, 0xbb, 0x96, 0xdb, 0x1d, 0xca, 0x2c, 0xd0, 0xdd, 0xc6, 0x4c, 0x9d,
0x04, 0x62, 0x5a, 0xe7, 0x7d, 0x68, 0xc5, 0x84, 0x2f, 0x04, 0x09, 0x3f, 0x83, 0xd9, 0xdc, 0xe6,
0x83, 0x9e, 0xc1, 0x52, 0x1a, 0x51, 0x59, 0x81, 0x1a, 0x04, 0xbe, 0xf5, 0x5a, 0xcd, 0xac, 0x31,
0x0c, 0xcc, 0x17, 0xb0, 0x28, 0x42, 0x46, 0x26, 0xfe, 0x09, 0x95, 0x36, 0x1f, 0x40, 0x3b, 0x11,
0x59, 0x1a, 0x33, 0x1d, 0x68, 0xed, 0xc7, 0x97, 0xad, 0xaa, 0xb6, 0x49, 0xda, 0xe6, 0x3a, 0xa0,
0xac, 0xbe, 0xfa, 0x04, 0xba, 0x9e, 0x07, 0xc5, 0x67, 0x8b, 0xc7, 0x8d, 0x1c, 0x1e, 0x63, 0xe2,
0x7f, 0x54, 0x61, 0x7e, 0x93, 0xca, 0x7b, 0x90, 0x13, 0xda, 0xe4, 0xae, 0xc1, 0x42, 0x18, 0xf5,
0x5c, 0xbf, 0x1f, 0x39, 0x44, 0x83, 0x02, 0x7d, 0xd2, 0x8f, 0xd0, 0x27, 0x6d, 0x7e, 0xc2, 0x58,
0x0c, 0xf3, 0xa1, 0xae, 0x70, 0xe5, 0x37, 0xfa, 0x10, 0xce, 0x3f, 0x22, 0x9f, 0xeb, 0xf5, 0x6c,
0x3a, 0x7e, 0xaf, 0x47, 0xbd, 0x41, 0x2c, 0xa4, 0x21, 0x85, 0x8c, 0x1f, 0x50, 0x06, 0x15, 0x9b,
0xe5, 0x50, 0x31, 0xa9, 0x92, 0x37, 0x7c, 0xd7, 0xa5, 0x5c, 0x23, 0xca, 0x1c, 0xcd, 0xfc, 0x59,
0x05, 0x16, 0x52, 0xcb, 0x6a, 0xdf, 0xdc, 0x52, 0x39, 0xa4, 0x3c, 0x73, 0x39, 0xeb, 0x99, 0xe2,
0xd0, 0xff, 0x3e, 0x7d, 0x4e, 0x65, 0xd3, 0xe7, 0x97, 0x55, 0x38, 0xbb, 0x49, 0x79, 0xbc, 0x71,
0xd1, 0xff, 0x37, 0x2f, 0x97, 0xf8, 0xa4, 0x7e, 0x38, 0x9f, 0x34, 0x4a, 0x7c, 0xd2, 0x85, 0xa5,
0xa2, 0x31, 0xb4, 0x63, 0xce, 0x40, 0x43, 0x44, 0x50, 0x7c, 0xaf, 0xa0, 0x1a, 0xe6, 0x3f, 0x9b,
0x70, 0xe9, 0x53, 0xd6, 0xc7, 0x3c, 0xb9, 0x17, 0xba, 0xe7, 0x07, 0x3b, 0xa2, 0xeb, 0x64, 0xac,
0x58, 0x78, 0xb3, 0xab, 0x4e, 0x7c, 0xb3, 0xab, 0x4d, 0x78, 0xb3, 0xab, 0x1f, 0xea, 0xcd, 0xae,
0x71, 0x62, 0x6f, 0x76, 0xa3, 0xb5, 0x56, 0xb3, 0xb4, 0xd6, 0x7a, 0x96, 0xab, 0x47, 0xa6, 0x65,
0xda, 0x7c, 0x33, 0x9b, 0x36, 0x13, 0xbd, 0x33, 0xf1, 0xb1, 0xa1, 0xf0, 0xd4, 0xd5, 0x7a, 0xed,
0x53, 0x57, 0x7b, 0xf4, 0xa9, 0xab, 0xfc, 0xb5, 0x04, 0xc6, 0xbe, 0x96, 0x5c, 0x81, 0xb9, 0xf0,
0xc0, 0xb3, 0x49, 0x3f, 0xb9, 0x2d, 0x9c, 0x51, 0xcb, 0xce, 0x53, 0x73, 0x19, 0x71, 0xaa, 0x90,
0x11, 0x49, 0xa4, 0xce, 0x66, 0x22, 0xb5, 0xe4, 0xa1, 0x63, 0xbe, 0xf4, 0xa1, 0xe3, 0x7f, 0xa6,
0x84, 0x5a, 0x81, 0xe5, 0x71, 0xbe, 0x53, 0x29, 0xb9, 0xf6, 0x47, 0x80, 0xc5, 0x14, 0x95, 0x8b,
0xbf, 0xd4, 0x26, 0xe8, 0x31, 0x2c, 0x6c, 0xea, 0xe7, 0xf9, 0xf8, 0x32, 0x15, 0x4d, 0x7a, 0xbf,
0xe8, 0x5c, 0x2c, 0xef, 0x54, 0x42, 0xcc, 0x29, 0x64, 0xc3, 0xf9, 0x22, 0xc3, 0xf4, 0xa9, 0xe4,
0xeb, 0x13, 0x38, 0x27, 0xa3, 0x5e, 0x27, 0xe2, 0x6a, 0x05, 0x3d, 0x83, 0xb9, 0xfc, 0x85, 0x3e,
0xca, 0xc1, 0x94, 0xd2, 0x37, 0x86, 0x8e, 0x39, 0x69, 0x48, 0xa2, 0xff, 0x73, 0xe1, 0xd0, 0xdc,
0xdd, 0x35, 0x32, 0xf3, 0x15, 0x7b, 0xd9, 0xed, 0x7f, 0xe7, 0x6b, 0x13, 0xc7, 0x24, 0xdc, 0x3f,
0x80, 0x56, 0x7c, 0xd7, 0x9b, 0x37, 0x73, 0xe1, 0x06, 0xb8, 0xb3, 0x90, 0xe7, 0xb7, 0x1b, 0x9a,
0x53, 0xe8, 0x23, 0x35, 0x79, 0x9d, 0xb1, 0x92, 0xc9, 0x99, 0x1b, 0xce, 0xce, 0xe9, 0x92, 0x5b,
0x45, 0x73, 0x0a, 0x7d, 0x1b, 0x66, 0xc4, 0xd7, 0x8e, 0x7e, 0x18, 0x5f, 0xea, 0xaa, 0xdf, 0x61,
0x74, 0xe3, 0xdf, 0x61, 0x74, 0xef, 0xba, 0x8c, 0x1f, 0x74, 0x4a, 0xae, 0xfd, 0x34, 0x83, 0xe7,
0x30, 0xbb, 0x49, 0x78, 0x5a, 0xa5, 0xa3, 0xcb, 0x87, 0xba, 0xcb, 0xe8, 0x98, 0xc5, 0x61, 0xa3,
0x85, 0xbe, 0x39, 0x85, 0x7e, 0x5d, 0x81, 0xd3, 0x9b, 0x84, 0x17, 0xeb, 0x5e, 0xf4, 0x6e, 0xb9,
0x90, 0x31, 0xf5, 0x71, 0xe7, 0xd1, 0x51, 0xb3, 0x2b, 0xcf, 0xd6, 0x9c, 0x42, 0xbf, 0xa9, 0xc0,
0xb9, 0x8c, 0x62, 0xd9, 0x42, 0x16, 0xdd, 0x98, 0xac, 0x5c, 0x49, 0xd1, 0xdb, 0xf9, 0xe4, 0x88,
0xbf, 0x77, 0xc8, 0xb0, 0x34, 0xa7, 0xd0, 0x8e, 0xf4, 0x49, 0x8a, 0x5b, 0xd1, 0xa5, 0x52, 0x80,
0x9a, 0x48, 0x5f, 0x1e, 0xd7, 0x9d, 0xf8, 0xe1, 0x13, 0x98, 0xd9, 0x24, 0x3c, 0x06, 0x50, 0xf9,
0x48, 0x2b, 0x60, 0xdb, 0x7c, 0xaa, 0x16, 0x31, 0x97, 0x8c, 0x98, 0x45, 0xc5, 0x2b, 0x03, 0x12,
0xf2, 0xb9, 0x5a, 0x8a, 0xa6, 0xf2, 0x11, 0x53, 0x8e, 0x31, 0xcc, 0x29, 0xf4, 0x02, 0x96, 0xca,
0x37, 0x3d, 0xf4, 0xf6, 0xa1, 0x0f, 0xb5, 0xce, 0xb5, 0xc3, 0x0c, 0x8d, 0x45, 0x7e, 0xbc, 0xfe,
0xd7, 0x57, 0xcb, 0x95, 0xbf, 0xbd, 0x5a, 0xae, 0xfc, 0xfb, 0xd5, 0x72, 0xe5, 0xfb, 0x37, 0x5f,
0xf3, 0xbb, 0xa8, 0xcc, 0x4f, 0xad, 0x30, 0xa3, 0xb6, 0x43, 0x89, 0xc7, 0x7b, 0x4d, 0x99, 0x6f,
0x37, 0xff, 0x13, 0x00, 0x00, 0xff, 0xff, 0xf0, 0xf8, 0x9b, 0x3a, 0x89, 0x25, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@@ -3196,6 +3214,15 @@ func (m *ManifestRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if len(m.InstallationID) > 0 {
i -= len(m.InstallationID)
copy(dAtA[i:], m.InstallationID)
i = encodeVarintRepository(dAtA, i, uint64(len(m.InstallationID)))
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0xda
}
if len(m.ProjectName) > 0 {
i -= len(m.ProjectName)
copy(dAtA[i:], m.ProjectName)
@@ -5211,6 +5238,13 @@ func (m *UpdateRevisionForPathsRequest) MarshalToSizedBuffer(dAtA []byte) (int,
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if len(m.InstallationID) > 0 {
i -= len(m.InstallationID)
copy(dAtA[i:], m.InstallationID)
i = encodeVarintRepository(dAtA, i, uint64(len(m.InstallationID)))
i--
dAtA[i] = 0x7a
}
if len(m.Paths) > 0 {
for iNdEx := len(m.Paths) - 1; iNdEx >= 0; iNdEx-- {
i -= len(m.Paths[iNdEx])
@@ -5492,6 +5526,10 @@ func (m *ManifestRequest) Size() (n int) {
if l > 0 {
n += 2 + l + sovRepository(uint64(l))
}
l = len(m.InstallationID)
if l > 0 {
n += 2 + l + sovRepository(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@@ -6342,6 +6380,10 @@ func (m *UpdateRevisionForPathsRequest) Size() (n int) {
n += 1 + l + sovRepository(uint64(l))
}
}
l = len(m.InstallationID)
if l > 0 {
n += 1 + l + sovRepository(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@@ -7253,6 +7295,38 @@ func (m *ManifestRequest) Unmarshal(dAtA []byte) error {
}
m.ProjectName = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 27:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field InstallationID", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowRepository
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthRepository
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthRepository
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.InstallationID = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipRepository(dAtA[iNdEx:])
@@ -12537,6 +12611,38 @@ func (m *UpdateRevisionForPathsRequest) Unmarshal(dAtA []byte) error {
}
m.Paths = append(m.Paths, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
case 15:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field InstallationID", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowRepository
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthRepository
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthRepository
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.InstallationID = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipRepository(dAtA[iNdEx:])

View File

@@ -290,13 +290,17 @@ func (c *Cache) UnlockGitReferences(repo string, lockId string) error {
// refSourceCommitSHAs is a list of resolved revisions for each ref source. This allows us to invalidate the cache
// when someone pushes a commit to a source which is referenced from the main source (the one referred to by `revision`).
func manifestCacheKey(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, namespace string, trackingMethod string, appLabelKey string, appName string, info ClusterRuntimeInfo, refSourceCommitSHAs ResolvedRevisions) string {
func manifestCacheKey(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, namespace string, trackingMethod string, appLabelKey string, appName string, info ClusterRuntimeInfo, refSourceCommitSHAs ResolvedRevisions, installationID string) string {
// TODO: this function is getting unwieldy. We should probably consolidate some of this stuff into a struct. For
// example, revision could be part of ResolvedRevisions. And srcRefs is probably redundant now that
// refSourceCommitSHAs has been added. We don't need to know the _target_ revisions of the referenced sources
// when the _resolved_ revisions are already part of the key.
trackingKey := trackingKey(appLabelKey, trackingMethod)
return fmt.Sprintf("mfst|%s|%s|%s|%s|%d", trackingKey, appName, revision, namespace, appSourceKey(appSrc, srcRefs, refSourceCommitSHAs)+clusterRuntimeInfoKey(info))
key := fmt.Sprintf("mfst|%s|%s|%s|%s|%d", trackingKey, appName, revision, namespace, appSourceKey(appSrc, srcRefs, refSourceCommitSHAs)+clusterRuntimeInfoKey(info))
if installationID != "" {
key = fmt.Sprintf("%s|%s", key, installationID)
}
return key
}
func trackingKey(appLabelKey string, trackingMethod string) string {
@@ -323,14 +327,14 @@ func LogDebugManifestCacheKeyFields(message string, reason string, revision stri
}
}
func (c *Cache) SetNewRevisionManifests(newRevision string, revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace string, trackingMethod string, appLabelKey string, appName string, refSourceCommitSHAs ResolvedRevisions) error {
oldKey := manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs)
newKey := manifestCacheKey(newRevision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs)
func (c *Cache) SetNewRevisionManifests(newRevision string, revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace string, trackingMethod string, appLabelKey string, appName string, refSourceCommitSHAs ResolvedRevisions, installationID string) error {
oldKey := manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs, installationID)
newKey := manifestCacheKey(newRevision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs, installationID)
return c.cache.RenameItem(oldKey, newKey, c.repoCacheExpiration)
}
func (c *Cache) GetManifests(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace string, trackingMethod string, appLabelKey string, appName string, res *CachedManifestResponse, refSourceCommitSHAs ResolvedRevisions) error {
err := c.cache.GetItem(manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs), res)
func (c *Cache) GetManifests(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace string, trackingMethod string, appLabelKey string, appName string, res *CachedManifestResponse, refSourceCommitSHAs ResolvedRevisions, installationID string) error {
err := c.cache.GetItem(manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs, installationID), res)
if err != nil {
return err
}
@@ -346,7 +350,7 @@ func (c *Cache) GetManifests(revision string, appSrc *appv1.ApplicationSource, s
LogDebugManifestCacheKeyFields("deleting manifests cache", "manifest hash did not match or cached response is empty", revision, appSrc, srcRefs, clusterInfo, namespace, trackingMethod, appLabelKey, appName, refSourceCommitSHAs)
err = c.DeleteManifests(revision, appSrc, srcRefs, clusterInfo, namespace, trackingMethod, appLabelKey, appName, refSourceCommitSHAs)
err = c.DeleteManifests(revision, appSrc, srcRefs, clusterInfo, namespace, trackingMethod, appLabelKey, appName, refSourceCommitSHAs, installationID)
if err != nil {
return fmt.Errorf("Unable to delete manifest after hash mismatch, %w", err)
}
@@ -366,7 +370,7 @@ func (c *Cache) GetManifests(revision string, appSrc *appv1.ApplicationSource, s
return nil
}
func (c *Cache) SetManifests(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace string, trackingMethod string, appLabelKey string, appName string, res *CachedManifestResponse, refSourceCommitSHAs ResolvedRevisions) error {
func (c *Cache) SetManifests(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace string, trackingMethod string, appLabelKey string, appName string, res *CachedManifestResponse, refSourceCommitSHAs ResolvedRevisions, installationID string) error {
// Generate and apply the cache entry hash, before writing
if res != nil {
res = res.shallowCopy()
@@ -378,7 +382,7 @@ func (c *Cache) SetManifests(revision string, appSrc *appv1.ApplicationSource, s
}
return c.cache.SetItem(
manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs),
manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs, installationID),
res,
&cacheutil.CacheActionOpts{
Expiration: c.repoCacheExpiration,
@@ -386,9 +390,9 @@ func (c *Cache) SetManifests(revision string, appSrc *appv1.ApplicationSource, s
})
}
func (c *Cache) DeleteManifests(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace, trackingMethod, appLabelKey, appName string, refSourceCommitSHAs ResolvedRevisions) error {
func (c *Cache) DeleteManifests(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace, trackingMethod, appLabelKey, appName string, refSourceCommitSHAs ResolvedRevisions, installationID string) error {
return c.cache.SetItem(
manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs),
manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs, installationID),
"",
&cacheutil.CacheActionOpts{Delete: true})
}

View File

@@ -95,43 +95,43 @@ func TestCache_GetManifests(t *testing.T) {
// cache miss
q := &apiclient.ManifestRequest{}
value := &CachedManifestResponse{}
err := cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil)
err := cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil, "")
assert.Equal(t, ErrCacheMiss, err)
// populate cache
res := &CachedManifestResponse{ManifestResponse: &apiclient.ManifestResponse{SourceType: "my-source-type"}}
err = cache.SetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", res, nil)
err = cache.SetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", res, nil, "")
require.NoError(t, err)
t.Run("expect cache miss because of changed revision", func(t *testing.T) {
err = cache.GetManifests("other-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil)
err = cache.GetManifests("other-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil, "")
assert.Equal(t, ErrCacheMiss, err)
})
t.Run("expect cache miss because of changed path", func(t *testing.T) {
err = cache.GetManifests("my-revision", &ApplicationSource{Path: "other-path"}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil)
err = cache.GetManifests("my-revision", &ApplicationSource{Path: "other-path"}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil, "")
assert.Equal(t, ErrCacheMiss, err)
})
t.Run("expect cache miss because of changed namespace", func(t *testing.T) {
err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "other-namespace", "", "my-app-label-key", "my-app-label-value", value, nil)
err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "other-namespace", "", "my-app-label-key", "my-app-label-value", value, nil, "")
assert.Equal(t, ErrCacheMiss, err)
})
t.Run("expect cache miss because of changed app label key", func(t *testing.T) {
err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "other-app-label-key", "my-app-label-value", value, nil)
err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "other-app-label-key", "my-app-label-value", value, nil, "")
assert.Equal(t, ErrCacheMiss, err)
})
t.Run("expect cache miss because of changed app label value", func(t *testing.T) {
err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "other-app-label-value", value, nil)
err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "other-app-label-value", value, nil, "")
assert.Equal(t, ErrCacheMiss, err)
})
t.Run("expect cache miss because of changed referenced source", func(t *testing.T) {
err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "other-app-label-value", value, map[string]string{"my-referenced-source": "my-referenced-revision"})
err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "other-app-label-value", value, map[string]string{"my-referenced-source": "my-referenced-revision"}, "")
assert.Equal(t, ErrCacheMiss, err)
})
t.Run("expect cache hit", func(t *testing.T) {
err = cache.SetManifests(
"my-revision1", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value",
&CachedManifestResponse{ManifestResponse: &apiclient.ManifestResponse{SourceType: "my-source-type", Revision: "my-revision2"}}, nil)
&CachedManifestResponse{ManifestResponse: &apiclient.ManifestResponse{SourceType: "my-source-type", Revision: "my-revision2"}}, nil, "")
require.NoError(t, err)
err = cache.GetManifests("my-revision1", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil)
err = cache.GetManifests("my-revision1", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil, "")
require.NoError(t, err)
assert.Equal(t, "my-source-type", value.ManifestResponse.SourceType)
@@ -200,7 +200,7 @@ func TestCachedManifestResponse_HashBehavior(t *testing.T) {
NumberOfConsecutiveFailures: 0,
}
q := &apiclient.ManifestRequest{}
err := repoCache.SetManifests(response.Revision, appSrc, q.RefSources, q, response.Namespace, "", appKey, appValue, store, nil)
err := repoCache.SetManifests(response.Revision, appSrc, q.RefSources, q, response.Namespace, "", appKey, appValue, store, nil, "")
if err != nil {
t.Fatal(err)
}
@@ -230,7 +230,7 @@ func TestCachedManifestResponse_HashBehavior(t *testing.T) {
// Retrieve the value using 'GetManifests' and confirm it works
retrievedVal := &CachedManifestResponse{}
err = repoCache.GetManifests(response.Revision, appSrc, q.RefSources, q, response.Namespace, "", appKey, appValue, retrievedVal, nil)
err = repoCache.GetManifests(response.Revision, appSrc, q.RefSources, q, response.Namespace, "", appKey, appValue, retrievedVal, nil, "")
if err != nil {
t.Fatal(err)
}
@@ -252,7 +252,7 @@ func TestCachedManifestResponse_HashBehavior(t *testing.T) {
// Retrieve the value using GetManifests and confirm it returns a cache miss
retrievedVal = &CachedManifestResponse{}
err = repoCache.GetManifests(response.Revision, appSrc, q.RefSources, q, response.Namespace, "", appKey, appValue, retrievedVal, nil)
err = repoCache.GetManifests(response.Revision, appSrc, q.RefSources, q, response.Namespace, "", appKey, appValue, retrievedVal, nil, "")
assert.Equal(t, err, cacheutil.ErrCacheMiss)

View File

@@ -814,7 +814,7 @@ func (s *Service) runManifestGenAsync(ctx context.Context, repoRoot, commitSHA,
// Retrieve a new copy (if available) of the cached response: this ensures we are updating the latest copy of the cache,
// rather than a copy of the cache that occurred before (a potentially lengthy) manifest generation.
innerRes := &cache.CachedManifestResponse{}
cacheErr := s.cache.GetManifests(cacheKey, appSourceCopy, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, innerRes, refSourceCommitSHAs)
cacheErr := s.cache.GetManifests(cacheKey, appSourceCopy, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, innerRes, refSourceCommitSHAs, q.InstallationID)
if cacheErr != nil && !errors.Is(cacheErr, cache.ErrCacheMiss) {
logCtx.Warnf("manifest cache get error %s: %v", appSourceCopy.String(), cacheErr)
ch.errCh <- cacheErr
@@ -832,7 +832,7 @@ func (s *Service) runManifestGenAsync(ctx context.Context, repoRoot, commitSHA,
// Update the cache to include failure information
innerRes.NumberOfConsecutiveFailures++
innerRes.MostRecentError = err.Error()
cacheErr = s.cache.SetManifests(cacheKey, appSourceCopy, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, innerRes, refSourceCommitSHAs)
cacheErr = s.cache.SetManifests(cacheKey, appSourceCopy, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, innerRes, refSourceCommitSHAs, q.InstallationID)
if cacheErr != nil {
logCtx.Warnf("manifest cache set error %s: %v", appSourceCopy.String(), cacheErr)
@@ -856,7 +856,7 @@ func (s *Service) runManifestGenAsync(ctx context.Context, repoRoot, commitSHA,
}
manifestGenResult.Revision = commitSHA
manifestGenResult.VerifyResult = opContext.verificationResult
err = s.cache.SetManifests(cacheKey, appSourceCopy, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, &manifestGenCacheEntry, refSourceCommitSHAs)
err = s.cache.SetManifests(cacheKey, appSourceCopy, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, &manifestGenCacheEntry, refSourceCommitSHAs, q.InstallationID)
if err != nil {
log.Warnf("manifest cache set error %s/%s: %v", appSourceCopy.String(), cacheKey, err)
}
@@ -873,7 +873,7 @@ func (s *Service) getManifestCacheEntry(cacheKey string, q *apiclient.ManifestRe
cache.LogDebugManifestCacheKeyFields("getting manifests cache", "GenerateManifest API call", cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, refSourceCommitSHAs)
res := cache.CachedManifestResponse{}
err := s.cache.GetManifests(cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, &res, refSourceCommitSHAs)
err := s.cache.GetManifests(cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, &res, refSourceCommitSHAs, q.InstallationID)
if err == nil {
// The cache contains an existing value
@@ -890,7 +890,7 @@ func (s *Service) getManifestCacheEntry(cacheKey string, q *apiclient.ManifestRe
cache.LogDebugManifestCacheKeyFields("deleting manifests cache", "manifest hash did not match or cached response is empty", cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, refSourceCommitSHAs)
// We can now try again, so reset the cache state and run the operation below
err = s.cache.DeleteManifests(cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, refSourceCommitSHAs)
err = s.cache.DeleteManifests(cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, refSourceCommitSHAs, q.InstallationID)
if err != nil {
log.Warnf("manifest cache set error %s/%s: %v", q.ApplicationSource.String(), cacheKey, err)
}
@@ -905,7 +905,7 @@ func (s *Service) getManifestCacheEntry(cacheKey string, q *apiclient.ManifestRe
cache.LogDebugManifestCacheKeyFields("deleting manifests cache", "reset after paused generation count", cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, refSourceCommitSHAs)
// We can now try again, so reset the error cache state and run the operation below
err = s.cache.DeleteManifests(cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, refSourceCommitSHAs)
err = s.cache.DeleteManifests(cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, refSourceCommitSHAs, q.InstallationID)
if err != nil {
log.Warnf("manifest cache set error %s/%s: %v", q.ApplicationSource.String(), cacheKey, err)
}
@@ -925,7 +925,7 @@ func (s *Service) getManifestCacheEntry(cacheKey string, q *apiclient.ManifestRe
// Increment the number of returned cached responses and push that new value to the cache
// (if we have not already done so previously in this function)
res.NumberOfCachedResponsesReturned++
err = s.cache.SetManifests(cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, &res, refSourceCommitSHAs)
err = s.cache.SetManifests(cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, &res, refSourceCommitSHAs, q.InstallationID)
if err != nil {
log.Warnf("manifest cache set error %s/%s: %v", q.ApplicationSource.String(), cacheKey, err)
}
@@ -1444,7 +1444,7 @@ func GenerateManifests(ctx context.Context, appPath, repoRoot, revision string,
for _, target := range targets {
if q.AppLabelKey != "" && q.AppName != "" && !kube.IsCRD(target) {
err = resourceTracking.SetAppInstance(target, q.AppLabelKey, q.AppName, q.Namespace, v1alpha1.TrackingMethod(q.TrackingMethod))
err = resourceTracking.SetAppInstance(target, q.AppLabelKey, q.AppName, q.Namespace, v1alpha1.TrackingMethod(q.TrackingMethod), q.InstallationID)
if err != nil {
return nil, fmt.Errorf("failed to set app instance tracking info on manifest: %w", err)
}
@@ -2769,7 +2769,10 @@ func (s *Service) UpdateRevisionForPaths(_ context.Context, request *apiclient.U
return nil, status.Errorf(codes.Internal, "unable to get changed files for repo %s with revision %s: %v", repo.Repo, revision, err)
}
changed := apppathutil.AppFilesHaveChanged(refreshPaths, files)
changed := false
if len(files) != 0 {
changed = apppathutil.AppFilesHaveChanged(refreshPaths, files)
}
if !changed {
logCtx.Debugf("no changes found for application %s in repo %s from revision %s to revision %s", request.AppName, repo.Repo, syncedRevision, revision)
@@ -2803,7 +2806,7 @@ func (s *Service) updateCachedRevision(logCtx *log.Entry, oldRev string, newRev
}
}
err := s.cache.SetNewRevisionManifests(newRev, oldRev, request.ApplicationSource, request.RefSources, request, request.Namespace, request.TrackingMethod, request.AppLabelKey, request.AppName, repoRefs)
err := s.cache.SetNewRevisionManifests(newRev, oldRev, request.ApplicationSource, request.RefSources, request, request.Namespace, request.TrackingMethod, request.AppLabelKey, request.AppName, repoRefs, request.InstallationID)
if err != nil {
if errors.Is(err, cache.ErrCacheMiss) {
logCtx.Debugf("manifest cache miss during comparison for application %s in repo %s from revision %s", request.AppName, request.GetRepo().Repo, oldRev)

View File

@@ -36,6 +36,8 @@ message ManifestRequest {
repeated string projectSourceRepos = 24;
// This is used to surface "source not permitted" errors for Helm repositories
string projectName = 25;
// Holds instance installation id
string installationID = 27;
}
message ManifestRequestWithFiles {
@@ -275,6 +277,7 @@ message UpdateRevisionForPathsRequest {
string syncedRevision = 11;
string revision = 12;
repeated string paths = 13;
string installationID = 15;
}
message UpdateRevisionForPathsResponse {

View File

@@ -310,7 +310,7 @@ func TestGenerateManifests_K8SAPIResetCache(t *testing.T) {
cachedFakeResponse := &apiclient.ManifestResponse{Manifests: []string{"Fake"}, Revision: mock.Anything}
err := service.cache.SetManifests(mock.Anything, &src, q.RefSources, &q, "", "", "", "", &cache.CachedManifestResponse{ManifestResponse: cachedFakeResponse}, nil)
err := service.cache.SetManifests(mock.Anything, &src, q.RefSources, &q, "", "", "", "", &cache.CachedManifestResponse{ManifestResponse: cachedFakeResponse}, nil, "")
require.NoError(t, err)
res, err := service.GenerateManifest(context.Background(), &q)
@@ -335,7 +335,7 @@ func TestGenerateManifests_EmptyCache(t *testing.T) {
ProjectSourceRepos: []string{"*"},
}
err := service.cache.SetManifests(mock.Anything, &src, q.RefSources, &q, "", "", "", "", &cache.CachedManifestResponse{ManifestResponse: nil}, nil)
err := service.cache.SetManifests(mock.Anything, &src, q.RefSources, &q, "", "", "", "", &cache.CachedManifestResponse{ManifestResponse: nil}, nil, "")
require.NoError(t, err)
res, err := service.GenerateManifest(context.Background(), &q)
@@ -768,7 +768,7 @@ func TestManifestGenErrorCacheByNumRequests(t *testing.T) {
assert.NotNil(t, manifestRequest)
cachedManifestResponse := &cache.CachedManifestResponse{}
err := service.cache.GetManifests(mock.Anything, manifestRequest.ApplicationSource, manifestRequest.RefSources, manifestRequest, manifestRequest.Namespace, "", manifestRequest.AppLabelKey, manifestRequest.AppName, cachedManifestResponse, nil)
err := service.cache.GetManifests(mock.Anything, manifestRequest.ApplicationSource, manifestRequest.RefSources, manifestRequest, manifestRequest.Namespace, "", manifestRequest.AppLabelKey, manifestRequest.AppName, cachedManifestResponse, nil, "")
require.NoError(t, err)
return cachedManifestResponse
}
@@ -2104,7 +2104,7 @@ func TestGenerateManifestsWithAppParameterFile(t *testing.T) {
// Try to pull from the cache with a `source` that does not include any overrides. Overrides should not be
// part of the cache key, because you can't get the overrides without a repo operation. And avoiding repo
// operations is the point of the cache.
err = service.cache.GetManifests(mock.Anything, source, argoappv1.RefTargetRevisionMapping{}, &argoappv1.ClusterInfo{}, "", "", "", "test", res, nil)
err = service.cache.GetManifests(mock.Anything, source, argoappv1.RefTargetRevisionMapping{}, &argoappv1.ClusterInfo{}, "", "", "", "test", res, nil, "")
require.NoError(t, err)
})
})

View File

@@ -0,0 +1,47 @@
local health_status = {
status = "Progressing",
message = "Provisioning..."
}
-- If .status is nil or doesn't have conditions, then the control plane is not ready
if obj.status == nil or obj.status.conditions == nil then
return health_status
end
-- Accumulator for the error messages (could be multiple conditions in error state)
err_msg = ""
-- Iterate over the conditions to determine the health status
for i, condition in ipairs(obj.status.conditions) do
-- Check if the Ready condition is True, then the control plane is ready
if condition.type == "Ready" and condition.status == "True" then
health_status.status = "Healthy"
health_status.message = "Control plane is ready"
return health_status
end
-- If we have a condition that is False and has an Error severity, then the control plane is in a degraded state
if condition.status == "False" and condition.severity == "Error" then
health_status.status = "Degraded"
err_msg = err_msg .. condition.message .. " "
end
end
-- If we have any error conditions, then the control plane is in a degraded state
if health_status.status == "Degraded" then
health_status.message = err_msg
return health_status
end
-- If .status.ready is False, then the control plane is not ready
if obj.status.ready == false then
health_status.status = "Progressing"
health_status.message = "Control plane is not ready (.status.ready is false)"
return health_status
end
-- If we reach this point, then the control plane is not ready and we don't have any error conditions
health_status.status = "Progressing"
health_status.message = "Control plane is not ready"
return health_status

View File

@@ -0,0 +1,17 @@
tests:
- healthStatus:
status: Healthy
message: 'Control plane is ready'
inputPath: testdata/healthy.yaml
- healthStatus:
status: Progressing
message: 'Control plane is not ready (.status.ready is false)'
inputPath: testdata/progressing_ready_false.yaml
- healthStatus:
status: Progressing
message: 'Control plane is not ready'
inputPath: testdata/progressing_ready_true.yaml
- healthStatus:
status: Degraded
message: '7 of 10 completed failed reconciling OIDC provider for cluster: failed to create OIDC provider: error creating provider: LimitExceeded: Cannot exceed quota for OpenIdConnectProvidersPerAccount: 100 '
inputPath: testdata/degraded.yaml

View File

@@ -0,0 +1,50 @@
apiVersion: controlplane.cluster.x-k8s.io/v1beta2
kind: AWSManagedControlPlane
metadata:
name: test
namespace: ns-test
ownerReferences:
- apiVersion: cluster.x-k8s.io/v1beta1
blockOwnerDeletion: true
controller: true
kind: Cluster
name: test
status:
conditions:
- lastTransitionTime: "2024-07-30T11:10:03Z"
status: "False"
severity: Error
message: "7 of 10 completed"
type: Ready
- lastTransitionTime: "2024-07-26T14:35:48Z"
status: "True"
type: ClusterSecurityGroupsReady
- lastTransitionTime: "2024-07-30T02:20:06Z"
status: "True"
type: EKSAddonsConfigured
- lastTransitionTime: "2024-07-26T14:43:57Z"
reason: created
severity: Info
status: "False"
type: EKSControlPlaneCreating
- lastTransitionTime: "2024-07-25T09:22:46Z"
message: "failed reconciling OIDC provider for cluster: failed to create OIDC provider: error creating provider: LimitExceeded: Cannot exceed quota for OpenIdConnectProvidersPerAccount: 100"
reason: EKSControlPlaneReconciliationFailed
severity: Error
status: "False"
- lastTransitionTime: "2024-07-30T11:10:03Z"
status: "True"
type: EKSControlPlaneReady
- lastTransitionTime: "2024-07-26T15:28:01Z"
status: "True"
type: IAMAuthenticatorConfigured
- lastTransitionTime: "2024-07-26T15:27:58Z"
status: "True"
type: IAMControlPlaneRolesReady
- lastTransitionTime: "2024-07-26T14:35:48Z"
status: "True"
type: SubnetsReady
- lastTransitionTime: "2024-07-26T14:35:46Z"
status: "True"
type: VpcReady
ready: true

View File

@@ -0,0 +1,46 @@
apiVersion: controlplane.cluster.x-k8s.io/v1beta2
kind: AWSManagedControlPlane
metadata:
name: test
namespace: ns-test
ownerReferences:
- apiVersion: cluster.x-k8s.io/v1beta1
blockOwnerDeletion: true
controller: true
kind: Cluster
name: test
status:
conditions:
- lastTransitionTime: "2024-07-30T11:10:03Z"
status: "True"
type: Ready
- lastTransitionTime: "2024-07-26T14:35:48Z"
status: "True"
type: ClusterSecurityGroupsReady
- lastTransitionTime: "2024-07-30T02:20:06Z"
status: "True"
type: EKSAddonsConfigured
- lastTransitionTime: "2024-07-26T14:43:57Z"
reason: created
severity: Info
status: "False"
type: EKSControlPlaneCreating
- lastTransitionTime: "2024-07-30T11:10:03Z"
status: "True"
type: EKSControlPlaneReady
- lastTransitionTime: "2024-07-30T13:05:45Z"
status: "True"
type: EKSIdentityProviderConfigured
- lastTransitionTime: "2024-07-26T15:28:01Z"
status: "True"
type: IAMAuthenticatorConfigured
- lastTransitionTime: "2024-07-26T15:27:58Z"
status: "True"
type: IAMControlPlaneRolesReady
- lastTransitionTime: "2024-07-26T14:35:48Z"
status: "True"
type: SubnetsReady
- lastTransitionTime: "2024-07-26T14:35:46Z"
status: "True"
type: VpcReady
ready: true

View File

@@ -0,0 +1,46 @@
apiVersion: controlplane.cluster.x-k8s.io/v1beta2
kind: AWSManagedControlPlane
metadata:
name: test
namespace: ns-test
ownerReferences:
- apiVersion: cluster.x-k8s.io/v1beta1
blockOwnerDeletion: true
controller: true
kind: Cluster
name: test
status:
conditions:
- lastTransitionTime: "2024-07-30T11:10:03Z"
status: "False"
type: Ready
- lastTransitionTime: "2024-07-26T14:35:48Z"
status: "True"
type: ClusterSecurityGroupsReady
- lastTransitionTime: "2024-07-30T02:20:06Z"
status: "True"
type: EKSAddonsConfigured
- lastTransitionTime: "2024-07-26T14:43:57Z"
reason: created
severity: Info
status: "False"
type: EKSControlPlaneCreating
- lastTransitionTime: "2024-07-30T11:10:03Z"
status: "True"
type: EKSControlPlaneReady
- lastTransitionTime: "2024-07-30T13:05:45Z"
status: "True"
type: EKSIdentityProviderConfigured
- lastTransitionTime: "2024-07-26T15:28:01Z"
status: "True"
type: IAMAuthenticatorConfigured
- lastTransitionTime: "2024-07-26T15:27:58Z"
status: "True"
type: IAMControlPlaneRolesReady
- lastTransitionTime: "2024-07-26T14:35:48Z"
status: "True"
type: SubnetsReady
- lastTransitionTime: "2024-07-26T14:35:46Z"
status: "True"
type: VpcReady
ready: false

View File

@@ -0,0 +1,46 @@
apiVersion: controlplane.cluster.x-k8s.io/v1beta2
kind: AWSManagedControlPlane
metadata:
name: test
namespace: ns-test
ownerReferences:
- apiVersion: cluster.x-k8s.io/v1beta1
blockOwnerDeletion: true
controller: true
kind: Cluster
name: test
status:
conditions:
- lastTransitionTime: "2024-07-30T11:10:03Z"
status: "False"
type: Ready
- lastTransitionTime: "2024-07-26T14:35:48Z"
status: "True"
type: ClusterSecurityGroupsReady
- lastTransitionTime: "2024-07-30T02:20:06Z"
status: "True"
type: EKSAddonsConfigured
- lastTransitionTime: "2024-07-26T14:43:57Z"
reason: created
severity: Info
status: "False"
type: EKSControlPlaneCreating
- lastTransitionTime: "2024-07-30T11:10:03Z"
status: "True"
type: EKSControlPlaneReady
- lastTransitionTime: "2024-07-30T13:05:45Z"
status: "True"
type: EKSIdentityProviderConfigured
- lastTransitionTime: "2024-07-26T15:28:01Z"
status: "True"
type: IAMAuthenticatorConfigured
- lastTransitionTime: "2024-07-26T15:27:58Z"
status: "True"
type: IAMControlPlaneRolesReady
- lastTransitionTime: "2024-07-26T14:35:48Z"
status: "True"
type: SubnetsReady
- lastTransitionTime: "2024-07-26T14:35:46Z"
status: "True"
type: VpcReady
ready: true

View File

@@ -509,6 +509,10 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
if err != nil {
return fmt.Errorf("error getting kustomize settings options: %w", err)
}
installationID, err := s.settingsMgr.GetInstallationID()
if err != nil {
return fmt.Errorf("error getting installation ID: %w", err)
}
manifestInfo, err := client.GenerateManifest(ctx, &apiclient.ManifestRequest{
Repo: repo,
@@ -529,6 +533,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
ProjectSourceRepos: proj.Spec.SourceRepos,
HasMultipleSources: a.Spec.HasMultipleSources(),
RefSources: refSources,
InstallationID: installationID,
})
if err != nil {
return fmt.Errorf("error generating manifests: %w", err)
@@ -1495,71 +1500,9 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe
return nil, err
}
var versionId int64 = 0
if q.VersionId != nil {
versionId = int64(*q.VersionId)
}
var source *v1alpha1.ApplicationSource
// To support changes between single source and multi source revisions
// we have to calculate if the operation has to be done as multisource or not.
// There are 2 different scenarios, checking current revision and historic revision
// - Current revision (VersionId is nil or 0):
// - The application is multi source and required version too -> multi source
// - The application is single source and the required version too -> single source
// - The application is multi source and the required version is single source -> single source
// - The application is single source and the required version is multi source -> multi source
// - Historic revision:
// - The application is multi source and the previous one too -> multi source
// - The application is single source and the previous one too -> single source
// - The application is multi source and the previous one is single source -> multi source
// - The application is single source and the previous one is multi source -> single source
isRevisionMultiSource := a.Spec.HasMultipleSources()
emptyHistory := len(a.Status.History) == 0
if !emptyHistory {
for _, h := range a.Status.History {
if h.ID == versionId {
isRevisionMultiSource = len(h.Revisions) > 0
break
}
}
}
// If the historical data is empty (because the app hasn't been synced yet)
// we can use the source, if not (the app has been synced at least once)
// we have to use the history because sources can be added/removed
if emptyHistory {
if isRevisionMultiSource {
source = &a.Spec.Sources[*q.SourceIndex]
} else {
s := a.Spec.GetSource()
source = &s
}
} else {
// the source count can change during the time, we cannot just trust in .status.sync
// because if a source has been added/removed, the revisions there won't match
// as this is only used for the UI and not internally, we can use the historical data
// using the specific revisionId
for _, h := range a.Status.History {
if h.ID == versionId {
// The iteration values are assigned to the respective iteration variables as in an assignment statement.
// The iteration variables may be declared by the “range” clause using a form of short variable declaration (:=).
// In this case their types are set to the types of the respective iteration values and their scope is the block of the "for" statement;
// they are re-used in each iteration. If the iteration variables are declared outside the "for" statement,
// after execution their values will be those of the last iteration.
// https://golang.org/ref/spec#For_statements
h := h
if isRevisionMultiSource {
source = &h.Sources[*q.SourceIndex]
} else {
source = &h.Source
}
}
}
}
if source == nil {
return nil, fmt.Errorf("revision not found: %w", err)
source, err := getAppSourceBySourceIndexAndVersionId(a, q.SourceIndex, q.VersionId)
if err != nil {
return nil, fmt.Errorf("error getting app source by source index and version ID: %w", err)
}
repo, err := s.db.GetRepository(ctx, source.RepoURL, proj.Name)
@@ -1585,22 +1528,9 @@ func (s *Server) RevisionChartDetails(ctx context.Context, q *application.Revisi
return nil, err
}
var source *v1alpha1.ApplicationSource
if a.Spec.HasMultipleSources() {
// the source count can change during the time, we cannot just trust in .status.sync
// because if a source has been added/removed, the revisions there won't match
// as this is only used for the UI and not internally, we can use the historical data
// using the specific revisionId
for _, h := range a.Status.History {
if h.ID == int64(*q.VersionId) {
source = &h.Sources[*q.SourceIndex]
}
}
if source == nil {
return nil, fmt.Errorf("revision not found: %w", err)
}
} else {
source = a.Spec.Source
source, err := getAppSourceBySourceIndexAndVersionId(a, q.SourceIndex, q.VersionId)
if err != nil {
return nil, fmt.Errorf("error getting app source by source index and version ID: %w", err)
}
if source.Chart == "" {
@@ -1622,6 +1552,76 @@ func (s *Server) RevisionChartDetails(ctx context.Context, q *application.Revisi
})
}
// getAppSourceBySourceIndexAndVersionId returns the source for a specific source index and version ID. Source index and
// version ID are optional. If the source index is not specified, it defaults to 0. If the version ID is not specified,
// we use the source(s) currently configured for the app. If the version ID is specified, we find the source for that
// version ID. If the version ID is not found, we return an error. If the source index is out of bounds for whichever
// source we choose (configured sources or sources for a specific version), we return an error.
func getAppSourceBySourceIndexAndVersionId(a *appv1.Application, sourceIndexMaybe *int32, versionIdMaybe *int32) (appv1.ApplicationSource, error) {
// Start with all the app's configured sources.
sources := a.Spec.GetSources()
// If the user specified a version, get the sources for that version. If the version is not found, return an error.
if versionIdMaybe != nil {
versionId := int64(*versionIdMaybe)
var err error
sources, err = getSourcesByVersionId(a, versionId)
if err != nil {
return appv1.ApplicationSource{}, fmt.Errorf("error getting source by version ID: %w", err)
}
}
// Start by assuming we want the first source.
sourceIndex := 0
// If the user specified a source index, use that instead.
if sourceIndexMaybe != nil {
sourceIndex = int(*sourceIndexMaybe)
if sourceIndex >= len(sources) {
if len(sources) == 1 {
return appv1.ApplicationSource{}, fmt.Errorf("source index %d not found because there is only 1 source", sourceIndex)
}
return appv1.ApplicationSource{}, fmt.Errorf("source index %d not found because there are only %d sources", sourceIndex, len(sources))
}
}
source := sources[sourceIndex]
return source, nil
}
// getRevisionHistoryByVersionId returns the revision history for a specific version ID.
// If the version ID is not found, it returns an empty revision history and false.
func getRevisionHistoryByVersionId(histories v1alpha1.RevisionHistories, versionId int64) (appv1.RevisionHistory, bool) {
for _, h := range histories {
if h.ID == versionId {
return h, true
}
}
return appv1.RevisionHistory{}, false
}
// getSourcesByVersionId returns the sources for a specific version ID. If there is no history, it returns an error.
// If the version ID is not found, it returns an error. If the version ID is found, and there are multiple sources,
// it returns the sources for that version ID. If the version ID is found, and there is only one source, it returns
// a slice with just the single source.
func getSourcesByVersionId(a *appv1.Application, versionId int64) ([]appv1.ApplicationSource, error) {
if len(a.Status.History) == 0 {
return nil, fmt.Errorf("version ID %d not found because the app has no history", versionId)
}
h, ok := getRevisionHistoryByVersionId(a.Status.History, versionId)
if !ok {
return nil, fmt.Errorf("revision history not found for version ID %d", versionId)
}
if len(h.Sources) > 0 {
return h.Sources, nil
}
return []v1alpha1.ApplicationSource{h.Source}, nil
}
func isMatchingResource(q *application.ResourcesQuery, key kube.ResourceKey) bool {
return (q.GetName() == "" || q.GetName() == key.Name) &&
(q.GetNamespace() == "" || q.GetNamespace() == key.Namespace) &&
@@ -1891,7 +1891,11 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
s.inferResourcesStatusHealth(a)
if !proj.Spec.SyncWindows.Matches(a).CanSync(true) {
canSync, err := proj.Spec.SyncWindows.Matches(a).CanSync(true)
if err != nil {
return a, status.Errorf(codes.PermissionDenied, "cannot sync: invalid sync window: %v", err)
}
if !canSync {
return a, status.Errorf(codes.PermissionDenied, "cannot sync: blocked by sync window")
}
@@ -2608,10 +2612,17 @@ func (s *Server) GetApplicationSyncWindows(ctx context.Context, q *application.A
}
windows := proj.Spec.SyncWindows.Matches(a)
sync := windows.CanSync(true)
sync, err := windows.CanSync(true)
if err != nil {
return nil, fmt.Errorf("invalid sync windows: %w", err)
}
activeWindows, err := windows.Active()
if err != nil {
return nil, fmt.Errorf("invalid sync windows: %w", err)
}
res := &application.ApplicationSyncWindowsResponse{
ActiveWindows: convertSyncWindows(windows.Active()),
ActiveWindows: convertSyncWindows(activeWindows),
AssignedWindows: convertSyncWindows(windows),
CanSync: &sync,
}

View File

@@ -10,6 +10,8 @@ import (
"testing"
"time"
"k8s.io/utils/pointer"
"k8s.io/apimachinery/pkg/labels"
"github.com/argoproj/gitops-engine/pkg/health"
@@ -3025,3 +3027,265 @@ func TestServer_ResolveSourceRevisions_SingleSource(t *testing.T) {
assert.Equal(t, ([]string)(nil), sourceRevisions)
assert.Equal(t, ([]string)(nil), displayRevisions)
}
func Test_RevisionMetadata(t *testing.T) {
singleSourceApp := newTestApp()
singleSourceApp.Name = "single-source-app"
singleSourceApp.Spec = appv1.ApplicationSpec{
Source: &appv1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
Path: "helm-guestbook",
TargetRevision: "HEAD",
},
}
multiSourceApp := newTestApp()
multiSourceApp.Name = "multi-source-app"
multiSourceApp.Spec = appv1.ApplicationSpec{
Sources: []appv1.ApplicationSource{
{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
Path: "helm-guestbook",
TargetRevision: "HEAD",
},
{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
Path: "kustomize-guestbook",
TargetRevision: "HEAD",
},
},
}
singleSourceHistory := []appv1.RevisionHistory{
{
ID: 1,
Source: singleSourceApp.Spec.GetSource(),
Revision: "a",
},
}
multiSourceHistory := []appv1.RevisionHistory{
{
ID: 1,
Sources: multiSourceApp.Spec.GetSources(),
Revisions: []string{"a", "b"},
},
}
testCases := []struct {
name string
multiSource bool
history *struct {
matchesSourceType bool
}
sourceIndex *int32
versionId *int32
expectErrorContains *string
}{
{
name: "single-source app without history, no source index, no version ID",
multiSource: false,
},
{
name: "single-source app without history, no source index, missing version ID",
multiSource: false,
versionId: pointer.Int32(999),
expectErrorContains: pointer.String("the app has no history"),
},
{
name: "single source app without history, present source index, no version ID",
multiSource: false,
sourceIndex: pointer.Int32(0),
},
{
name: "single source app without history, invalid source index, no version ID",
multiSource: false,
sourceIndex: pointer.Int32(999),
expectErrorContains: pointer.String("source index 999 not found"),
},
{
name: "single source app with matching history, no source index, no version ID",
multiSource: false,
history: &struct{ matchesSourceType bool }{true},
},
{
name: "single source app with matching history, no source index, missing version ID",
multiSource: false,
history: &struct{ matchesSourceType bool }{true},
versionId: pointer.Int32(999),
expectErrorContains: pointer.String("history not found for version ID 999"),
},
{
name: "single source app with matching history, no source index, present version ID",
multiSource: false,
history: &struct{ matchesSourceType bool }{true},
versionId: pointer.Int32(1),
},
{
name: "single source app with multi-source history, no source index, no version ID",
multiSource: false,
history: &struct{ matchesSourceType bool }{false},
},
{
name: "single source app with multi-source history, no source index, missing version ID",
multiSource: false,
history: &struct{ matchesSourceType bool }{false},
versionId: pointer.Int32(999),
expectErrorContains: pointer.String("history not found for version ID 999"),
},
{
name: "single source app with multi-source history, no source index, present version ID",
multiSource: false,
history: &struct{ matchesSourceType bool }{false},
versionId: pointer.Int32(1),
},
{
name: "single-source app with multi-source history, source index 1, no version ID",
multiSource: false,
sourceIndex: pointer.Int32(1),
history: &struct{ matchesSourceType bool }{false},
// Since the user requested source index 1, but no version ID, we'll get an error when looking at the live
// source, because the live source is single-source.
expectErrorContains: pointer.String("there is only 1 source"),
},
{
name: "single-source app with multi-source history, invalid source index, no version ID",
multiSource: false,
sourceIndex: pointer.Int32(999),
history: &struct{ matchesSourceType bool }{false},
expectErrorContains: pointer.String("source index 999 not found"),
},
{
name: "single-source app with multi-source history, valid source index, present version ID",
multiSource: false,
sourceIndex: pointer.Int32(1),
history: &struct{ matchesSourceType bool }{false},
versionId: pointer.Int32(1),
},
{
name: "multi-source app without history, no source index, no version ID",
multiSource: true,
},
{
name: "multi-source app without history, no source index, missing version ID",
multiSource: true,
versionId: pointer.Int32(999),
expectErrorContains: pointer.String("the app has no history"),
},
{
name: "multi-source app without history, present source index, no version ID",
multiSource: true,
sourceIndex: pointer.Int32(1),
},
{
name: "multi-source app without history, invalid source index, no version ID",
multiSource: true,
sourceIndex: pointer.Int32(999),
expectErrorContains: pointer.String("source index 999 not found"),
},
{
name: "multi-source app with matching history, no source index, no version ID",
multiSource: true,
history: &struct{ matchesSourceType bool }{true},
},
{
name: "multi-source app with matching history, no source index, missing version ID",
multiSource: true,
history: &struct{ matchesSourceType bool }{true},
versionId: pointer.Int32(999),
expectErrorContains: pointer.String("history not found for version ID 999"),
},
{
name: "multi-source app with matching history, no source index, present version ID",
multiSource: true,
history: &struct{ matchesSourceType bool }{true},
versionId: pointer.Int32(1),
},
{
name: "multi-source app with single-source history, no source index, no version ID",
multiSource: true,
history: &struct{ matchesSourceType bool }{false},
},
{
name: "multi-source app with single-source history, no source index, missing version ID",
multiSource: true,
history: &struct{ matchesSourceType bool }{false},
versionId: pointer.Int32(999),
expectErrorContains: pointer.String("history not found for version ID 999"),
},
{
name: "multi-source app with single-source history, no source index, present version ID",
multiSource: true,
history: &struct{ matchesSourceType bool }{false},
versionId: pointer.Int32(1),
},
{
name: "multi-source app with single-source history, source index 1, no version ID",
multiSource: true,
sourceIndex: pointer.Int32(1),
history: &struct{ matchesSourceType bool }{false},
},
{
name: "multi-source app with single-source history, invalid source index, no version ID",
multiSource: true,
sourceIndex: pointer.Int32(999),
history: &struct{ matchesSourceType bool }{false},
expectErrorContains: pointer.String("source index 999 not found"),
},
{
name: "multi-source app with single-source history, valid source index, present version ID",
multiSource: true,
sourceIndex: pointer.Int32(0),
history: &struct{ matchesSourceType bool }{false},
versionId: pointer.Int32(1),
},
{
name: "multi-source app with single-source history, source index 1, present version ID",
multiSource: true,
sourceIndex: pointer.Int32(1),
history: &struct{ matchesSourceType bool }{false},
versionId: pointer.Int32(1),
expectErrorContains: pointer.String("source index 1 not found"),
},
}
for _, tc := range testCases {
tcc := tc
t.Run(tcc.name, func(t *testing.T) {
app := singleSourceApp
if tcc.multiSource {
app = multiSourceApp
}
if tcc.history != nil {
if tcc.history.matchesSourceType {
if tcc.multiSource {
app.Status.History = multiSourceHistory
} else {
app.Status.History = singleSourceHistory
}
} else {
if tcc.multiSource {
app.Status.History = singleSourceHistory
} else {
app.Status.History = multiSourceHistory
}
}
}
s := newTestAppServer(t, app)
request := &application.RevisionMetadataQuery{
Name: pointer.String(app.Name),
Revision: pointer.String("HEAD"),
SourceIndex: tcc.sourceIndex,
VersionId: tcc.versionId,
}
_, err := s.RevisionMetadata(context.Background(), request)
if tcc.expectErrorContains != nil {
require.ErrorContains(t, err, *tcc.expectErrorContains)
} else {
require.NoError(t, err)
}
})
}
}

View File

@@ -144,8 +144,8 @@ func mergeLogStreams(streams []chan logEntry, bufferingDuration time.Duration) c
_ = send(true)
close(merged)
ticker.Stop()
close(merged)
}()
return merged
}

View File

@@ -229,7 +229,7 @@ func (s *terminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fieldLog.Info("terminal session starting")
session, err := newTerminalSession(w, r, nil, s.sessionManager)
session, err := newTerminalSession(ctx, w, r, nil, s.sessionManager, appRBACName, s.enf)
if err != nil {
http.Error(w, "Failed to start terminal session", http.StatusBadRequest)
return

View File

@@ -1,12 +1,16 @@
package application
import (
"context"
"encoding/json"
"fmt"
"net/http"
"sync"
"time"
"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
"github.com/argoproj/argo-cd/v2/util/rbac"
"github.com/argoproj/argo-cd/v2/common"
httputil "github.com/argoproj/argo-cd/v2/util/http"
util_session "github.com/argoproj/argo-cd/v2/util/session"
@@ -32,6 +36,7 @@ var upgrader = func() websocket.Upgrader {
// terminalSession implements PtyHandler
type terminalSession struct {
ctx context.Context
wsConn *websocket.Conn
sizeChan chan remotecommand.TerminalSize
doneChan chan struct{}
@@ -40,6 +45,8 @@ type terminalSession struct {
writeLock sync.Mutex
sessionManager *util_session.SessionManager
token *string
appRBACName string
enf *rbac.Enforcer
}
// getToken get auth token from web socket request
@@ -49,7 +56,7 @@ func getToken(r *http.Request) (string, error) {
}
// newTerminalSession create terminalSession
func newTerminalSession(w http.ResponseWriter, r *http.Request, responseHeader http.Header, sessionManager *util_session.SessionManager) (*terminalSession, error) {
func newTerminalSession(ctx context.Context, w http.ResponseWriter, r *http.Request, responseHeader http.Header, sessionManager *util_session.SessionManager, appRBACName string, enf *rbac.Enforcer) (*terminalSession, error) {
token, err := getToken(r)
if err != nil {
return nil, err
@@ -60,12 +67,15 @@ func newTerminalSession(w http.ResponseWriter, r *http.Request, responseHeader h
return nil, err
}
session := &terminalSession{
ctx: ctx,
wsConn: conn,
tty: true,
sizeChan: make(chan remotecommand.TerminalSize),
doneChan: make(chan struct{}),
sessionManager: sessionManager,
token: &token,
appRBACName: appRBACName,
enf: enf,
}
return session, nil
}
@@ -126,6 +136,29 @@ func (t *terminalSession) reconnect() (int, error) {
return 0, nil
}
func (t *terminalSession) validatePermissions(p []byte) (int, error) {
permissionDeniedMessage, _ := json.Marshal(TerminalMessage{
Operation: "stdout",
Data: "Permission denied",
})
if err := t.enf.EnforceErr(t.ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, t.appRBACName); err != nil {
err = t.wsConn.WriteMessage(websocket.TextMessage, permissionDeniedMessage)
if err != nil {
log.Errorf("permission denied message err: %v", err)
}
return copy(p, EndOfTransmission), permissionDeniedErr
}
if err := t.enf.EnforceErr(t.ctx.Value("claims"), rbacpolicy.ResourceExec, rbacpolicy.ActionCreate, t.appRBACName); err != nil {
err = t.wsConn.WriteMessage(websocket.TextMessage, permissionDeniedMessage)
if err != nil {
log.Errorf("permission denied message err: %v", err)
}
return copy(p, EndOfTransmission), permissionDeniedErr
}
return 0, nil
}
// Read called in a loop from remotecommand as long as the process is running
func (t *terminalSession) Read(p []byte) (int, error) {
// check if token still valid
@@ -136,6 +169,12 @@ func (t *terminalSession) Read(p []byte) (int, error) {
return t.reconnect()
}
// validate permissions
code, err := t.validatePermissions(p)
if err != nil {
return code, err
}
t.readLock.Lock()
_, message, err := t.wsConn.ReadMessage()
t.readLock.Unlock()

View File

@@ -1,25 +1,65 @@
package application
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
"github.com/argoproj/argo-cd/v2/common"
"github.com/argoproj/argo-cd/v2/util/assets"
"github.com/argoproj/argo-cd/v2/util/rbac"
"github.com/golang-jwt/jwt/v4"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func reconnect(w http.ResponseWriter, r *http.Request) {
func newTestTerminalSession(w http.ResponseWriter, r *http.Request) terminalSession {
upgrader := websocket.Upgrader{}
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
return terminalSession{}
}
ts := terminalSession{wsConn: c}
return terminalSession{wsConn: c}
}
func newEnforcer() *rbac.Enforcer {
additionalConfig := make(map[string]string, 0)
kubeclientset := fake.NewSimpleClientset(&v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNamespace,
Name: "argocd-cm",
Labels: map[string]string{
"app.kubernetes.io/part-of": "argocd",
},
},
Data: additionalConfig,
}, &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: testNamespace,
},
Data: map[string][]byte{
"admin.password": []byte("test"),
"server.secretkey": []byte("test"),
},
})
enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil)
return enforcer
}
func reconnect(w http.ResponseWriter, r *http.Request) {
ts := newTestTerminalSession(w, r)
_, _ = ts.reconnect()
}
@@ -44,3 +84,71 @@ func TestReconnect(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, ReconnectMessage, message.Data)
}
func TestValidateWithAdminPermissions(t *testing.T) {
validate := func(w http.ResponseWriter, r *http.Request) {
enf := newEnforcer()
_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
enf.SetDefaultRole("role:admin")
enf.SetClaimsEnforcerFunc(func(claims jwt.Claims, rvals ...interface{}) bool {
return true
})
ts := newTestTerminalSession(w, r)
ts.enf = enf
ts.appRBACName = "test"
// nolint:staticcheck
ts.ctx = context.WithValue(context.Background(), "claims", &jwt.MapClaims{"groups": []string{"admin"}})
_, err := ts.validatePermissions([]byte{})
require.NoError(t, err)
}
s := httptest.NewServer(http.HandlerFunc(validate))
defer s.Close()
u := "ws" + strings.TrimPrefix(s.URL, "http")
// Connect to the server
ws, _, err := websocket.DefaultDialer.Dial(u, nil)
require.NoError(t, err)
defer ws.Close()
}
func TestValidateWithoutPermissions(t *testing.T) {
validate := func(w http.ResponseWriter, r *http.Request) {
enf := newEnforcer()
_ = enf.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
enf.SetDefaultRole("role:test")
enf.SetClaimsEnforcerFunc(func(claims jwt.Claims, rvals ...interface{}) bool {
return false
})
ts := newTestTerminalSession(w, r)
ts.enf = enf
ts.appRBACName = "test"
// nolint:staticcheck
ts.ctx = context.WithValue(context.Background(), "claims", &jwt.MapClaims{"groups": []string{"test"}})
_, err := ts.validatePermissions([]byte{})
require.Error(t, err)
assert.Equal(t, permissionDeniedErr.Error(), err.Error())
}
s := httptest.NewServer(http.HandlerFunc(validate))
defer s.Close()
u := "ws" + strings.TrimPrefix(s.URL, "http")
// Connect to the server
ws, _, err := websocket.DefaultDialer.Dial(u, nil)
require.NoError(t, err)
defer ws.Close()
_, p, _ := ws.ReadMessage()
var message TerminalMessage
err = json.Unmarshal(p, &message)
require.NoError(t, err)
assert.Equal(t, "Permission denied", message.Data)
}

View File

@@ -525,7 +525,10 @@ func (s *Server) GetSyncWindowsState(ctx context.Context, q *project.SyncWindows
res := &project.SyncWindowsResponse{}
windows := proj.Spec.SyncWindows.Active()
windows, err := proj.Spec.SyncWindows.Active()
if err != nil {
return nil, err
}
if windows.HasWindows() {
res.Windows = *windows
} else {

View File

@@ -1049,7 +1049,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
// Webhook handler for git events (Note: cache timeouts are hardcoded because API server does not write to cache and not really using them)
argoDB := db.NewDB(a.Namespace, a.settingsMgr, a.KubeClientset)
acdWebhookHandler := webhook.NewHandler(a.Namespace, a.ArgoCDServerOpts.ApplicationNamespaces, a.AppClientset, a.settings, a.settingsMgr, a.RepoServerCache, a.Cache, argoDB)
acdWebhookHandler := webhook.NewHandler(a.Namespace, a.ArgoCDServerOpts.ApplicationNamespaces, a.AppClientset, a.settings, a.settingsMgr, a.RepoServerCache, a.Cache, argoDB, a.settingsMgr.GetMaxWebhookPayloadSize())
mux.HandleFunc("/api/webhook", acdWebhookHandler.Handler)

View File

@@ -42,6 +42,7 @@ message Settings {
bool execEnabled = 22;
string controllerNamespace = 23;
bool appsInAnyNamespaceEnabled = 24;
string installationID = 26;
}
message GoogleAnalyticsConfig {

View File

@@ -2880,3 +2880,49 @@ func TestAnnotationTrackingExtraResources(t *testing.T) {
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy))
}
func TestInstallationID(t *testing.T) {
ctx := Given(t)
ctx.
SetTrackingMethod(string(argo.TrackingMethodAnnotation)).
And(func() {
_, err := fixture.KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(
context.Background(), &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "test-configmap",
Annotations: map[string]string{
common.AnnotationKeyAppInstance: fmt.Sprintf("%s:/ConfigMap:%s/test-configmap", ctx.AppName(), DeploymentNamespace()),
},
},
}, metav1.CreateOptions{})
require.NoError(t, err)
}).
Path(guestbookPath).
Prune(false).
When().IgnoreErrors().CreateApp().Sync().
Then().Expect(OperationPhaseIs(OperationSucceeded)).Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
And(func(app *Application) {
var cm *ResourceStatus
for i := range app.Status.Resources {
if app.Status.Resources[i].Kind == "ConfigMap" && app.Status.Resources[i].Name == "test-configmap" {
cm = &app.Status.Resources[i]
break
}
}
require.NotNil(t, cm)
assert.Equal(t, SyncStatusCodeOutOfSync, cm.Status)
}).
When().SetInstallationID("test").Sync().
Then().
Expect(SyncStatusIs(SyncStatusCodeSynced)).
And(func(app *Application) {
require.Len(t, app.Status.Resources, 2)
svc, err := fixture.KubeClientset.CoreV1().Services(DeploymentNamespace()).Get(context.Background(), "guestbook-ui", metav1.GetOptions{})
require.NoError(t, err)
require.Equal(t, "test", svc.Annotations[common.AnnotationInstallationID])
deploy, err := fixture.KubeClientset.AppsV1().Deployments(DeploymentNamespace()).Get(context.Background(), "guestbook-ui", metav1.GetOptions{})
require.NoError(t, err)
require.Equal(t, "test", deploy.Annotations[common.AnnotationInstallationID])
})
}

View File

@@ -522,101 +522,6 @@ func TestSimpleListGeneratorGoTemplate(t *testing.T) {
Delete().Then().Expect(ApplicationsDoNotExist([]argov1alpha1.Application{*expectedAppNewMetadata}))
}
func TestCreateApplicationDespiteParamsError(t *testing.T) {
expectedErrorMessage := `failed to execute go template {{.cluster}}-guestbook: template: :1:2: executing "" at <.cluster>: map has no entry for key "cluster"`
expectedConditionsParamsError := []v1alpha1.ApplicationSetCondition{
{
Type: v1alpha1.ApplicationSetConditionErrorOccurred,
Status: v1alpha1.ApplicationSetConditionStatusTrue,
Message: expectedErrorMessage,
Reason: v1alpha1.ApplicationSetReasonRenderTemplateParamsError,
},
{
Type: v1alpha1.ApplicationSetConditionParametersGenerated,
Status: v1alpha1.ApplicationSetConditionStatusFalse,
Message: expectedErrorMessage,
Reason: v1alpha1.ApplicationSetReasonErrorOccurred,
},
{
Type: v1alpha1.ApplicationSetConditionResourcesUpToDate,
Status: v1alpha1.ApplicationSetConditionStatusFalse,
Message: expectedErrorMessage,
Reason: v1alpha1.ApplicationSetReasonRenderTemplateParamsError,
},
}
expectedApp := argov1alpha1.Application{
TypeMeta: metav1.TypeMeta{
Kind: application.ApplicationKind,
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
Source: &argov1alpha1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: argov1alpha1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "guestbook",
},
},
}
Given(t).
// Create a ListGenerator-based ApplicationSet
When().Create(v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "simple-list-generator",
},
Spec: v1alpha1.ApplicationSetSpec{
GoTemplate: true,
GoTemplateOptions: []string{"missingkey=error"},
Template: v1alpha1.ApplicationSetTemplate{
ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{Name: "{{.cluster}}-guestbook"},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
Source: &argov1alpha1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
TargetRevision: "HEAD",
Path: "guestbook",
},
Destination: argov1alpha1.ApplicationDestination{
Server: "{{.url}}",
Namespace: "guestbook",
},
},
},
Generators: []v1alpha1.ApplicationSetGenerator{
{
List: &v1alpha1.ListGenerator{
Elements: []apiextensionsv1.JSON{
{
Raw: []byte(`{"cluster": "my-cluster","url": "https://kubernetes.default.svc"}`),
},
{
Raw: []byte(`{"invalidCluster": "invalid-cluster","url": "https://kubernetes.default.svc"}`),
},
},
},
},
},
},
}).Then().Expect(ApplicationsExist([]argov1alpha1.Application{expectedApp})).
// verify the ApplicationSet status conditions were set correctly
Expect(ApplicationSetHasConditions("simple-list-generator", expectedConditionsParamsError)).
// Delete the ApplicationSet, and verify it deletes the Applications
When().
Delete().Then().Expect(ApplicationsDoNotExist([]argov1alpha1.Application{expectedApp}))
}
func TestRenderHelmValuesObject(t *testing.T) {
expectedApp := argov1alpha1.Application{
TypeMeta: metav1.TypeMeta{

View File

@@ -469,6 +469,11 @@ func (a *Actions) SetTrackingMethod(trackingMethod string) *Actions {
return a
}
func (a *Actions) SetInstallationID(installationID string) *Actions {
fixture.SetInstallationID(installationID)
return a
}
func (a *Actions) SetTrackingLabel(trackingLabel string) *Actions {
fixture.SetTrackingLabel(trackingLabel)
return a

View File

@@ -362,6 +362,11 @@ func (c *Context) SetTrackingMethod(trackingMethod string) *Context {
return c
}
func (c *Context) SetInstallationID(installationID string) *Context {
fixture.SetTrackingMethod(installationID)
return c
}
func (c *Context) GetTrackingMethod() v1alpha1.TrackingMethod {
return c.trackingMethod
}

View File

@@ -403,6 +403,13 @@ func SetResourceOverrides(overrides map[string]v1alpha1.ResourceOverride) {
SetResourceOverridesSplitKeys(overrides)
}
func SetInstallationID(installationID string) {
updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
cm.Data["installationID"] = installationID
return nil
})
}
func SetTrackingMethod(trackingMethod string) {
updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
cm.Data["application.resourceTrackingMethod"] = trackingMethod

View File

@@ -121,7 +121,7 @@
"ts-node": "10.9.2",
"typescript": "^4.9.5",
"typescript-eslint": "^7.8.0",
"webpack": "^5.84.1",
"webpack": "^5.94.0",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.7.4",
"yarn": "^1.22.21"

View File

@@ -119,7 +119,11 @@ export const ApplicationCreatePanel = (props: {
} else {
setDestFormat('URL');
}
}, []);
return () => {
debouncedOnAppChanged.cancel();
};
}, [debouncedOnAppChanged]);
function normalizeTypeFields(formApi: FormApi, type: models.AppSourceType) {
const appToNormalize = formApi.getFormState().values;

View File

@@ -153,7 +153,9 @@ export const ApplicationDeploymentHistory = ({
</React.Fragment>
))
)
) : null}
) : (
<p>Click to see source details.</p>
)}
</div>
</div>
))}

View File

@@ -5,7 +5,7 @@ import {ApplicationSource, RevisionMetadata, ChartDetails} from '../../../shared
import {services} from '../../../shared/services';
export const RevisionMetadataRows = (props: {applicationName: string; applicationNamespace: string; source: ApplicationSource; index: number; versionId: number}) => {
if (props.source.chart) {
if (props?.source?.chart) {
return (
<DataLoader
input={props}

Some files were not shown because too many files have changed in this diff Show More