Compare commits

...

26 Commits

Author SHA1 Message Date
github-actions[bot]
c279299281 Bump version to 2.8.4 (#15493)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: jannfis <jannfis@users.noreply.github.com>
2023-09-13 15:11:00 -04:00
gcp-cherry-pick-bot[bot]
24f8d82954 chore(deps): bump github.com/cyphar/filepath-securejoin (#15401) (#15492)
Bumps [github.com/cyphar/filepath-securejoin](https://github.com/cyphar/filepath-securejoin) from 0.2.3 to 0.2.4.
- [Release notes](https://github.com/cyphar/filepath-securejoin/releases)
- [Commits](https://github.com/cyphar/filepath-securejoin/compare/v0.2.3...v0.2.4)

---
updated-dependencies:
- dependency-name: github.com/cyphar/filepath-securejoin
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-13 13:59:11 -04:00
gcp-cherry-pick-bot[bot]
1ab570ad96 fix(controller): make managed namespaces more 'prune-proof' (#13999) (#15488)
* fix: make managed namespaces more 'prune-proof'

In the cases where someone would want to set resource tracking on a
managed namespace, or if someone would want to migrate from having a
namespace in Git to using `managedNamespaceMetadata`, we need to take
steps to ensure that the namespace does not get pruned.

In order to do that, we add the live namespace to the list of
`targetObjs` (so that it's considered a part of the source even though
it's not).

Finally, we need to also ensure that the managed namespace is not
considered `OutOfSync` (due to the same reason as above).

This is a subset of #11350, the main difference being that this commit
does not set resource tracking on its own, but can be opted into if
people choose to do so.



* refactor: extract managed namespace check



---------

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2023-09-13 13:43:29 -04:00
gcp-cherry-pick-bot[bot]
8612a99fe8 fix: Gitlab scm_provider - don't create transport from scratch (#15424) (#15425) (#15471) 2023-09-13 13:35:07 -04:00
jannfis
33fa128bad fix: Allow retrieving badges in other namespaces (#15468) (#15482)
Signed-off-by: jannfis <jann@mistrust.net>
2023-09-13 13:01:05 -04:00
gcp-cherry-pick-bot[bot]
4e79aab03d fix: extends CR to allow cronjob/workflow triggers (#15300) (#15487)
Signed-off-by: Marcelo Moreira de Mello <tchello.mello@gmail.com>
Co-authored-by: Marcelo Mello <tchello.mello@gmail.com>
Co-authored-by: Nicholas Morey <nicholas@morey.tech>
2023-09-13 12:15:23 -04:00
gcp-cherry-pick-bot[bot]
7b89ae4424 fix: Stop appending :443 to the server address when using grpc-web (#15435) (#15470)
Signed-off-by: David Marby <david@dmarby.se>
Co-authored-by: David Marby <david@dmarby.se>
Co-authored-by: jannfis <jann@mistrust.net>
2023-09-12 19:13:05 -04:00
gcp-cherry-pick-bot[bot]
2577d51796 fix(appsets): gotemplate can cause panic from nil dereference (#15377) (#15378) (#15434)
* fix(appsets): gotemplate can cause panic from nil deference



* fix(appsets): gotemplate can cause panic from nil dereference



---------

Signed-off-by: rumstead <37445536+rumstead@users.noreply.github.com>
Co-authored-by: rumstead <37445536+rumstead@users.noreply.github.com>
2023-09-08 18:58:22 -04:00
gcp-cherry-pick-bot[bot]
2f8533efeb fix: handle annotations for resources with ':' in the name (#15101) (#15380) (#15414)
* fix: handle annotations for resources with ':' in the name



* fix: switch to using splitN for handling annotation splitting



* fix: check len(parts) !=3



* Retrigger CI pipeline



---------

Signed-off-by: Oreon Lothamer <oreon.lothamer@softrams.com>
Co-authored-by: Oreon Lothamer <73498677+oreonl@users.noreply.github.com>
2023-09-08 13:56:18 -04:00
gcp-cherry-pick-bot[bot]
c698426a5c fix(appset): Revert applicationset-name labels (#15324) (#15422)
* fix(15282) Revert applicationset-name labels



* fix(15282) fix unit test



---------

Signed-off-by: gmuselli <geoffrey.muselli@gmail.com>
Co-authored-by: Geoffrey MUSELLI <geoffrey.muselli@gmail.com>
2023-09-08 10:00:12 -04:00
github-actions[bot]
77556d9e64 Bump version to 2.8.3 (#15404)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: pasha-codefresh <pasha-codefresh@users.noreply.github.com>
2023-09-07 17:59:35 +03:00
Alexander Matyushentsev
1391ba7214 Merge pull request from GHSA-fwr2-64vr-xv9m
* fix: prevent seeing/editing 'kubectl.kubernetes.io/last-applied-configuration' cluster annotation

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>

* Update util/db/cluster_test.go

Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>

---------

Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-09-07 10:14:57 -04:00
pasha-codefresh
44e52c4ae7 Merge pull request from GHSA-g687-f2gx-6wm8
* feat: use untar with limiter

Signed-off-by: pashakostohrys <pavel@codefresh.io>

* feat: use untar with limiter

Signed-off-by: pashakostohrys <pavel@codefresh.io>

---------

Signed-off-by: pashakostohrys <pavel@codefresh.io>
2023-09-07 10:12:15 -04:00
gcp-cherry-pick-bot[bot]
4b55084a8f docs: Update ApplicationSet docs (#15269) (#15270)
Signed-off-by: David Muckle <dvdmuckle@dvdmuckle.xyz>
Co-authored-by: David Muckle <dvdmuckle@dvdmuckle.xyz>
2023-08-28 20:36:18 -04:00
gcp-cherry-pick-bot[bot]
c689ea4957 docs: improve doc on labels parameter on scmProvider generator (#15255) (#15265)
Signed-off-by: Jedrzej Kotkowski <jedrzejk143@gmail.com>
Co-authored-by: Jędrzej Kotkowski <96917147+jjsiv@users.noreply.github.com>
2023-08-28 17:48:58 -04:00
gcp-cherry-pick-bot[bot]
ea9827791f fix(ui): Helm chart empty maintainers blow up Argo UI (#15225) (#15243)
Signed-off-by: Carlos Castro carlos.castro@jumo.world

Signed-off-by: Carlos Castro carlos.castro@jumo.world

Signed-off-by: Carlos Castro carlos.castro@jumo.world
Signed-off-by: Carlos Castro <carlos.castro@jumo.world>
Co-authored-by: Carlos Castro <carlos.castro@jumo.world>
2023-08-25 09:59:33 -04:00
github-actions[bot]
dbdfc71270 Bump version to 2.8.2 (#15232)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: crenshaw-dev <crenshaw-dev@users.noreply.github.com>
2023-08-24 16:03:15 -04:00
gcp-cherry-pick-bot[bot]
31473491f5 fix(ui): Update default and max count for maxCookieNumber (#14979) (#15230)
* Update default and max count for maxCookieNumber

Signed-off-by: zvlb <vl.zemtsov@gmail.com>
Co-authored-by: Vladimir <31961982+zvlb@users.noreply.github.com>
2023-08-24 15:16:01 -04:00
gcp-cherry-pick-bot[bot]
016c2f25cd docs: document sourceNamespaces field (#15195) (#15212)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-08-24 11:30:55 -04:00
gcp-cherry-pick-bot[bot]
31e6e28ca4 chore: add example jq path expression (#15130) (#15209)
Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
2023-08-24 11:29:53 -04:00
gcp-cherry-pick-bot[bot]
f07088281b fix: requeue ApplicationSet if there are validation errors (#14429) (#15206)
Signed-off-by: Chetan Banavikalmutt <chetanrns1997@gmail.com>
Co-authored-by: Chetan Banavikalmutt <chetanrns1997@gmail.com>
2023-08-24 11:29:05 -04:00
gcp-cherry-pick-bot[bot]
bf77b09b7b fix(appset): bitbucket server scm provider EOF on empty repo (#14411) (#15203)
* fix bitbucket server scm provider EOF on empty repo default branch check



* add unit test for bitbucketServer empty repo



* check for EOF explicitly



---------

Signed-off-by: Jedrzej Kotkowski <jedrzejk143@gmail.com>
Co-authored-by: jjsiv <96917147+jjsiv@users.noreply.github.com>
2023-08-24 11:28:21 -04:00
gcp-cherry-pick-bot[bot]
e982e0b80e fix(health): spec.executor.instances is Optional, Support a flexible number of executors (#11877) (#15200)
Support a flexible number of executors. For example with a mounted ConfigMap inside the Spark-Operator.

Signed-off-by: Philipp Dallig <philipp.dallig@gmail.com>
Co-authored-by: Philipp Dallig <philipp.dallig@gmail.com>
2023-08-24 11:27:53 -04:00
gcp-cherry-pick-bot[bot]
3a468c6862 fix(ui): switch podgroup notification to tooltip message (#14821) (#15224)
* improve pod grouping ux



fix: update log view on container select



* fix(ui): improve pod grouping ux



* fix(ui):update the pod grouping messages to tooltip



* fix(ui):update the pod grouping messages to tooltip



* fix: GroupNodes notification



* fix: GroupNodes notification



---------

Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>
Signed-off-by: AS <11219262+ashutosh16@users.noreply.github.com>
Co-authored-by: AS <11219262+ashutosh16@users.noreply.github.com>
2023-08-24 11:14:42 -04:00
gcp-cherry-pick-bot[bot]
383f2a288b fix: stop creating new otel interceptor to avoid memory leak (#15174) (#15178)
Signed-off-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
Co-authored-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2023-08-22 20:09:28 -07:00
gcp-cherry-pick-bot[bot]
e006908fe9 fix(appset): Fix helm valuesObject with ApplicationSet (#14912) (#14920) (#15175)
Signed-off-by: Geoffrey Muselli <geoffrey.muselli@gmail.com>
Co-authored-by: Geoffrey MUSELLI <geoffrey.muselli@gmail.com>
2023-08-22 16:15:57 -07:00
69 changed files with 1144 additions and 417 deletions

View File

@@ -1 +1 @@
2.8.1
2.8.4

View File

@@ -58,10 +58,6 @@ const (
// https://github.com/argoproj-labs/argocd-notifications/blob/33d345fa838829bb50fca5c08523aba380d2c12b/pkg/controller/state.go#L17
NotifiedAnnotationKey = "notified.notifications.argoproj.io"
ReconcileRequeueOnValidationError = time.Minute * 3
// LabelKeyAppSetInstance is the label key to use to uniquely identify the apps of an applicationset
// The ArgoCD applicationset name is used as the instance name
LabelKeyAppSetInstance = "argocd.argoproj.io/application-set-name"
)
var (
@@ -295,7 +291,6 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
}
requeueAfter := r.getMinRequeueAfter(&applicationSetInfo)
logCtx.WithField("requeueAfter", requeueAfter).Info("end reconcile")
if len(validateErrors) == 0 {
if err := r.setApplicationSetStatusCondition(ctx,
@@ -309,8 +304,13 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
); err != nil {
return ctrl.Result{}, err
}
} else if requeueAfter == time.Duration(0) {
// Ensure that the request is requeued if there are validation errors.
requeueAfter = ReconcileRequeueOnValidationError
}
logCtx.WithField("requeueAfter", requeueAfter).Info("end reconcile")
return ctrl.Result{
RequeueAfter: requeueAfter,
}, nil
@@ -512,10 +512,6 @@ func (r *ApplicationSetReconciler) generateApplications(applicationSetInfo argov
for _, a := range t {
tmplApplication := getTempApplication(a.Template)
if tmplApplication.Labels == nil {
tmplApplication.Labels = make(map[string]string)
}
tmplApplication.Labels[LabelKeyAppSetInstance] = applicationSetInfo.Name
for _, p := range a.Params {
app, err := r.Renderer.RenderTemplateParams(tmplApplication, applicationSetInfo.Spec.SyncPolicy, p, applicationSetInfo.Spec.GoTemplate, applicationSetInfo.Spec.GoTemplateOptions)

View File

@@ -165,9 +165,6 @@ func TestExtractApplications(t *testing.T) {
if cc.generateParamsError == nil {
for _, p := range cc.params {
tmpApplication := getTempApplication(cc.template)
tmpApplication.Labels[LabelKeyAppSetInstance] = appSet.Name
if cc.rendererError != nil {
rendererMock.On("RenderTemplateParams", getTempApplication(cc.template), p, false, []string(nil)).
Return(nil, cc.rendererError)
@@ -289,21 +286,7 @@ func TestMergeTemplateApplications(t *testing.T) {
rendererMock := rendererMock{}
appSet := &v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{generator},
Template: cc.template,
},
}
tmpApplication := getTempApplication(cc.expectedMerged)
tmpApplication.Labels[LabelKeyAppSetInstance] = appSet.Name
rendererMock.On("RenderTemplateParams", tmpApplication, cc.params[0], false, []string(nil)).
rendererMock.On("RenderTemplateParams", getTempApplication(cc.expectedMerged), cc.params[0], false, []string(nil)).
Return(&cc.expectedApps[0], nil)
r := ApplicationSetReconciler{
@@ -317,7 +300,17 @@ func TestMergeTemplateApplications(t *testing.T) {
KubeClientset: kubefake.NewSimpleClientset(),
}
got, _, _ := r.generateApplications(*appSet)
got, _, _ := r.generateApplications(v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: v1alpha1.ApplicationSetSpec{
Generators: []v1alpha1.ApplicationSetGenerator{generator},
Template: cc.template,
},
},
)
assert.Equal(t, cc.expectedApps, got)
})
@@ -2003,7 +1996,7 @@ func TestReconcilerValidationErrorBehaviour(t *testing.T) {
// Verify that on validation error, no error is returned, but the object is requeued
res, err := r.Reconcile(context.Background(), req)
assert.Nil(t, err)
assert.True(t, res.RequeueAfter == 0)
assert.True(t, res.RequeueAfter == ReconcileRequeueOnValidationError)
var app v1alpha1.Application
@@ -2169,7 +2162,7 @@ func applicationsUpdateSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alp
assert.Nil(t, err)
retrievedApplicationSet.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"}
retrievedApplicationSet.Spec.Template.Labels = map[string]string{"argocd.argoproj.io/application-set-name": "name", "label-key": "label-value"}
retrievedApplicationSet.Spec.Template.Labels = map[string]string{"label-key": "label-value"}
retrievedApplicationSet.Spec.Template.Spec.Source.Helm = &v1alpha1.ApplicationSourceHelm{
Values: "global.test: test",
@@ -2197,7 +2190,6 @@ func TestUpdateNotPerformedWithSyncPolicyCreateOnly(t *testing.T) {
assert.Nil(t, app.Spec.Source.Helm)
assert.Nil(t, app.ObjectMeta.Annotations)
assert.Equal(t, map[string]string{"argocd.argoproj.io/application-set-name": "name"}, app.ObjectMeta.Labels)
}
func TestUpdateNotPerformedWithSyncPolicyCreateDelete(t *testing.T) {
@@ -2208,7 +2200,6 @@ func TestUpdateNotPerformedWithSyncPolicyCreateDelete(t *testing.T) {
assert.Nil(t, app.Spec.Source.Helm)
assert.Nil(t, app.ObjectMeta.Annotations)
assert.Equal(t, map[string]string{"argocd.argoproj.io/application-set-name": "name"}, app.ObjectMeta.Labels)
}
func TestUpdatePerformedWithSyncPolicyCreateUpdate(t *testing.T) {
@@ -2219,7 +2210,7 @@ func TestUpdatePerformedWithSyncPolicyCreateUpdate(t *testing.T) {
assert.Equal(t, "global.test: test", app.Spec.Source.Helm.Values)
assert.Equal(t, map[string]string{"annotation-key": "annotation-value"}, app.ObjectMeta.Annotations)
assert.Equal(t, map[string]string{"argocd.argoproj.io/application-set-name": "name", "label-key": "label-value"}, app.ObjectMeta.Labels)
assert.Equal(t, map[string]string{"label-key": "label-value"}, app.ObjectMeta.Labels)
}
func TestUpdatePerformedWithSyncPolicySync(t *testing.T) {
@@ -2230,7 +2221,7 @@ func TestUpdatePerformedWithSyncPolicySync(t *testing.T) {
assert.Equal(t, "global.test: test", app.Spec.Source.Helm.Values)
assert.Equal(t, map[string]string{"annotation-key": "annotation-value"}, app.ObjectMeta.Annotations)
assert.Equal(t, map[string]string{"argocd.argoproj.io/application-set-name": "name", "label-key": "label-value"}, app.ObjectMeta.Labels)
assert.Equal(t, map[string]string{"label-key": "label-value"}, app.ObjectMeta.Labels)
}
func TestUpdatePerformedWithSyncPolicyCreateOnlyAndAllowPolicyOverrideFalse(t *testing.T) {
@@ -2241,7 +2232,7 @@ func TestUpdatePerformedWithSyncPolicyCreateOnlyAndAllowPolicyOverrideFalse(t *t
assert.Equal(t, "global.test: test", app.Spec.Source.Helm.Values)
assert.Equal(t, map[string]string{"annotation-key": "annotation-value"}, app.ObjectMeta.Annotations)
assert.Equal(t, map[string]string{"argocd.argoproj.io/application-set-name": "name", "label-key": "label-value"}, app.ObjectMeta.Labels)
assert.Equal(t, map[string]string{"label-key": "label-value"}, app.ObjectMeta.Labels)
}
func applicationsDeleteSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alpha1.ApplicationsSyncPolicy, recordBuffer int, allowPolicyOverride bool) v1alpha1.ApplicationList {
@@ -2449,8 +2440,7 @@ func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{
Name: "AppSet-branch1-1",
Labels: map[string]string{
"app1": "label1",
LabelKeyAppSetInstance: "",
"app1": "label1",
},
},
Spec: v1alpha1.ApplicationSpec{

View File

@@ -153,7 +153,7 @@ func InterpolateGenerator(requestedGenerator *argoprojiov1alpha1.ApplicationSetG
interpolatedGenerator, err := render.RenderGeneratorParams(requestedGenerator, params, useGoTemplate, goTemplateOptions)
if err != nil {
log.WithError(err).WithField("interpolatedGenerator", interpolatedGenerator).Error("error interpolating generator with other generator's parameter")
return *interpolatedGenerator, err
return argoprojiov1alpha1.ApplicationSetGenerator{}, err
}
return *interpolatedGenerator, nil

View File

@@ -501,3 +501,60 @@ func TestInterpolateGenerator_go(t *testing.T) {
assert.Equal(t, "production_01/west", interpolatedGenerator.Git.Files[0].Path)
assert.Equal(t, "https://production-01.example.com", interpolatedGenerator.Git.Files[1].Path)
}
func TestInterpolateGeneratorError(t *testing.T) {
type args struct {
requestedGenerator *argov1alpha1.ApplicationSetGenerator
params map[string]interface{}
useGoTemplate bool
goTemplateOptions []string
}
tests := []struct {
name string
args args
want argov1alpha1.ApplicationSetGenerator
expectedErrStr string
}{
{name: "Empty Gen", args: args{
requestedGenerator: nil,
params: nil,
useGoTemplate: false,
goTemplateOptions: nil,
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: "generator is empty"},
{name: "No Params", args: args{
requestedGenerator: &argov1alpha1.ApplicationSetGenerator{},
params: map[string]interface{}{},
useGoTemplate: false,
goTemplateOptions: nil,
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: ""},
{name: "Error templating", args: args{
requestedGenerator: &argov1alpha1.ApplicationSetGenerator{Git: &argov1alpha1.GitGenerator{
RepoURL: "foo",
Files: []argov1alpha1.GitFileGeneratorItem{{Path: "bar/"}},
Revision: "main",
Values: map[string]string{
"git_test": "{{ toPrettyJson . }}",
"selection": "{{ default .override .test }}",
"resolved": "{{ index .rmap (default .override .test) }}",
},
}},
params: map[string]interface{}{
"name": "in-cluster",
"override": "foo",
},
useGoTemplate: true,
goTemplateOptions: []string{},
}, want: argov1alpha1.ApplicationSetGenerator{}, expectedErrStr: "failed to replace parameters in generator: failed to execute go template {{ index .rmap (default .override .test) }}: template: :1:3: executing \"\" at <index .rmap (default .override .test)>: error calling index: index of untyped nil"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := InterpolateGenerator(tt.args.requestedGenerator, tt.args.params, tt.args.useGoTemplate, tt.args.goTemplateOptions)
if tt.expectedErrStr != "" {
assert.EqualError(t, err, tt.expectedErrStr)
} else {
require.NoError(t, err)
}
assert.Equalf(t, tt.want, got, "InterpolateGenerator(%v, %v, %v, %v)", tt.args.requestedGenerator, tt.args.params, tt.args.useGoTemplate, tt.args.goTemplateOptions)
})
}
}

View File

@@ -26,11 +26,13 @@ func NewGiteaService(ctx context.Context, token, url, owner, repo string, insecu
if insecure {
cookieJar, _ := cookiejar.New(nil)
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
httpClient = &http.Client{
Jar: cookieJar,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}}
Jar: cookieJar,
Transport: tr,
}
}
client, err := gitea.NewClient(url, gitea.SetToken(token), gitea.SetHTTPClient(httpClient))
if err != nil {

View File

@@ -32,9 +32,9 @@ func NewGitLabService(ctx context.Context, token, url, project string, labels []
token = os.Getenv("GITLAB_TOKEN")
}
tr := &http.Transport{
TLSClientConfig: utils.GetTlsConfig(scmRootCAPath, insecure),
}
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.TLSClientConfig = utils.GetTlsConfig(scmRootCAPath, insecure)
retryClient := retryablehttp.NewClient()
retryClient.HTTPClient.Transport = tr

View File

@@ -3,6 +3,7 @@ package scm_provider
import (
"context"
"fmt"
"io"
"github.com/argoproj/argo-cd/v2/applicationset/utils"
bitbucketv1 "github.com/gfleury/go-bitbucket-v1"
@@ -183,8 +184,9 @@ func (b *BitbucketServerProvider) listBranches(repo *Repository) ([]bitbucketv1.
func (b *BitbucketServerProvider) getDefaultBranch(org string, repo string) (*bitbucketv1.Branch, error) {
response, err := b.client.DefaultApi.GetDefaultBranch(org, repo)
if response != nil && response.StatusCode == 404 {
// There's no default branch i.e. empty repo, not an error
// The API will return 404 if a default branch is set but doesn't exist. In case the repo is empty and default branch is unset,
// we will get an EOF and a nil response.
if (response != nil && response.StatusCode == 404) || (response == nil && err == io.EOF) {
return nil, nil
}
if err != nil {

View File

@@ -365,6 +365,28 @@ func TestGetBranchesMissingDefault(t *testing.T) {
assert.Empty(t, repos)
}
func TestGetBranchesEmptyRepo(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Empty(t, r.Header.Get("Authorization"))
switch r.RequestURI {
case "/rest/api/1.0/projects/PROJECT/repos/REPO/branches/default":
return
}
}))
defer ts.Close()
provider, err := NewBitbucketServerProviderNoAuth(context.Background(), ts.URL, "PROJECT", false)
assert.NoError(t, err)
repos, err := provider.GetBranches(context.Background(), &Repository{
Organization: "PROJECT",
Repository: "REPO",
URL: "ssh://git@mycompany.bitbucket.org/PROJECT/REPO.git",
Labels: []string{},
RepositoryId: 1,
})
assert.Empty(t, repos)
assert.NoError(t, err)
}
func TestGetBranchesErrorDefaultBranch(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Empty(t, r.Header.Get("Authorization"))

View File

@@ -27,11 +27,13 @@ func NewGiteaProvider(ctx context.Context, owner, token, url string, allBranches
if insecure {
cookieJar, _ := cookiejar.New(nil)
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
httpClient = &http.Client{
Jar: cookieJar,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}}
Jar: cookieJar,
Transport: tr,
}
}
client, err := gitea.NewClient(url, gitea.SetToken(token), gitea.SetHTTPClient(httpClient))
if err != nil {

View File

@@ -28,9 +28,9 @@ func NewGitlabProvider(ctx context.Context, organization string, token string, u
}
var client *gitlab.Client
tr := &http.Transport{
TLSClientConfig: utils.GetTlsConfig(scmRootCAPath, insecure),
}
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.TLSClientConfig = utils.GetTlsConfig(scmRootCAPath, insecure)
retryClient := retryablehttp.NewClient()
retryClient.HTTPClient.Transport = tr

View File

@@ -17,6 +17,7 @@ import (
"github.com/Masterminds/sprig/v3"
"github.com/valyala/fasttemplate"
"sigs.k8s.io/yaml"
log "github.com/sirupsen/logrus"
@@ -51,6 +52,22 @@ func copyUnexported(copy, original reflect.Value) {
copyValueIntoUnexported(copy, unexported)
}
func IsJSONStr(str string) bool {
str = strings.TrimSpace(str)
return len(str) > 0 && str[0] == '{'
}
func ConvertYAMLToJSON(str string) (string, error) {
if !IsJSONStr(str) {
jsonStr, err := yaml.YAMLToJSON([]byte(str))
if err != nil {
return str, err
}
return string(jsonStr), nil
}
return str, nil
}
// This function is in charge of searching all String fields of the object recursively and apply templating
// thanks to https://gist.github.com/randallmlough/1fd78ec8a1034916ca52281e3b886dc7
func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[string]interface{}, useGoTemplate bool, goTemplateOptions []string) error {
@@ -86,11 +103,18 @@ func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[stri
originalValue := original.Elem()
// Create a new object. Now new gives us a pointer, but we want the value it
// points to, so we have to call Elem() to unwrap it
copyValue := reflect.New(originalValue.Type()).Elem()
if err := r.deeplyReplace(copyValue, originalValue, replaceMap, useGoTemplate, goTemplateOptions); err != nil {
return err
if originalValue.IsValid() {
reflectType := originalValue.Type()
reflectValue := reflect.New(reflectType)
copyValue := reflectValue.Elem()
if err := r.deeplyReplace(copyValue, originalValue, replaceMap, useGoTemplate, goTemplateOptions); err != nil {
return err
}
copy.Set(copyValue)
}
copy.Set(copyValue)
// If it is a struct we translate each field
case reflect.Struct:
@@ -99,10 +123,14 @@ func (r *Render) deeplyReplace(copy, original reflect.Value, replaceMap map[stri
// specific case time
if currentType == "time.Time" {
copy.Field(i).Set(original.Field(i))
} else if currentType == "Raw.k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" {
} else if currentType == "Raw.k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" || currentType == "Raw.k8s.io/apimachinery/pkg/runtime" {
var unmarshaled interface{}
originalBytes := original.Field(i).Bytes()
err := json.Unmarshal(originalBytes, &unmarshaled)
convertedToJson, err := ConvertYAMLToJSON(string(originalBytes))
if err != nil {
return fmt.Errorf("error while converting template to json %q: %w", convertedToJson, err)
}
err = json.Unmarshal([]byte(convertedToJson), &unmarshaled)
if err != nil {
return fmt.Errorf("failed to unmarshal JSON field: %w", err)
}

View File

@@ -2,6 +2,7 @@ package utils
import (
"crypto/x509"
"encoding/json"
"os"
"path"
"testing"
@@ -198,6 +199,113 @@ func TestRenderTemplateParams(t *testing.T) {
}
func TestRenderHelmValuesObjectJson(t *testing.T) {
params := map[string]interface{}{
"test": "Hello world",
}
application := &argoappsv1.Application{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"annotation-key": "annotation-value", "annotation-key2": "annotation-value2"},
Labels: map[string]string{"label-key": "label-value", "label-key2": "label-value2"},
CreationTimestamp: metav1.NewTime(time.Now()),
UID: types.UID("d546da12-06b7-4f9a-8ea2-3adb16a20e2b"),
Name: "application-one",
Namespace: "default",
},
Spec: argoappsv1.ApplicationSpec{
Source: &argoappsv1.ApplicationSource{
Path: "",
RepoURL: "",
TargetRevision: "",
Chart: "",
Helm: &argoappsv1.ApplicationSourceHelm{
ValuesObject: &runtime.RawExtension{
Raw: []byte(`{
"some": {
"string": "{{.test}}"
}
}`),
},
},
},
Destination: argoappsv1.ApplicationDestination{
Server: "",
Namespace: "",
Name: "",
},
Project: "",
},
}
// Render the cloned application, into a new application
render := Render{}
newApplication, err := render.RenderTemplateParams(application, nil, params, true, []string{})
assert.NoError(t, err)
assert.NotNil(t, newApplication)
var unmarshaled interface{}
err = json.Unmarshal(newApplication.Spec.Source.Helm.ValuesObject.Raw, &unmarshaled)
assert.NoError(t, err)
assert.Equal(t, unmarshaled.(map[string]interface{})["some"].(map[string]interface{})["string"], "Hello world")
}
func TestRenderHelmValuesObjectYaml(t *testing.T) {
params := map[string]interface{}{
"test": "Hello world",
}
application := &argoappsv1.Application{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{"annotation-key": "annotation-value", "annotation-key2": "annotation-value2"},
Labels: map[string]string{"label-key": "label-value", "label-key2": "label-value2"},
CreationTimestamp: metav1.NewTime(time.Now()),
UID: types.UID("d546da12-06b7-4f9a-8ea2-3adb16a20e2b"),
Name: "application-one",
Namespace: "default",
},
Spec: argoappsv1.ApplicationSpec{
Source: &argoappsv1.ApplicationSource{
Path: "",
RepoURL: "",
TargetRevision: "",
Chart: "",
Helm: &argoappsv1.ApplicationSourceHelm{
ValuesObject: &runtime.RawExtension{
Raw: []byte(`some:
string: "{{.test}}"`),
},
},
},
Destination: argoappsv1.ApplicationDestination{
Server: "",
Namespace: "",
Name: "",
},
Project: "",
},
}
// Render the cloned application, into a new application
render := Render{}
newApplication, err := render.RenderTemplateParams(application, nil, params, true, []string{})
assert.NoError(t, err)
assert.NotNil(t, newApplication)
var unmarshaled interface{}
err = json.Unmarshal(newApplication.Spec.Source.Helm.ValuesObject.Raw, &unmarshaled)
assert.NoError(t, err)
assert.Equal(t, unmarshaled.(map[string]interface{})["some"].(map[string]interface{})["string"], "Hello world")
}
func TestRenderTemplateParamsGoTemplate(t *testing.T) {
// Believe it or not, this is actually less complex than the equivalent solution using reflection

View File

@@ -82,6 +82,8 @@ func NewCommand() *cobra.Command {
allowOutOfBoundsSymlinks bool
streamedManifestMaxTarSize string
streamedManifestMaxExtractedSize string
helmManifestMaxExtractedSize string
disableManifestMaxExtractedSize bool
)
var command = cobra.Command{
Use: cliName,
@@ -120,6 +122,9 @@ func NewCommand() *cobra.Command {
streamedManifestMaxExtractedSizeQuantity, err := resource.ParseQuantity(streamedManifestMaxExtractedSize)
errors.CheckError(err)
helmManifestMaxExtractedSizeQuantity, err := resource.ParseQuantity(helmManifestMaxExtractedSize)
errors.CheckError(err)
askPassServer := askpass.NewServer()
metricsServer := metrics.NewMetricsServer()
cacheutil.CollectMetrics(redisClient, metricsServer)
@@ -134,6 +139,7 @@ func NewCommand() *cobra.Command {
AllowOutOfBoundsSymlinks: allowOutOfBoundsSymlinks,
StreamedManifestMaxExtractedSize: streamedManifestMaxExtractedSizeQuantity.ToDec().Value(),
StreamedManifestMaxTarSize: streamedManifestMaxTarSizeQuantity.ToDec().Value(),
HelmManifestMaxExtractedSize: helmManifestMaxExtractedSizeQuantity.ToDec().Value(),
}, askPassServer)
errors.CheckError(err)
@@ -216,6 +222,8 @@ func NewCommand() *cobra.Command {
command.Flags().BoolVar(&allowOutOfBoundsSymlinks, "allow-oob-symlinks", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_ALLOW_OUT_OF_BOUNDS_SYMLINKS", false), "Allow out-of-bounds symlinks in repositories (not recommended)")
command.Flags().StringVar(&streamedManifestMaxTarSize, "streamed-manifest-max-tar-size", env.StringFromEnv("ARGOCD_REPO_SERVER_STREAMED_MANIFEST_MAX_TAR_SIZE", "100M"), "Maximum size of streamed manifest archives")
command.Flags().StringVar(&streamedManifestMaxExtractedSize, "streamed-manifest-max-extracted-size", env.StringFromEnv("ARGOCD_REPO_SERVER_STREAMED_MANIFEST_MAX_EXTRACTED_SIZE", "1G"), "Maximum size of streamed manifest archives when extracted")
command.Flags().StringVar(&helmManifestMaxExtractedSize, "helm-manifest-max-extracted-size", env.StringFromEnv("ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE", "1G"), "Maximum size of helm manifest archives when extracted")
command.Flags().BoolVar(&disableManifestMaxExtractedSize, "disable-helm-manifest-max-extracted-size", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE", false), "Disable maximum size of helm manifest archives when extracted")
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(&command)
cacheSrc = reposervercache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
redisClient = client

View File

@@ -7,7 +7,6 @@ import (
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
@@ -47,8 +46,8 @@ func NewConnection(address string) (*grpc.ClientConn, error) {
grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor(retryOpts...)),
grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(unaryInterceptors...)),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxGRPCMessageSize), grpc.MaxCallSendMsgSize(MaxGRPCMessageSize)),
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()),
grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()),
grpc.WithUnaryInterceptor(grpc_util.OTELUnaryClientInterceptor()),
grpc.WithStreamInterceptor(grpc_util.OTELStreamClientInterceptor()),
}
dialOpts = append(dialOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))

View File

@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
v1 "k8s.io/api/core/v1"
"reflect"
"strings"
"time"
@@ -335,6 +336,10 @@ func verifyGnuPGSignature(revision string, project *v1alpha1.AppProject, manifes
return conditions
}
func isManagedNamespace(ns *unstructured.Unstructured, app *v1alpha1.Application) bool {
return ns != nil && ns.GetKind() == kubeutil.NamespaceKind && ns.GetName() == app.Spec.Destination.Namespace && app.Spec.SyncPolicy != nil && app.Spec.SyncPolicy.ManagedNamespaceMetadata != nil
}
// CompareAppState compares application git state to the live app state, using the specified
// revision and supplied source. If revision or overrides are empty, then compares against
// revision and overrides in the app spec.
@@ -494,6 +499,35 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
LastTransitionTime: &now,
})
}
// For the case when a namespace is managed with `managedNamespaceMetadata` AND it has resource tracking
// enabled (e.g. someone manually adds resource tracking labels or annotations), we need to do some
// bookkeeping in order to prevent the managed namespace from being pruned.
//
// Live namespaces which are managed namespaces (i.e. application namespaces which are managed with
// CreateNamespace=true and has non-nil managedNamespaceMetadata) will (usually) not have a corresponding
// entry in source control. In order for the namespace not to risk being pruned, we'll need to generate a
// namespace which we can compare the live namespace with. For that, we'll do the same as is done in
// gitops-engine, the difference here being that we create a managed namespace which is only used for comparison.
if isManagedNamespace(liveObj, app) {
nsSpec := &v1.Namespace{TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: kubeutil.NamespaceKind}, ObjectMeta: metav1.ObjectMeta{Name: liveObj.GetName()}}
managedNs, err := kubeutil.ToUnstructured(nsSpec)
if err != nil {
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
failedToLoadObjs = true
continue
}
// No need to care about the return value here, we just want the modified managedNs
_, err = syncNamespace(m.resourceTracking, appLabelKey, trackingMethod, app.Name, app.Spec.SyncPolicy)(managedNs, liveObj)
if err != nil {
conditions = append(conditions, v1alpha1.ApplicationCondition{Type: v1alpha1.ApplicationConditionComparisonError, Message: err.Error(), LastTransitionTime: &now})
failedToLoadObjs = true
} else {
targetObjs = append(targetObjs, managedNs)
}
}
}
}
@@ -588,12 +622,22 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
} else {
diffResult = diff.DiffResult{Modified: false, NormalizedLive: []byte("{}"), PredictedLive: []byte("{}")}
}
// For the case when a namespace is managed with `managedNamespaceMetadata` AND it has resource tracking
// enabled (e.g. someone manually adds resource tracking labels or annotations), we need to do some
// bookkeeping in order to ensure that it's not considered `OutOfSync` (since it does not exist in source
// control).
//
// This is in addition to the bookkeeping we do (see `isManagedNamespace` and its references) to prevent said
// namespace from being pruned.
isManagedNs := isManagedNamespace(targetObj, app) && liveObj == nil
if resState.Hook || ignore.Ignore(obj) || (targetObj != nil && hookutil.Skip(targetObj)) || !isSelfReferencedObj {
// For resource hooks, skipped resources or objects that may have
// been created by another controller with annotations copied from
// the source object, don't store sync status, and do not affect
// overall sync status
} else if diffResult.Modified || targetObj == nil || liveObj == nil {
} else if !isManagedNs && (diffResult.Modified || targetObj == nil || liveObj == nil) {
// Set resource state to OutOfSync since one of the following is true:
// * target and live resource are different
// * target resource not defined and live resource is extra

View File

@@ -433,6 +433,47 @@ func TestCompareAppStateDuplicatedNamespacedResources(t *testing.T) {
assert.Equal(t, 4, len(compRes.resources))
}
func TestCompareAppStateManagedNamespaceMetadataWithLiveNsDoesNotGetPruned(t *testing.T) {
app := newFakeApp()
app.Spec.SyncPolicy = &argoappv1.SyncPolicy{
ManagedNamespaceMetadata: &argoappv1.ManagedNamespaceMetadata{
Labels: nil,
Annotations: nil,
},
}
ns := NewNamespace()
ns.SetName(test.FakeDestNamespace)
ns.SetNamespace(test.FakeDestNamespace)
ns.SetAnnotations(map[string]string{"argocd.argoproj.io/sync-options": "ServerSideApply=true"})
data := fakeData{
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(ns): ns,
},
}
ctrl := newFakeController(&data)
compRes := ctrl.appStateManager.CompareAppState(app, &defaultProj, []string{}, app.Spec.Sources, false, false, nil, false)
assert.NotNil(t, compRes)
assert.Equal(t, 0, len(app.Status.Conditions))
assert.NotNil(t, compRes)
assert.NotNil(t, compRes.syncStatus)
// Ensure that ns does not get pruned
assert.NotNil(t, compRes.reconciliationResult.Target[0])
assert.Equal(t, compRes.reconciliationResult.Target[0].GetName(), ns.GetName())
assert.Equal(t, compRes.reconciliationResult.Target[0].GetAnnotations(), ns.GetAnnotations())
assert.Equal(t, compRes.reconciliationResult.Target[0].GetLabels(), ns.GetLabels())
assert.Len(t, compRes.resources, 1)
assert.Len(t, compRes.managedResources, 1)
}
var defaultProj = argoappv1.AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: "default",

View File

@@ -214,6 +214,9 @@ spec:
kind: Deployment
jsonPointers:
- /spec/replicas
- kind: ConfigMap
jqPathExpressions:
- '.data["config.yaml"].auth'
# for the specified managedFields managers
- group: "*"
kind: "*"

View File

@@ -38,16 +38,16 @@ With the ApplicationSet v0.1.0 release, one could *only* specify `url` and `clus
spec:
generators:
- list:
elements:
# v0.1.0 form - requires cluster/url keys:
- cluster: engineering-dev
url: https://kubernetes.default.svc
values:
additional: value
# v0.2.0+ form - does not require cluster/URL keys
# (but they are still supported).
- staging: "true"
gitRepo: https://kubernetes.default.svc
elements:
# v0.1.0 form - requires cluster/url keys:
- cluster: engineering-dev
url: https://kubernetes.default.svc
values:
additional: value
# v0.2.0+ form - does not require cluster/URL keys
# (but they are still supported).
- staging: "true"
gitRepo: https://kubernetes.default.svc
# (...)
```
@@ -74,7 +74,6 @@ spec:
files:
- path: applicationset/examples/list-generator/list-elementsYaml-example.yaml
- list:
elements: []
elementsYaml: "{{ .key.components | toJson }}"
template:
metadata:

View File

@@ -406,7 +406,7 @@ spec:
* `sha`: The Git commit SHA for the branch.
* `short_sha`: The abbreviated Git commit SHA for the branch (8 chars or the length of the `sha` if it's shorter).
* `short_sha_7`: The abbreviated Git commit SHA for the branch (7 chars or the length of the `sha` if it's shorter).
* `labels`: A comma-separated list of repository labels.
* `labels`: A comma-separated list of repository labels in case of Gitea, repository topics in case of Gitlab and Github. Not supported by Bitbucket Cloud, Bitbucket Server, or Azure DevOps.
* `branchNormalized`: The value of `branch` normalized to contain only lowercase alphanumeric characters, '-' or '.'.
## Pass additional key-value pairs via `values` field

View File

@@ -103,6 +103,7 @@ generators' templating:
- `{{ path.filename }}` becomes `{{ .path.filename }}`
- `{{ path.filenameNormalized }}` becomes `{{ .path.filenameNormalized }}`
- `{{ path[n] }}` becomes `{{ index .path.segments n }}`
- `{{ values }}` if being used in the file generator becomes `{{ .values }}`
Here is an example:

View File

@@ -91,3 +91,8 @@ spec:
# scoped to this project. Set the following field to `true` to restrict apps in this cluster to only clusters
# scoped to this project.
permitOnlyProjectScopedClusters: false
# When using Applications-in-any-namespace, this field determines which namespaces this AppProject permits
# Applications to reside in. Details: https://argo-cd.readthedocs.io/en/stable/operator-manual/app-any-namespace/
sourceNamespaces:
- "argocd-apps-*"

View File

@@ -16,7 +16,9 @@ argocd-repo-server [flags]
--address string Listen on given address for incoming connections (default "0.0.0.0")
--allow-oob-symlinks Allow out-of-bounds symlinks in repositories (not recommended)
--default-cache-expiration duration Cache expiration default (default 24h0m0s)
--disable-helm-manifest-max-extracted-size Disable maximum size of helm manifest archives when extracted
--disable-tls Disable TLS on the gRPC endpoint
--helm-manifest-max-extracted-size string Maximum size of helm manifest archives when extracted (default "1G")
-h, --help help for argocd-repo-server
--logformat string Set the logging format. One of: text|json (default "text")
--loglevel string Set the logging level. One of: debug|info|warn|error (default "info")

2
go.mod
View File

@@ -20,7 +20,7 @@ require (
github.com/bradleyfalzon/ghinstallation/v2 v2.5.0
github.com/casbin/casbin/v2 v2.71.1
github.com/coreos/go-oidc/v3 v3.6.0
github.com/cyphar/filepath-securejoin v0.2.3
github.com/cyphar/filepath-securejoin v0.2.4
github.com/dustin/go-humanize v1.0.1
github.com/evanphx/json-patch v5.6.0+incompatible
github.com/fsnotify/fsnotify v1.6.0

3
go.sum
View File

@@ -275,8 +275,9 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

View File

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

View File

@@ -150,6 +150,18 @@ spec:
key: reposerver.streamed.manifest.max.extracted.size
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE
valueFrom:
configMapKeyRef:
key: reposerver.helm.manifest.max.extracted.size
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE
valueFrom:
configMapKeyRef:
name: argocd-cmd-params-cm
key: reposerver.disable.helm.manifest.max.extracted.size
optional: true
- name: ARGOCD_GIT_MODULES_ENABLED
valueFrom:
configMapKeyRef:

View File

@@ -36,3 +36,15 @@ rules:
- get
- list
- watch
- apiGroups:
- batch
resources:
- jobs
verbs:
- create # supports triggering jobs from UI
- apiGroups:
- argoproj.io
resources:
- workflows
verbs:
- create # supports triggering workflows from UI

View File

@@ -18880,7 +18880,7 @@ spec:
key: applicationsetcontroller.allowed.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -19156,6 +19156,18 @@ spec:
key: reposerver.streamed.manifest.max.extracted.size
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE
valueFrom:
configMapKeyRef:
key: reposerver.helm.manifest.max.extracted.size
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE
valueFrom:
configMapKeyRef:
key: reposerver.disable.helm.manifest.max.extracted.size
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_GIT_MODULES_ENABLED
valueFrom:
configMapKeyRef:
@@ -19168,7 +19180,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -19220,7 +19232,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -19439,7 +19451,7 @@ spec:
key: controller.kubectl.parallelism.limit
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
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: v2.8.1
newTag: v2.8.4

View File

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

View File

@@ -18763,6 +18763,18 @@ rules:
- get
- list
- watch
- apiGroups:
- batch
resources:
- jobs
verbs:
- create
- apiGroups:
- argoproj.io
resources:
- workflows
verbs:
- create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
@@ -20117,7 +20129,7 @@ spec:
key: applicationsetcontroller.allowed.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -20240,7 +20252,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -20310,7 +20322,7 @@ spec:
key: notificationscontroller.log.level
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -20612,6 +20624,18 @@ spec:
key: reposerver.streamed.manifest.max.extracted.size
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE
valueFrom:
configMapKeyRef:
key: reposerver.helm.manifest.max.extracted.size
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE
valueFrom:
configMapKeyRef:
key: reposerver.disable.helm.manifest.max.extracted.size
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_GIT_MODULES_ENABLED
valueFrom:
configMapKeyRef:
@@ -20624,7 +20648,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -20676,7 +20700,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -20965,7 +20989,7 @@ spec:
key: server.enable.proxy.extension
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -21211,7 +21235,7 @@ spec:
key: controller.kubectl.parallelism.limit
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -1635,7 +1635,7 @@ spec:
key: applicationsetcontroller.allowed.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -1758,7 +1758,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -1828,7 +1828,7 @@ spec:
key: notificationscontroller.log.level
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -2130,6 +2130,18 @@ spec:
key: reposerver.streamed.manifest.max.extracted.size
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE
valueFrom:
configMapKeyRef:
key: reposerver.helm.manifest.max.extracted.size
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE
valueFrom:
configMapKeyRef:
key: reposerver.disable.helm.manifest.max.extracted.size
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_GIT_MODULES_ENABLED
valueFrom:
configMapKeyRef:
@@ -2142,7 +2154,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -2194,7 +2206,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -2483,7 +2495,7 @@ spec:
key: server.enable.proxy.extension
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2729,7 +2741,7 @@ spec:
key: controller.kubectl.parallelism.limit
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -18722,6 +18722,18 @@ rules:
- get
- list
- watch
- apiGroups:
- batch
resources:
- jobs
verbs:
- create
- apiGroups:
- argoproj.io
resources:
- workflows
verbs:
- create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
@@ -19218,7 +19230,7 @@ spec:
key: applicationsetcontroller.allowed.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -19341,7 +19353,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -19411,7 +19423,7 @@ spec:
key: notificationscontroller.log.level
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -19669,6 +19681,18 @@ spec:
key: reposerver.streamed.manifest.max.extracted.size
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE
valueFrom:
configMapKeyRef:
key: reposerver.helm.manifest.max.extracted.size
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE
valueFrom:
configMapKeyRef:
key: reposerver.disable.helm.manifest.max.extracted.size
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_GIT_MODULES_ENABLED
valueFrom:
configMapKeyRef:
@@ -19681,7 +19705,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -19733,7 +19757,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -20020,7 +20044,7 @@ spec:
key: server.enable.proxy.extension
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -20266,7 +20290,7 @@ spec:
key: controller.kubectl.parallelism.limit
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -736,7 +736,7 @@ spec:
key: applicationsetcontroller.allowed.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -859,7 +859,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -929,7 +929,7 @@ spec:
key: notificationscontroller.log.level
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -1187,6 +1187,18 @@ spec:
key: reposerver.streamed.manifest.max.extracted.size
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE
valueFrom:
configMapKeyRef:
key: reposerver.helm.manifest.max.extracted.size
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE
valueFrom:
configMapKeyRef:
key: reposerver.disable.helm.manifest.max.extracted.size
name: argocd-cmd-params-cm
optional: true
- name: ARGOCD_GIT_MODULES_ENABLED
valueFrom:
configMapKeyRef:
@@ -1199,7 +1211,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -1251,7 +1263,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -1538,7 +1550,7 @@ spec:
key: server.enable.proxy.extension
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -1784,7 +1796,7 @@ spec:
key: controller.kubectl.parallelism.limit
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.8.1
image: quay.io/argoproj/argocd:v2.8.4
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -22,7 +22,6 @@ import (
grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
"github.com/hashicorp/go-retryablehttp"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"golang.org/x/oauth2"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
@@ -222,10 +221,6 @@ func NewClient(opts *ClientOptions) (Client, error) {
if c.ServerAddr == "" {
return nil, errors.New("Argo CD server address unspecified")
}
if parts := strings.Split(c.ServerAddr, ":"); len(parts) == 1 {
// If port is unspecified, assume the most likely port
c.ServerAddr += ":443"
}
// Override auth-token if specified in env variable or CLI flag
c.AuthToken = env.StringFromEnv(EnvArgoCDAuthToken, c.AuthToken)
if opts.AuthToken != "" {
@@ -281,6 +276,10 @@ func NewClient(opts *ClientOptions) (Client, error) {
}
}
if !c.GRPCWeb {
if parts := strings.Split(c.ServerAddr, ":"); len(parts) == 1 {
// If port is unspecified, assume the most likely port
c.ServerAddr += ":443"
}
// test if we need to set it to true
// if a call to grpc failed, then try again with GRPCWeb
conn, versionIf, err := c.NewVersionClient()
@@ -520,8 +519,8 @@ func (c *client) newConn() (*grpc.ClientConn, io.Closer, error) {
dialOpts = append(dialOpts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxGRPCMessageSize), grpc.MaxCallSendMsgSize(MaxGRPCMessageSize)))
dialOpts = append(dialOpts, grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor(retryOpts...)))
dialOpts = append(dialOpts, grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(grpc_retry.UnaryClientInterceptor(retryOpts...))))
dialOpts = append(dialOpts, grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()))
dialOpts = append(dialOpts, grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()))
dialOpts = append(dialOpts, grpc.WithUnaryInterceptor(grpc_util.OTELUnaryClientInterceptor()))
dialOpts = append(dialOpts, grpc.WithStreamInterceptor(grpc_util.OTELStreamClientInterceptor()))
ctx := context.Background()

View File

@@ -9,7 +9,6 @@ import (
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
@@ -67,8 +66,8 @@ func NewConnection(address string, timeoutSeconds int, tlsConfig *TLSConfigurati
grpc.WithStreamInterceptor(grpc_retry.StreamClientInterceptor(retryOpts...)),
grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(unaryInterceptors...)),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxGRPCMessageSize), grpc.MaxCallSendMsgSize(MaxGRPCMessageSize)),
grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()),
grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()),
grpc.WithUnaryInterceptor(argogrpc.OTELUnaryClientInterceptor()),
grpc.WithStreamInterceptor(argogrpc.OTELStreamClientInterceptor()),
}
tlsC := &tls.Config{}

View File

@@ -107,6 +107,8 @@ type RepoServerInitConstants struct {
AllowOutOfBoundsSymlinks bool
StreamedManifestMaxExtractedSize int64
StreamedManifestMaxTarSize int64
HelmManifestMaxExtractedSize int64
DisableHelmManifestMaxExtractedSize bool
}
// NewService returns a new instance of the Manifest service
@@ -346,7 +348,7 @@ func (s *Service) runRepoOperation(
if source.Helm != nil {
helmPassCredentials = source.Helm.PassCredentials
}
chartPath, closer, err := helmClient.ExtractChart(source.Chart, revision, helmPassCredentials)
chartPath, closer, err := helmClient.ExtractChart(source.Chart, revision, helmPassCredentials, s.initConstants.HelmManifestMaxExtractedSize, s.initConstants.DisableHelmManifestMaxExtractedSize)
if err != nil {
return err
}
@@ -2233,7 +2235,7 @@ func (s *Service) GetRevisionChartDetails(ctx context.Context, q *apiclient.Repo
if err != nil {
return nil, fmt.Errorf("helm client error: %v", err)
}
chartPath, closer, err := helmClient.ExtractChart(q.Name, revision, false)
chartPath, closer, err := helmClient.ExtractChart(q.Name, revision, false, s.initConstants.HelmManifestMaxExtractedSize, s.initConstants.DisableHelmManifestMaxExtractedSize)
if err != nil {
return nil, fmt.Errorf("error extracting chart: %v", err)
}

View File

@@ -5,10 +5,10 @@ infinity = 2^1024-1
local function executor_range_api()
min_executor_instances = 0
max_executor_instances = infinity
if obj.spec.dynamicAllocation.maxExecutors then
if obj.spec.dynamicAllocation.maxExecutors then
max_executor_instances = obj.spec.dynamicAllocation.maxExecutors
end
if obj.spec.dynamicAllocation.minExecutors then
if obj.spec.dynamicAllocation.minExecutors then
min_executor_instances = obj.spec.dynamicAllocation.minExecutors
end
return min_executor_instances, max_executor_instances
@@ -17,7 +17,7 @@ end
local function maybe_executor_range_spark_conf()
min_executor_instances = 0
max_executor_instances = infinity
if obj.spec.sparkConf["spark.streaming.dynamicAllocation.enabled"] ~= nil and
if obj.spec.sparkConf["spark.streaming.dynamicAllocation.enabled"] ~= nil and
obj.spec.sparkConf["spark.streaming.dynamicAllocation.enabled"] == "true" then
if(obj.spec.sparkConf["spark.streaming.dynamicAllocation.maxExecutors"] ~= nil) then
max_executor_instances = tonumber(obj.spec.sparkConf["spark.streaming.dynamicAllocation.maxExecutors"])
@@ -26,7 +26,7 @@ local function maybe_executor_range_spark_conf()
min_executor_instances = tonumber(obj.spec.sparkConf["spark.streaming.dynamicAllocation.minExecutors"])
end
return min_executor_instances, max_executor_instances
elseif obj.spec.sparkConf["spark.dynamicAllocation.enabled"] ~= nil and
elseif obj.spec.sparkConf["spark.dynamicAllocation.enabled"] ~= nil and
obj.spec.sparkConf["spark.dynamicAllocation.enabled"] == "true" then
if(obj.spec.sparkConf["spark.dynamicAllocation.maxExecutors"] ~= nil) then
max_executor_instances = tonumber(obj.spec.sparkConf["spark.dynamicAllocation.maxExecutors"])
@@ -45,11 +45,19 @@ local function maybe_executor_range()
return executor_range_api()
elseif obj.spec["sparkConf"] ~= nil then
return maybe_executor_range_spark_conf()
else
else
return nil
end
end
local function dynamic_executors_without_spec_config()
if obj.spec.dynamicAllocation == nil and obj.spec.executor.instances == nil then
return true
else
return false
end
end
if obj.status ~= nil then
if obj.status.applicationState.state ~= nil then
if obj.status.applicationState.state == "" then
@@ -60,23 +68,26 @@ if obj.status ~= nil then
if obj.status.applicationState.state == "RUNNING" then
if obj.status.executorState ~= nil then
count=0
executor_instances = obj.spec.executor.instances
for i, executorState in pairs(obj.status.executorState) do
if executorState == "RUNNING" then
count=count+1
end
end
if executor_instances == count then
if obj.spec.executor.instances ~= nil and obj.spec.executor.instances == count then
health_status.status = "Healthy"
health_status.message = "SparkApplication is Running"
return health_status
elseif maybe_executor_range() then
min_executor_instances, max_executor_instances = maybe_executor_range()
if count >= min_executor_instances and count <= max_executor_instances then
if count >= min_executor_instances and count <= max_executor_instances then
health_status.status = "Healthy"
health_status.message = "SparkApplication is Running"
return health_status
end
elseif dynamic_executors_without_spec_config() and count >= 1 then
health_status.status = "Healthy"
health_status.message = "SparkApplication is Running"
return health_status
end
end
end

View File

@@ -23,3 +23,7 @@ tests:
status: Healthy
message: "SparkApplication is Running"
inputPath: testdata/healthy_dynamic_alloc_operator_api.yaml
- healthStatus:
status: Healthy
message: "SparkApplication is Running"
inputPath: testdata/healthy_dynamic_alloc_without_spec_config.yaml

View File

@@ -0,0 +1,31 @@
apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
generation: 4
labels:
argocd.argoproj.io/instance: spark-job
name: spark-job-app
namespace: spark-cluster
resourceVersion: "31812990"
uid: bfee52b0-74ca-4465-8005-f6643097ed64
spec:
executor: {}
status:
applicationState:
state: RUNNING
driverInfo:
podName: ingestion-datalake-news-app-driver
webUIAddress: 172.20.207.161:4040
webUIPort: 4040
webUIServiceName: ingestion-datalake-news-app-ui-svc
executionAttempts: 13
executorState:
ingestion-datalake-news-app-1591613851251-exec-1: RUNNING
ingestion-datalake-news-app-1591613851251-exec-2: RUNNING
ingestion-datalake-news-app-1591613851251-exec-4: RUNNING
ingestion-datalake-news-app-1591613851251-exec-5: RUNNING
lastSubmissionAttemptTime: "2020-06-08T10:57:32Z"
sparkApplicationId: spark-a5920b2a5aa04d22a737c60759b5bf82
submissionAttempts: 1
submissionID: 3e713ec8-9f6c-4e78-ac28-749797c846f0
terminationTime: null

View File

@@ -9,25 +9,28 @@ import (
healthutil "github.com/argoproj/gitops-engine/pkg/health"
"k8s.io/apimachinery/pkg/api/errors"
validation "k8s.io/apimachinery/pkg/api/validation"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
"github.com/argoproj/argo-cd/v2/util/argo"
"github.com/argoproj/argo-cd/v2/util/assets"
"github.com/argoproj/argo-cd/v2/util/security"
"github.com/argoproj/argo-cd/v2/util/settings"
)
//NewHandler creates handler serving to do api/badge endpoint
func NewHandler(appClientset versioned.Interface, settingsMrg *settings.SettingsManager, namespace string) http.Handler {
return &Handler{appClientset: appClientset, namespace: namespace, settingsMgr: settingsMrg}
// NewHandler creates handler serving to do api/badge endpoint
func NewHandler(appClientset versioned.Interface, settingsMrg *settings.SettingsManager, namespace string, enabledNamespaces []string) http.Handler {
return &Handler{appClientset: appClientset, namespace: namespace, settingsMgr: settingsMrg, enabledNamespaces: enabledNamespaces}
}
//Handler used to get application in order to access health/sync
// Handler used to get application in order to access health/sync
type Handler struct {
namespace string
appClientset versioned.Interface
settingsMgr *settings.SettingsManager
namespace string
appClientset versioned.Interface
settingsMgr *settings.SettingsManager
enabledNamespaces []string
}
var (
@@ -62,8 +65,8 @@ func replaceFirstGroupSubMatch(re *regexp.Regexp, str string, repl string) strin
return result + str[lastIndex:]
}
//ServeHTTP returns badge with health and sync status for application
//(or an error badge if wrong query or application name is given)
// ServeHTTP returns badge with health and sync status for application
// (or an error badge if wrong query or application name is given)
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
health := healthutil.HealthStatusUnknown
status := appv1.SyncStatusCodeUnknown
@@ -75,21 +78,50 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
enabled = sets.StatusBadgeEnabled
}
//Sample url: http://localhost:8080/api/badge?name=123
if name, ok := r.URL.Query()["name"]; ok && enabled {
if app, err := h.appClientset.ArgoprojV1alpha1().Applications(h.namespace).Get(context.Background(), name[0], v1.GetOptions{}); err == nil {
health = app.Status.Health.Status
status = app.Status.Sync.Status
if app.Status.OperationState != nil && app.Status.OperationState.SyncResult != nil {
revision = app.Status.OperationState.SyncResult.Revision
reqNs := ""
if ns, ok := r.URL.Query()["namespace"]; ok && enabled {
if errs := validation.NameIsDNSSubdomain(strings.ToLower(ns[0]), false); len(errs) == 0 {
if security.IsNamespaceEnabled(ns[0], h.namespace, h.enabledNamespaces) {
reqNs = ns[0]
} else {
notFound = true
}
} else if errors.IsNotFound(err) {
notFound = true
} else {
w.WriteHeader(http.StatusBadRequest)
return
}
} else {
reqNs = h.namespace
}
//Sample url: http://localhost:8080/api/badge?name=123
if name, ok := r.URL.Query()["name"]; ok && enabled && !notFound {
if errs := validation.NameIsDNSLabel(strings.ToLower(name[0]), false); len(errs) == 0 {
if app, err := h.appClientset.ArgoprojV1alpha1().Applications(reqNs).Get(context.Background(), name[0], v1.GetOptions{}); err == nil {
health = app.Status.Health.Status
status = app.Status.Sync.Status
if app.Status.OperationState != nil && app.Status.OperationState.SyncResult != nil {
revision = app.Status.OperationState.SyncResult.Revision
}
} else {
if errors.IsNotFound(err) {
notFound = true
}
}
} else {
w.WriteHeader(http.StatusBadRequest)
return
}
}
//Sample url: http://localhost:8080/api/badge?project=default
if projects, ok := r.URL.Query()["project"]; ok && enabled {
if apps, err := h.appClientset.ArgoprojV1alpha1().Applications(h.namespace).List(context.Background(), v1.ListOptions{}); err == nil {
if projects, ok := r.URL.Query()["project"]; ok && enabled && !notFound {
for _, p := range projects {
if errs := validation.NameIsDNSLabel(strings.ToLower(p), false); len(p) > 0 && len(errs) != 0 {
w.WriteHeader(http.StatusBadRequest)
return
}
}
if apps, err := h.appClientset.ArgoprojV1alpha1().Applications(reqNs).List(context.Background(), v1.ListOptions{}); err == nil {
applicationSet := argo.FilterByProjects(apps.Items, projects)
for _, a := range applicationSet {
if a.Status.Sync.Status != appv1.SyncStatusCodeSynced {

View File

@@ -15,6 +15,7 @@ import (
"github.com/argoproj/gitops-engine/pkg/health"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
@@ -41,7 +42,19 @@ var (
},
}
testApp = v1alpha1.Application{
ObjectMeta: v1.ObjectMeta{Name: "testApp", Namespace: "default"},
ObjectMeta: v1.ObjectMeta{Name: "test-app", Namespace: "default"},
Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{Status: v1alpha1.SyncStatusCodeSynced},
Health: v1alpha1.HealthStatus{Status: health.HealthStatusHealthy},
OperationState: &v1alpha1.OperationState{
SyncResult: &v1alpha1.SyncOperationResult{
Revision: "aa29b85",
},
},
},
}
testApp2 = v1alpha1.Application{
ObjectMeta: v1.ObjectMeta{Name: "test-app", Namespace: "argocd-test"},
Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{Status: v1alpha1.SyncStatusCodeSynced},
Health: v1alpha1.HealthStatus{Status: health.HealthStatusHealthy},
@@ -53,15 +66,15 @@ var (
},
}
testProject = v1alpha1.AppProject{
ObjectMeta: v1.ObjectMeta{Name: "testProject", Namespace: "default"},
ObjectMeta: v1.ObjectMeta{Name: "test-project", Namespace: "default"},
Spec: v1alpha1.AppProjectSpec{},
}
)
func TestHandlerFeatureIsEnabled(t *testing.T) {
settingsMgr := settings.NewSettingsManager(context.Background(), fake.NewSimpleClientset(&argoCDCm, &argoCDSecret), "default")
handler := NewHandler(appclientset.NewSimpleClientset(&testApp), settingsMgr, "default")
req, err := http.NewRequest(http.MethodGet, "/api/badge?name=testApp", nil)
handler := NewHandler(appclientset.NewSimpleClientset(&testApp), settingsMgr, "default", []string{})
req, err := http.NewRequest(http.MethodGet, "/api/badge?name=test-app", nil)
assert.NoError(t, err)
rr := httptest.NewRecorder()
@@ -81,6 +94,7 @@ func TestHandlerFeatureIsEnabled(t *testing.T) {
func TestHandlerFeatureProjectIsEnabled(t *testing.T) {
projectTests := []struct {
testApp []*v1alpha1.Application
response int
apiEndPoint string
namespace string
health string
@@ -89,42 +103,105 @@ func TestHandlerFeatureProjectIsEnabled(t *testing.T) {
statusColor color.RGBA
}{
{createApplications([]string{"Healthy:Synced", "Healthy:Synced"}, []string{"default", "default"}, "test"),
"/api/badge?project=default", "test", "Healthy", "Synced", Green, Green},
{createApplications([]string{"Healthy:Synced", "Healthy:OutOfSync"}, []string{"testProject", "testProject"}, "default"),
"/api/badge?project=testProject", "default", "Healthy", "OutOfSync", Green, Orange},
http.StatusOK, "/api/badge?project=default", "test", "Healthy", "Synced", Green, Green},
{createApplications([]string{"Healthy:Synced", "Healthy:OutOfSync"}, []string{"test-project", "test-project"}, "default"),
http.StatusOK, "/api/badge?project=test-project", "default", "Healthy", "OutOfSync", Green, Orange},
{createApplications([]string{"Healthy:Synced", "Degraded:Synced"}, []string{"default", "default"}, "test"),
"/api/badge?project=default", "test", "Degraded", "Synced", Red, Green},
{createApplications([]string{"Healthy:Synced", "Degraded:OutOfSync"}, []string{"testProject", "testProject"}, "default"),
"/api/badge?project=testProject", "default", "Degraded", "OutOfSync", Red, Orange},
{createApplications([]string{"Healthy:Synced", "Healthy:Synced"}, []string{"testProject", "default"}, "test"),
"/api/badge?project=default&project=testProject", "test", "Healthy", "Synced", Green, Green},
{createApplications([]string{"Healthy:OutOfSync", "Healthy:Synced"}, []string{"testProject", "default"}, "default"),
"/api/badge?project=default&project=testProject", "default", "Healthy", "OutOfSync", Green, Orange},
{createApplications([]string{"Degraded:Synced", "Healthy:Synced"}, []string{"testProject", "default"}, "test"),
"/api/badge?project=default&project=testProject", "test", "Degraded", "Synced", Red, Green},
{createApplications([]string{"Degraded:OutOfSync", "Healthy:OutOfSync"}, []string{"testProject", "default"}, "default"),
"/api/badge?project=default&project=testProject", "default", "Degraded", "OutOfSync", Red, Orange},
{createApplications([]string{"Unknown:Unknown", "Unknown:Unknown"}, []string{"testProject", "default"}, "default"),
"/api/badge?project=", "default", "Unknown", "Unknown", Purple, Purple},
http.StatusOK, "/api/badge?project=default", "test", "Degraded", "Synced", Red, Green},
{createApplications([]string{"Healthy:Synced", "Degraded:OutOfSync"}, []string{"test-project", "test-project"}, "default"),
http.StatusOK, "/api/badge?project=test-project", "default", "Degraded", "OutOfSync", Red, Orange},
{createApplications([]string{"Healthy:Synced", "Healthy:Synced"}, []string{"test-project", "default"}, "test"),
http.StatusOK, "/api/badge?project=default&project=test-project", "test", "Healthy", "Synced", Green, Green},
{createApplications([]string{"Healthy:OutOfSync", "Healthy:Synced"}, []string{"test-project", "default"}, "default"),
http.StatusOK, "/api/badge?project=default&project=test-project", "default", "Healthy", "OutOfSync", Green, Orange},
{createApplications([]string{"Degraded:Synced", "Healthy:Synced"}, []string{"test-project", "default"}, "test"),
http.StatusOK, "/api/badge?project=default&project=test-project", "test", "Degraded", "Synced", Red, Green},
{createApplications([]string{"Degraded:OutOfSync", "Healthy:OutOfSync"}, []string{"test-project", "default"}, "default"),
http.StatusOK, "/api/badge?project=default&project=test-project", "default", "Degraded", "OutOfSync", Red, Orange},
{createApplications([]string{"Unknown:Unknown", "Unknown:Unknown"}, []string{"test-project", "default"}, "default"),
http.StatusOK, "/api/badge?project=", "default", "Unknown", "Unknown", Purple, Purple},
{createApplications([]string{"Unknown:Unknown", "Unknown:Unknown"}, []string{"test-project", "default"}, "default"),
http.StatusBadRequest, "/api/badge?project=test$project", "default", "Unknown", "Unknown", Purple, Purple},
{createApplications([]string{"Unknown:Unknown", "Unknown:Unknown"}, []string{"test-project", "default"}, "default"),
http.StatusOK, "/api/badge?project=unknown", "default", "Unknown", "Unknown", Purple, Purple},
{createApplications([]string{"Unknown:Unknown", "Unknown:Unknown"}, []string{"test-project", "default"}, "default"),
http.StatusBadRequest, "/api/badge?name=foo_bar", "default", "Unknown", "Unknown", Purple, Purple},
{createApplications([]string{"Unknown:Unknown", "Unknown:Unknown"}, []string{"test-project", "default"}, "default"),
http.StatusOK, "/api/badge?name=foobar", "default", "Not Found", "", Purple, Purple},
}
for _, tt := range projectTests {
argoCDCm.ObjectMeta.Namespace = tt.namespace
argoCDSecret.ObjectMeta.Namespace = tt.namespace
settingsMgr := settings.NewSettingsManager(context.Background(), fake.NewSimpleClientset(&argoCDCm, &argoCDSecret), tt.namespace)
handler := NewHandler(appclientset.NewSimpleClientset(&testProject, tt.testApp[0], tt.testApp[1]), settingsMgr, tt.namespace)
handler := NewHandler(appclientset.NewSimpleClientset(&testProject, tt.testApp[0], tt.testApp[1]), settingsMgr, tt.namespace, []string{})
rr := httptest.NewRecorder()
req, err := http.NewRequest(http.MethodGet, tt.apiEndPoint, nil)
assert.NoError(t, err)
handler.ServeHTTP(rr, req)
require.Equal(t, tt.response, rr.Result().StatusCode)
if rr.Result().StatusCode != 400 {
assert.Equal(t, "private, no-store", rr.Header().Get("Cache-Control"))
assert.Equal(t, "*", rr.Header().Get("Access-Control-Allow-Origin"))
response := rr.Body.String()
require.Greater(t, len(response), 2)
assert.Equal(t, toRGBString(tt.healthColor), leftRectColorPattern.FindStringSubmatch(response)[1])
assert.Equal(t, toRGBString(tt.statusColor), rightRectColorPattern.FindStringSubmatch(response)[1])
assert.Equal(t, tt.health, leftTextPattern.FindStringSubmatch(response)[1])
assert.Equal(t, tt.status, rightTextPattern.FindStringSubmatch(response)[1])
}
}
}
func TestHandlerNamespacesIsEnabled(t *testing.T) {
t.Run("Application in allowed namespace", func(t *testing.T) {
settingsMgr := settings.NewSettingsManager(context.Background(), fake.NewSimpleClientset(&argoCDCm, &argoCDSecret), "default")
handler := NewHandler(appclientset.NewSimpleClientset(&testApp2), settingsMgr, "default", []string{"argocd-test"})
req, err := http.NewRequest(http.MethodGet, "/api/badge?name=test-app&namespace=argocd-test", nil)
assert.NoError(t, err)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
assert.Equal(t, "private, no-store", rr.Header().Get("Cache-Control"))
assert.Equal(t, "*", rr.Header().Get("Access-Control-Allow-Origin"))
response := rr.Body.String()
assert.Equal(t, toRGBString(tt.healthColor), leftRectColorPattern.FindStringSubmatch(response)[1])
assert.Equal(t, toRGBString(tt.statusColor), rightRectColorPattern.FindStringSubmatch(response)[1])
assert.Equal(t, tt.health, leftTextPattern.FindStringSubmatch(response)[1])
assert.Equal(t, tt.status, rightTextPattern.FindStringSubmatch(response)[1])
}
response := rr.Body.String()
assert.Equal(t, toRGBString(Green), leftRectColorPattern.FindStringSubmatch(response)[1])
assert.Equal(t, toRGBString(Green), rightRectColorPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "Healthy", leftTextPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "Synced", rightTextPattern.FindStringSubmatch(response)[1])
assert.NotContains(t, response, "(aa29b85)")
})
t.Run("Application in disallowed namespace", func(t *testing.T) {
settingsMgr := settings.NewSettingsManager(context.Background(), fake.NewSimpleClientset(&argoCDCm, &argoCDSecret), "default")
handler := NewHandler(appclientset.NewSimpleClientset(&testApp2), settingsMgr, "default", []string{"argocd-test"})
req, err := http.NewRequest(http.MethodGet, "/api/badge?name=test-app&namespace=kube-system", nil)
assert.NoError(t, err)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Result().StatusCode)
response := rr.Body.String()
assert.Equal(t, toRGBString(Purple), leftRectColorPattern.FindStringSubmatch(response)[1])
assert.Equal(t, toRGBString(Purple), rightRectColorPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "Not Found", leftTextPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "", rightTextPattern.FindStringSubmatch(response)[1])
})
t.Run("Request with illegal namespace", func(t *testing.T) {
settingsMgr := settings.NewSettingsManager(context.Background(), fake.NewSimpleClientset(&argoCDCm, &argoCDSecret), "default")
handler := NewHandler(appclientset.NewSimpleClientset(&testApp2), settingsMgr, "default", []string{"argocd-test"})
req, err := http.NewRequest(http.MethodGet, "/api/badge?name=test-app&namespace=kube()system", nil)
assert.NoError(t, err)
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Result().StatusCode)
})
}
func createApplicationFeatureProjectIsEnabled(healthStatus health.HealthStatusCode, syncStatus v1alpha1.SyncStatusCode, appName, projectName, namespace string) *v1alpha1.Application {
@@ -176,8 +253,8 @@ func createApplications(appCombo, projectName []string, namespace string) []*v1a
}
func TestHandlerFeatureIsEnabledRevisionIsEnabled(t *testing.T) {
settingsMgr := settings.NewSettingsManager(context.Background(), fake.NewSimpleClientset(&argoCDCm, &argoCDSecret), "default")
handler := NewHandler(appclientset.NewSimpleClientset(&testApp), settingsMgr, "default")
req, err := http.NewRequest(http.MethodGet, "/api/badge?name=testApp&revision=true", nil)
handler := NewHandler(appclientset.NewSimpleClientset(&testApp), settingsMgr, "default", []string{})
req, err := http.NewRequest(http.MethodGet, "/api/badge?name=test-app&revision=true", nil)
assert.NoError(t, err)
rr := httptest.NewRecorder()
@@ -199,8 +276,8 @@ func TestHandlerRevisionIsEnabledNoOperationState(t *testing.T) {
app.Status.OperationState = nil
settingsMgr := settings.NewSettingsManager(context.Background(), fake.NewSimpleClientset(&argoCDCm, &argoCDSecret), "default")
handler := NewHandler(appclientset.NewSimpleClientset(app), settingsMgr, "default")
req, err := http.NewRequest(http.MethodGet, "/api/badge?name=testApp&revision=true", nil)
handler := NewHandler(appclientset.NewSimpleClientset(app), settingsMgr, "default", []string{})
req, err := http.NewRequest(http.MethodGet, "/api/badge?name=test-app&revision=true", nil)
assert.NoError(t, err)
rr := httptest.NewRecorder()
@@ -222,8 +299,8 @@ func TestHandlerRevisionIsEnabledShortCommitSHA(t *testing.T) {
app.Status.OperationState.SyncResult.Revision = "abc"
settingsMgr := settings.NewSettingsManager(context.Background(), fake.NewSimpleClientset(&argoCDCm, &argoCDSecret), "default")
handler := NewHandler(appclientset.NewSimpleClientset(app), settingsMgr, "default")
req, err := http.NewRequest(http.MethodGet, "/api/badge?name=testApp&revision=true", nil)
handler := NewHandler(appclientset.NewSimpleClientset(app), settingsMgr, "default", []string{})
req, err := http.NewRequest(http.MethodGet, "/api/badge?name=test-app&revision=true", nil)
assert.NoError(t, err)
rr := httptest.NewRecorder()
@@ -239,8 +316,8 @@ func TestHandlerFeatureIsDisabled(t *testing.T) {
delete(argoCDCmDisabled.Data, "statusbadge.enabled")
settingsMgr := settings.NewSettingsManager(context.Background(), fake.NewSimpleClientset(argoCDCmDisabled, &argoCDSecret), "default")
handler := NewHandler(appclientset.NewSimpleClientset(&testApp), settingsMgr, "default")
req, err := http.NewRequest(http.MethodGet, "/api/badge?name=testApp", nil)
handler := NewHandler(appclientset.NewSimpleClientset(&testApp), settingsMgr, "default", []string{})
req, err := http.NewRequest(http.MethodGet, "/api/badge?name=test-app", nil)
assert.NoError(t, err)
rr := httptest.NewRecorder()

View File

@@ -63,7 +63,6 @@ import (
"github.com/argoproj/argo-cd/v2/pkg/apiclient"
accountpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/account"
applicationpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
applicationsetpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/applicationset"
certificatepkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/certificate"
clusterpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster"
@@ -437,8 +436,8 @@ func (a *ArgoCDServer) Listen() (*Listeners, error) {
var dOpts []grpc.DialOption
dOpts = append(dOpts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(apiclient.MaxGRPCMessageSize)))
dOpts = append(dOpts, grpc.WithUserAgent(fmt.Sprintf("%s/%s", common.ArgoCDUserAgentName, common.GetVersion().Version)))
dOpts = append(dOpts, grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor()))
dOpts = append(dOpts, grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor()))
dOpts = append(dOpts, grpc.WithUnaryInterceptor(grpc_util.OTELUnaryClientInterceptor()))
dOpts = append(dOpts, grpc.WithStreamInterceptor(grpc_util.OTELStreamClientInterceptor()))
if a.useTLS() {
// The following sets up the dial Options for grpc-gateway to talk to gRPC server over TLS.
// grpc-gateway is just translating HTTP/HTTPS requests as gRPC requests over localhost,
@@ -952,7 +951,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
Handler: &handlerSwitcher{
handler: mux,
urlToHandler: map[string]http.Handler{
"/api/badge": badge.NewHandler(a.AppClientset, a.settingsMgr, a.Namespace),
"/api/badge": badge.NewHandler(a.AppClientset, a.settingsMgr, a.Namespace, a.ApplicationNamespaces),
common.LogoutEndpoint: logout.NewHandler(a.AppClientset, a.settingsMgr, a.sessionMgr, a.ArgoCDServerOpts.RootPath, a.ArgoCDServerOpts.BaseHRef, a.Namespace),
},
contentTypeToHandler: map[string]http.Handler{

View File

@@ -1754,6 +1754,40 @@ func TestCompareOptionIgnoreExtraneous(t *testing.T) {
Expect(SyncStatusIs(SyncStatusCodeSynced))
}
func TestSourceNamespaceCanBeMigratedToManagedNamespaceWithoutBeingPrunedOrOutOfSync(t *testing.T) {
Given(t).
Prune(true).
Path("guestbook-with-plain-namespace-manifest").
When().
PatchFile("guestbook-ui-namespace.yaml", fmt.Sprintf(`[{"op": "replace", "path": "/metadata/name", "value": "%s"}]`, DeploymentNamespace())).
CreateApp().
Sync().
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
When().
PatchApp(`[{
"op": "add",
"path": "/spec/syncPolicy",
"value": { "prune": true, "syncOptions": ["PrunePropagationPolicy=foreground"], "managedNamespaceMetadata": { "labels": { "foo": "bar" } } }
}]`).
Sync().
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
And(func(app *Application) {
assert.Equal(t, &ManagedNamespaceMetadata{Labels: map[string]string{"foo": "bar"}}, app.Spec.SyncPolicy.ManagedNamespaceMetadata)
}).
When().
DeleteFile("guestbook-ui-namespace.yaml").
Refresh(RefreshTypeHard).
Sync().
Wait().
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced))
}
func TestSelfManagedApps(t *testing.T) {
Given(t).

View File

@@ -13,6 +13,7 @@ import (
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
@@ -47,7 +48,6 @@ var (
Reason: v1alpha1.ApplicationSetReasonApplicationSetUpToDate,
},
}
LabelKeyAppSetInstance = "argocd.argoproj.io/application-set-name"
)
func TestSimpleListGeneratorExternalNamespace(t *testing.T) {
@@ -58,11 +58,8 @@ func TestSimpleListGeneratorExternalNamespace(t *testing.T) {
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook",
Namespace: utils.ArgoCDExternalNamespace,
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-list-generator-external",
},
Name: "my-cluster-guestbook",
Namespace: utils.ArgoCDExternalNamespace,
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
},
Spec: argov1alpha1.ApplicationSpec{
@@ -134,15 +131,13 @@ func TestSimpleListGeneratorExternalNamespace(t *testing.T) {
expectedAppNewMetadata = expectedAppNewNamespace.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{
"label-key": "label-value",
LabelKeyAppSetInstance: "simple-list-generator-external",
"label-key": "label-value",
}
}).
Update(func(appset *v1alpha1.ApplicationSet) {
appset.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"}
appset.Spec.Template.Labels = map[string]string{
"label-key": "label-value",
LabelKeyAppSetInstance: "simple-list-generator-external",
"label-key": "label-value",
}
}).Then().Expect(ApplicationsExist([]argov1alpha1.Application{*expectedAppNewMetadata})).
@@ -163,11 +158,8 @@ func TestSimpleListGenerator(t *testing.T) {
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-cluster-guestbook",
Namespace: fixture.TestNamespace(),
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-list-generator",
},
Name: "my-cluster-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
},
Spec: argov1alpha1.ApplicationSpec{
@@ -234,10 +226,7 @@ func TestSimpleListGenerator(t *testing.T) {
And(func() {
expectedAppNewMetadata = expectedAppNewNamespace.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{
LabelKeyAppSetInstance: "simple-list-generator",
"label-key": "label-value",
}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"}
}).
Update(func(appset *v1alpha1.ApplicationSet) {
appset.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"}
@@ -264,9 +253,6 @@ func TestSimpleListGeneratorGoTemplate(t *testing.T) {
Name: "my-cluster-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-list-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -333,10 +319,7 @@ func TestSimpleListGeneratorGoTemplate(t *testing.T) {
And(func() {
expectedAppNewMetadata = expectedAppNewNamespace.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{
LabelKeyAppSetInstance: "simple-list-generator",
"label-key": "label-value",
}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"}
}).
Update(func(appset *v1alpha1.ApplicationSet) {
appset.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"}
@@ -352,6 +335,82 @@ func TestSimpleListGeneratorGoTemplate(t *testing.T) {
}
func TestRenderHelmValuesObject(t *testing.T) {
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: "helm-guestbook",
Helm: &argov1alpha1.ApplicationSourceHelm{
ValuesObject: &runtime.RawExtension{
// This will always be converted as yaml
Raw: []byte(`{"some":{"string":"Hello world"}}`),
},
},
},
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: "test-values-object",
},
Spec: v1alpha1.ApplicationSetSpec{
GoTemplate: true,
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: "helm-guestbook",
Helm: &argov1alpha1.ApplicationSourceHelm{
ValuesObject: &runtime.RawExtension{
Raw: []byte(`{"some":{"string":"{{.test}}"}}`),
},
},
},
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", "test": "Hello world"}`),
}},
},
},
},
},
}).Then().Expect(ApplicationsExist([]argov1alpha1.Application{expectedApp})).
// Delete the ApplicationSet, and verify it deletes the Applications
When().
Delete().Then().Expect(ApplicationsDoNotExist([]argov1alpha1.Application{expectedApp}))
}
func TestSyncPolicyCreateUpdate(t *testing.T) {
expectedApp := argov1alpha1.Application{
@@ -363,9 +422,6 @@ func TestSyncPolicyCreateUpdate(t *testing.T) {
Name: "my-cluster-guestbook-sync-policy-create-update",
Namespace: utils.ArgoCDNamespace,
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "sync-policy-create-update",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -435,15 +491,13 @@ func TestSyncPolicyCreateUpdate(t *testing.T) {
expectedAppNewMetadata = expectedAppNewNamespace.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{
LabelKeyAppSetInstance: "sync-policy-create-update",
"label-key": "label-value",
"label-key": "label-value",
}
}).
Update(func(appset *v1alpha1.ApplicationSet) {
appset.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"}
appset.Spec.Template.Labels = map[string]string{
LabelKeyAppSetInstance: "sync-policy-create-update",
"label-key": "label-value",
"label-key": "label-value",
}
applicationsSyncPolicy := argov1alpha1.ApplicationsSyncPolicyCreateUpdate
appset.Spec.SyncPolicy = &argov1alpha1.ApplicationSetSyncPolicy{
@@ -478,9 +532,6 @@ func TestSyncPolicyCreateDelete(t *testing.T) {
Name: "my-cluster-guestbook-sync-policy-create-delete",
Namespace: utils.ArgoCDNamespace,
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "sync-policy-create-delete",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -581,9 +632,6 @@ func TestSyncPolicyCreateOnly(t *testing.T) {
Name: "my-cluster-guestbook-sync-policy-create-only",
Namespace: utils.ArgoCDNamespace,
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "sync-policy-create-only",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -684,9 +732,6 @@ func TestSimpleGitDirectoryGenerator(t *testing.T) {
Name: name,
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-git-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -768,10 +813,7 @@ func TestSimpleGitDirectoryGenerator(t *testing.T) {
for _, expectedApp := range expectedAppsNewNamespace {
expectedAppNewMetadata := expectedApp.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{
LabelKeyAppSetInstance: "simple-git-generator",
"label-key": "label-value",
}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"}
expectedAppsNewMetadata = append(expectedAppsNewMetadata, *expectedAppNewMetadata)
}
}).
@@ -799,9 +841,6 @@ func TestSimpleGitDirectoryGeneratorGoTemplate(t *testing.T) {
Name: name,
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-git-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -884,10 +923,7 @@ func TestSimpleGitDirectoryGeneratorGoTemplate(t *testing.T) {
for _, expectedApp := range expectedAppsNewNamespace {
expectedAppNewMetadata := expectedApp.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{
LabelKeyAppSetInstance: "simple-git-generator",
"label-key": "label-value",
}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"}
expectedAppsNewMetadata = append(expectedAppsNewMetadata, *expectedAppNewMetadata)
}
}).
@@ -916,9 +952,6 @@ func TestSimpleGitFilesGenerator(t *testing.T) {
Name: name,
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-git-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -999,10 +1032,7 @@ func TestSimpleGitFilesGenerator(t *testing.T) {
for _, expectedApp := range expectedAppsNewNamespace {
expectedAppNewMetadata := expectedApp.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{
LabelKeyAppSetInstance: "simple-git-generator",
"label-key": "label-value",
}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"}
expectedAppsNewMetadata = append(expectedAppsNewMetadata, *expectedAppNewMetadata)
}
}).
@@ -1031,9 +1061,6 @@ func TestSimpleGitFilesGeneratorGoTemplate(t *testing.T) {
Name: name,
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-git-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -1115,10 +1142,7 @@ func TestSimpleGitFilesGeneratorGoTemplate(t *testing.T) {
for _, expectedApp := range expectedAppsNewNamespace {
expectedAppNewMetadata := expectedApp.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{
LabelKeyAppSetInstance: "simple-git-generator",
"label-key": "label-value",
}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"}
expectedAppsNewMetadata = append(expectedAppsNewMetadata, *expectedAppNewMetadata)
}
}).
@@ -1477,9 +1501,6 @@ func TestSimpleSCMProviderGenerator(t *testing.T) {
Name: "argo-cd-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-scm-provider-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -1554,9 +1575,6 @@ func TestSimpleSCMProviderGeneratorGoTemplate(t *testing.T) {
Name: "argo-cd-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-scm-provider-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -1626,9 +1644,6 @@ func TestSCMProviderGeneratorSCMProviderNotAllowed(t *testing.T) {
Name: "argo-cd-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-scm-provider-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -1704,9 +1719,6 @@ func TestCustomApplicationFinalizers(t *testing.T) {
Name: "my-cluster-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io/background"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-list-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -1773,9 +1785,6 @@ func TestCustomApplicationFinalizersGoTemplate(t *testing.T) {
Name: "my-cluster-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io/background"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-list-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -1883,9 +1892,6 @@ func TestSimplePullRequestGenerator(t *testing.T) {
Name: "guestbook-1",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-pull-request-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -1963,10 +1969,7 @@ func TestSimplePullRequestGeneratorGoTemplate(t *testing.T) {
Name: "guestbook-1",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
"app": "preview",
LabelKeyAppSetInstance: "simple-pull-request-generator",
},
Labels: map[string]string{"app": "preview"},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -2042,8 +2045,7 @@ func TestPullRequestGeneratorNotAllowedSCMProvider(t *testing.T) {
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
"app": "preview",
LabelKeyAppSetInstance: "simple-pull-request-generator",
"app": "preview",
},
},
Spec: argov1alpha1.ApplicationSpec{
@@ -2126,9 +2128,6 @@ func TestGitGeneratorPrivateRepo(t *testing.T) {
Name: name,
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-git-generator-private",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -2204,9 +2203,6 @@ func TestGitGeneratorPrivateRepoGoTemplate(t *testing.T) {
Name: name,
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-git-generator-private",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",

View File

@@ -24,11 +24,8 @@ func TestSimpleClusterGeneratorExternalNamespace(t *testing.T) {
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "cluster1-guestbook",
Namespace: utils.ArgoCDExternalNamespace,
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-cluster-generator",
},
Name: "cluster1-guestbook",
Namespace: utils.ArgoCDExternalNamespace,
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
},
Spec: argov1alpha1.ApplicationSpec{
@@ -104,15 +101,13 @@ func TestSimpleClusterGeneratorExternalNamespace(t *testing.T) {
expectedAppNewMetadata = expectedAppNewNamespace.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{
"label-key": "label-value",
LabelKeyAppSetInstance: "simple-cluster-generator",
"label-key": "label-value",
}
}).
Update(func(appset *v1alpha1.ApplicationSet) {
appset.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"}
appset.Spec.Template.Labels = map[string]string{
"label-key": "label-value",
LabelKeyAppSetInstance: "simple-cluster-generator",
"label-key": "label-value",
}
}).Then().Expect(ApplicationsExist([]argov1alpha1.Application{*expectedAppNewMetadata})).
@@ -132,9 +127,6 @@ func TestSimpleClusterGenerator(t *testing.T) {
Name: "cluster1-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-cluster-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -206,10 +198,7 @@ func TestSimpleClusterGenerator(t *testing.T) {
And(func() {
expectedAppNewMetadata = expectedAppNewNamespace.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{
LabelKeyAppSetInstance: "simple-cluster-generator",
"label-key": "label-value",
}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"}
}).
Update(func(appset *v1alpha1.ApplicationSet) {
appset.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"}
@@ -231,9 +220,6 @@ func TestClusterGeneratorWithLocalCluster(t *testing.T) {
Name: "in-cluster-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "in-cluster-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -326,10 +312,7 @@ func TestClusterGeneratorWithLocalCluster(t *testing.T) {
And(func() {
expectedAppNewMetadata = expectedAppNewNamespace.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{
"label-key": "label-value",
LabelKeyAppSetInstance: "in-cluster-generator",
}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"}
}).
Update(func(appset *v1alpha1.ApplicationSet) {
appset.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"}
@@ -354,9 +337,6 @@ func TestSimpleClusterGeneratorAddingCluster(t *testing.T) {
Name: "{{name}}-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-cluster-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -439,9 +419,6 @@ func TestSimpleClusterGeneratorDeletingCluster(t *testing.T) {
Name: "{{name}}-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-cluster-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",

View File

@@ -25,11 +25,8 @@ func TestSimpleClusterDecisionResourceGeneratorExternalNamespace(t *testing.T) {
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "cluster1-guestbook",
Namespace: utils.ArgoCDExternalNamespace,
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-cluster-generator",
},
Name: "cluster1-guestbook",
Namespace: utils.ArgoCDExternalNamespace,
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
},
Spec: argov1alpha1.ApplicationSpec{
@@ -113,15 +110,13 @@ func TestSimpleClusterDecisionResourceGeneratorExternalNamespace(t *testing.T) {
expectedAppNewMetadata = expectedAppNewNamespace.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{
"label-key": "label-value",
LabelKeyAppSetInstance: "simple-cluster-generator",
"label-key": "label-value",
}
}).
Update(func(appset *v1alpha1.ApplicationSet) {
appset.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"}
appset.Spec.Template.Labels = map[string]string{
"label-key": "label-value",
LabelKeyAppSetInstance: "simple-cluster-generator",
"label-key": "label-value",
}
}).Then().Expect(ApplicationsExist([]argov1alpha1.Application{*expectedAppNewMetadata})).
@@ -141,9 +136,6 @@ func TestSimpleClusterDecisionResourceGenerator(t *testing.T) {
Name: "cluster1-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-cluster-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -223,10 +215,7 @@ func TestSimpleClusterDecisionResourceGenerator(t *testing.T) {
And(func() {
expectedAppNewMetadata = expectedAppNewNamespace.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{
LabelKeyAppSetInstance: "simple-cluster-generator",
"label-key": "label-value",
}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"}
}).
Update(func(appset *v1alpha1.ApplicationSet) {
appset.Spec.Template.Annotations = map[string]string{"annotation-key": "annotation-value"}
@@ -249,9 +238,6 @@ func TestSimpleClusterDecisionResourceGeneratorAddingCluster(t *testing.T) {
Name: "{{name}}-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-cluster-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -347,9 +333,6 @@ func TestSimpleClusterDecisionResourceGeneratorDeletingClusterSecret(t *testing.
Name: "{{name}}-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-cluster-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -447,9 +430,6 @@ func TestSimpleClusterDecisionResourceGeneratorDeletingClusterFromResource(t *te
Name: "{{name}}-guestbook",
Namespace: fixture.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "simple-cluster-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",

View File

@@ -26,9 +26,6 @@ func TestListMatrixGenerator(t *testing.T) {
Name: fmt.Sprintf("%s-%s", cluster, name),
Namespace: utils.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "matrix-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -128,10 +125,7 @@ func TestListMatrixGenerator(t *testing.T) {
for _, expectedApp := range expectedAppsNewNamespace {
expectedAppNewMetadata := expectedApp.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{
"label-key": "label-value",
LabelKeyAppSetInstance: "matrix-generator",
}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"}
expectedAppsNewMetadata = append(expectedAppsNewMetadata, *expectedAppNewMetadata)
}
}).
@@ -156,9 +150,6 @@ func TestClusterMatrixGenerator(t *testing.T) {
Name: fmt.Sprintf("%s-%s", cluster, name),
Namespace: utils.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "matrix-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -261,10 +252,7 @@ func TestClusterMatrixGenerator(t *testing.T) {
for _, expectedApp := range expectedAppsNewNamespace {
expectedAppNewMetadata := expectedApp.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{
"label-key": "label-value",
LabelKeyAppSetInstance: "matrix-generator",
}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"}
expectedAppsNewMetadata = append(expectedAppsNewMetadata, *expectedAppNewMetadata)
}
}).
@@ -289,9 +277,6 @@ func TestMatrixTerminalMatrixGeneratorSelector(t *testing.T) {
Name: fmt.Sprintf("%s-%s", cluster, name),
Namespace: utils.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "matrix-generator-nested-matrix",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -443,9 +428,6 @@ func TestMatrixTerminalMergeGeneratorSelector(t *testing.T) {
Name: fmt.Sprintf("%s-%s", name, nameSuffix),
Namespace: utils.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "matrix-generator-nested-merge",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",

View File

@@ -27,9 +27,6 @@ func TestListMergeGenerator(t *testing.T) {
Name: fmt.Sprintf("%s-%s", name, nameSuffix),
Namespace: utils.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "merge-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -125,10 +122,7 @@ func TestListMergeGenerator(t *testing.T) {
for _, expectedApp := range expectedAppsNewNamespace {
expectedAppNewMetadata := expectedApp.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{
"label-key": "label-value",
LabelKeyAppSetInstance: "merge-generator",
}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"}
expectedAppsNewMetadata = append(expectedAppsNewMetadata, *expectedAppNewMetadata)
}
}).
@@ -153,9 +147,6 @@ func TestClusterMergeGenerator(t *testing.T) {
Name: fmt.Sprintf("%s-%s-%s", cluster, name, nameSuffix),
Namespace: utils.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "merge-generator",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
@@ -276,10 +267,7 @@ func TestClusterMergeGenerator(t *testing.T) {
for _, expectedApp := range expectedAppsNewNamespace {
expectedAppNewMetadata := expectedApp.DeepCopy()
expectedAppNewMetadata.ObjectMeta.Annotations = map[string]string{"annotation-key": "annotation-value"}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{
"label-key": "label-value",
LabelKeyAppSetInstance: "merge-generator",
}
expectedAppNewMetadata.ObjectMeta.Labels = map[string]string{"label-key": "label-value"}
expectedAppsNewMetadata = append(expectedAppsNewMetadata, *expectedAppNewMetadata)
}
}).
@@ -304,9 +292,6 @@ func TestMergeTerminalMergeGeneratorSelector(t *testing.T) {
Name: fmt.Sprintf("%s-%s", name, nameSuffix),
Namespace: utils.TestNamespace(),
Finalizers: []string{"resources-finalizer.argocd.argoproj.io"},
Labels: map[string]string{
LabelKeyAppSetInstance: "merge-generator-nested-merge",
},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",

View File

@@ -0,0 +1,23 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: guestbook-ui
labels:
test: "true"
spec:
replicas: 0
revisionHistoryLimit: 3
selector:
matchLabels:
app: guestbook-ui
template:
metadata:
labels:
app: guestbook-ui
spec:
containers:
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
imagePullPolicy: IfNotPresent
name: guestbook-ui
ports:
- containerPort: 80

View File

@@ -0,0 +1,9 @@
apiVersion: v1
kind: Namespace
metadata:
name: guestbook-ui-with-namespace-manifest
labels:
test: "true"
annotations:
foo: bar
something: else

View File

@@ -0,0 +1,10 @@
apiVersion: v1
kind: Service
metadata:
name: guestbook-ui
spec:
ports:
- port: 80
targetPort: 80
selector:
app: guestbook-ui

View File

@@ -32,7 +32,7 @@ export const RevisionMetadataRows = (props: {applicationName: string; applicatio
<div className='columns small-9'>{m.description}</div>
</div>
)}
{m.maintainers.length > 0 && (
{m.maintainers && m.maintainers.length > 0 && (
<div className='row'>
<div className='columns small-3'>Maintainers:</div>
<div className='columns small-9'>{m.maintainers.join(', ')}</div>

View File

@@ -1,4 +1,4 @@
import {DropDownMenu, NotificationType, SlidingPanel} from 'argo-ui';
import {DropDownMenu, NotificationType, SlidingPanel, Tooltip} from 'argo-ui';
import * as classNames from 'classnames';
import * as PropTypes from 'prop-types';
import * as React from 'react';
@@ -147,7 +147,8 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
this.setState({slidingPanelPage: 0});
}
private toggleCompactView(pref: AppDetailsPreferences) {
private toggleCompactView(appName: string, pref: AppDetailsPreferences) {
pref.userHelpTipMsgs = pref.userHelpTipMsgs.map(usrMsg => (usrMsg.appName === appName && usrMsg.msgKey === 'groupNodes' ? {...usrMsg, display: true} : usrMsg));
services.viewPreferences.updatePreferences({appDetails: {...pref, groupNodes: !pref.groupNodes}});
}
@@ -231,6 +232,7 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
const syncResourceKey = new URLSearchParams(this.props.history.location.search).get('deploy');
const tab = new URLSearchParams(this.props.history.location.search).get('tab');
const source = getAppDefaultSource(application);
const showToolTip = pref?.userHelpTipMsgs.find(usrMsg => usrMsg.appName === application.metadata.name);
const resourceNodes = (): any[] => {
const statusByKey = new Map<string, models.ResourceStatus>();
application.status.resources.forEach(res => statusByKey.set(AppUtils.nodeKey(res), res));
@@ -296,6 +298,14 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
const setShowCompactNodes = (showCompactView: boolean) => {
services.viewPreferences.updatePreferences({appDetails: {...pref, groupNodes: showCompactView}});
};
const updateHelpTipState = (usrHelpTip: models.UserMessages) => {
const existingIndex = pref.userHelpTipMsgs.findIndex(msg => msg.appName === usrHelpTip.appName && msg.msgKey === usrHelpTip.msgKey);
if (existingIndex !== -1) {
pref.userHelpTipMsgs[existingIndex] = usrHelpTip;
} else {
(pref.userHelpTipMsgs || []).push(usrHelpTip);
}
};
const toggleNameDirection = () => {
this.setState({truncateNameOnRight: !this.state.truncateNameOnRight});
};
@@ -446,13 +456,19 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
/>
</a>
{(pref.view === 'tree' || pref.view === 'network') && (
<a
className={`group-nodes-button group-nodes-button${!pref.groupNodes ? '' : '-on'}`}
title={pref.view === 'tree' ? 'Group Nodes' : 'Collapse Pods'}
onClick={() => this.toggleCompactView(pref)}>
<i className={classNames('fa fa-object-group fa-fw')} />
</a>
<Tooltip
content={AppUtils.userMsgsList[showToolTip?.msgKey] || 'Group Nodes'}
visible={pref.groupNodes && showToolTip !== undefined && !showToolTip?.display}
duration={showToolTip?.duration}>
<a
className={`group-nodes-button group-nodes-button${!pref.groupNodes ? '' : '-on'}`}
title={pref.view === 'tree' ? 'Group Nodes' : 'Collapse Pods'}
onClick={() => this.toggleCompactView(application.metadata.name, pref)}>
<i className={classNames('fa fa-object-group fa-fw')} />
</a>
</Tooltip>
)}
<span className={`separator`} />
<a className={`group-nodes-button`} onClick={() => expandAll()} title='Expand all child nodes of all parent nodes'>
<i className='fa fa-plus fa-fw' />
@@ -481,6 +497,7 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
)
}
showCompactNodes={pref.groupNodes}
userMsgs={pref.userHelpTipMsgs}
tree={tree}
app={application}
showOrphanedResources={pref.orphanedResources}
@@ -493,6 +510,7 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
nameDirection={this.state.truncateNameOnRight}
filters={pref.resourceFilter}
setTreeFilterGraph={setFilterGraph}
updateUsrHelpTipMsgs={updateHelpTipState}
setShowCompactNodes={setShowCompactNodes}
setNodeExpansion={(node, isExpanded) => this.setNodeExpansion(node, isExpanded)}
getNodeExpansion={node => this.getNodeExpansion(node)}
@@ -651,7 +669,7 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
<div className='columns small-9'>{m.description}</div>
</div>
)}
{m.maintainers.length > 0 && (
{m.maintainers && m.maintainers.length > 0 && (
<div className='row white-box__details-row'>
<div className='columns small-3'>Maintainers:</div>
<div className='columns small-9'>{m.maintainers.join(', ')}</div>

View File

@@ -1,4 +1,4 @@
import {DropDown, DropDownMenu, NotificationType, Tooltip} from 'argo-ui';
import {DropDown, DropDownMenu, Tooltip} from 'argo-ui';
import * as classNames from 'classnames';
import * as dagre from 'dagre';
import * as React from 'react';
@@ -22,7 +22,8 @@ import {
isYoungerThanXMinutes,
NodeId,
nodeKey,
PodHealthIcon
PodHealthIcon,
getUsrMsgKeyToDisplay
} from '../utils';
import {NodeUpdateAnimation} from './node-update-animation';
import {PodGroup} from '../application-pod-view/pod-view';
@@ -59,6 +60,8 @@ export interface ApplicationResourceTreeProps {
appContext?: AppContext;
showOrphanedResources: boolean;
showCompactNodes: boolean;
userMsgs: models.UserMessages[];
updateUsrHelpTipMsgs: (userMsgs: models.UserMessages) => void;
setShowCompactNodes: (showCompactNodes: boolean) => void;
zoom: number;
podGroupCount: number;
@@ -927,6 +930,7 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) =>
const [filters, setFilters] = React.useState(props.filters);
const [filteredGraph, setFilteredGraph] = React.useState([]);
const filteredNodes: any[] = [];
React.useEffect(() => {
if (props.filters !== filters) {
setFilters(props.filters);
@@ -934,19 +938,16 @@ export const ApplicationResourceTree = (props: ApplicationResourceTreeProps) =>
props.setTreeFilterGraph(filteredGraph);
}
}, [props.filters]);
const {podGroupCount, userMsgs, updateUsrHelpTipMsgs, setShowCompactNodes} = props;
const podCount = nodes.filter(node => node.kind === 'Pod').length;
React.useEffect(() => {
const {podGroupCount, setShowCompactNodes, appContext} = props;
if (podCount > podGroupCount) {
setShowCompactNodes(true);
appContext.apis.notifications.show({
content: `Since the number of pods has surpassed the threshold pod count of ${podGroupCount}, you will now be switched to the group node view.
If you prefer the tree view, you can simply click on the Group Nodes toolbar button to deselect the current view.`,
type: NotificationType.Success
});
} else {
props.setShowCompactNodes(false);
const userMsg = getUsrMsgKeyToDisplay(appNode.name, 'groupNodes', userMsgs);
updateUsrHelpTipMsgs(userMsg);
if (!userMsg.display) {
setShowCompactNodes(true);
}
}
}, [podCount]);

View File

@@ -1252,3 +1252,17 @@ export function formatCreationTimestamp(creationTimestamp: string) {
}
export const selectPostfix = (arr: string[], singular: string, plural: string) => (arr.length > 1 ? plural : singular);
export function getUsrMsgKeyToDisplay(appName: string, msgKey: string, usrMessages: appModels.UserMessages[]) {
const usrMsg = usrMessages?.find((msg: appModels.UserMessages) => msg.appName === appName && msg.msgKey === msgKey);
if (usrMsg !== undefined) {
return {...usrMsg, display: true};
} else {
return {appName, msgKey, display: false, duration: 1} as appModels.UserMessages;
}
}
export const userMsgsList: {[key: string]: string} = {
groupNodes: `Since the number of pods has surpassed the threshold pod count of 15, you will now be switched to the group node view.
If you prefer the tree view, you can simply click on the Group Nodes toolbar button to deselect the current view.`
};

View File

@@ -955,3 +955,12 @@ export interface LinkInfo {
export interface LinksResponse {
items: LinkInfo[];
}
export interface UserMessages {
appName: string;
msgKey: string;
display: boolean;
condition?: HealthStatusCode;
duration?: number;
animation?: string;
}

View File

@@ -2,6 +2,7 @@ import * as deepMerge from 'deepmerge';
import {BehaviorSubject, Observable} from 'rxjs';
import {PodGroupType} from '../../applications/components/application-pod-view/pod-view';
import {UserMessages} from '../models';
export type AppsDetailsViewType = 'tree' | 'network' | 'list' | 'pods';
@@ -28,6 +29,7 @@ export interface AppDetailsPreferences {
groupNodes?: boolean;
zoom: number;
podGroupCount: number;
userHelpTipMsgs: UserMessages[];
}
export interface PodViewPreferences {
@@ -122,7 +124,8 @@ const DEFAULT_PREFERENCES: ViewPreferences = {
followLogs: false,
wrapLines: false,
zoom: 1.0,
podGroupCount: 15.0
podGroupCount: 15.0,
userHelpTipMsgs: []
},
appList: {
view: 'tiles' as AppsListViewType,

View File

@@ -165,7 +165,7 @@ func (rt *resourceTracking) BuildAppInstanceValue(value AppInstanceValue) string
//ParseAppInstanceValue parse resource tracking id from format <application-name>:<group>/<kind>:<namespace>/<name> to struct
func (rt *resourceTracking) ParseAppInstanceValue(value string) (*AppInstanceValue, error) {
var appInstanceValue AppInstanceValue
parts := strings.Split(value, ":")
parts := strings.SplitN(value, ":", 3)
appInstanceValue.ApplicationName = parts[0]
if len(parts) != 3 {
return nil, WrongResourceTrackingFormat

View File

@@ -89,6 +89,19 @@ func TestParseAppInstanceValue(t *testing.T) {
assert.Equal(t, appInstanceValue.Name, "<name>")
}
func TestParseAppInstanceValueColon(t *testing.T) {
resourceTracking := NewResourceTracking()
appInstanceValue, err := resourceTracking.ParseAppInstanceValue("app:<group>/<kind>:<namespace>/<name>:<colon>")
if !assert.NoError(t, err) {
t.Fatal()
}
assert.Equal(t, appInstanceValue.ApplicationName, "app")
assert.Equal(t, appInstanceValue.Group, "<group>")
assert.Equal(t, appInstanceValue.Kind, "<kind>")
assert.Equal(t, appInstanceValue.Namespace, "<namespace>")
assert.Equal(t, appInstanceValue.Name, "<name>:<colon>")
}
func TestParseAppInstanceValueWrongFormat1(t *testing.T) {
resourceTracking := NewResourceTracking()
_, err := resourceTracking.ParseAppInstanceValue("app")

View File

@@ -345,6 +345,9 @@ func clusterToSecret(c *appv1.Cluster, secret *apiv1.Secret) error {
secret.Data = data
secret.Labels = c.Labels
if c.Annotations != nil && c.Annotations[apiv1.LastAppliedConfigAnnotation] != "" {
return status.Errorf(codes.InvalidArgument, "annotation %s cannot be set", apiv1.LastAppliedConfigAnnotation)
}
secret.Annotations = c.Annotations
if secret.Annotations == nil {
@@ -403,6 +406,8 @@ func SecretToCluster(s *apiv1.Secret) (*appv1.Cluster, error) {
annotations := map[string]string{}
if s.Annotations != nil {
annotations = collections.CopyStringMap(s.Annotations)
// delete system annotations
delete(annotations, apiv1.LastAppliedConfigAnnotation)
delete(annotations, common.AnnotationKeyManagedBy)
}

View File

@@ -7,6 +7,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
@@ -56,6 +58,24 @@ func Test_secretToCluster(t *testing.T) {
})
}
func Test_secretToCluster_LastAppliedConfigurationDropped(t *testing.T) {
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "mycluster",
Namespace: fakeNamespace,
Annotations: map[string]string{v1.LastAppliedConfigAnnotation: "val2"},
},
Data: map[string][]byte{
"name": []byte("test"),
"server": []byte("http://mycluster"),
"config": []byte("{\"username\":\"foo\"}"),
},
}
cluster, err := SecretToCluster(secret)
require.NoError(t, err)
assert.Len(t, cluster.Annotations, 0)
}
func TestClusterToSecret(t *testing.T) {
cluster := &appv1.Cluster{
Server: "server",
@@ -78,6 +98,21 @@ func TestClusterToSecret(t *testing.T) {
assert.Equal(t, cluster.Labels, s.Labels)
}
func TestClusterToSecret_LastAppliedConfigurationRejected(t *testing.T) {
cluster := &appv1.Cluster{
Server: "server",
Annotations: map[string]string{v1.LastAppliedConfigAnnotation: "val2"},
Name: "test",
Config: v1alpha1.ClusterConfig{},
Project: "project",
Namespaces: []string{"default"},
}
s := &v1.Secret{}
err := clusterToSecret(cluster, s)
require.Error(t, err)
require.Equal(t, codes.InvalidArgument, status.Code(err))
}
func Test_secretToCluster_NoConfig(t *testing.T) {
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{

33
util/grpc/trace.go Normal file
View File

@@ -0,0 +1,33 @@
package grpc
import (
"sync"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
)
var (
otelUnaryInterceptor grpc.UnaryClientInterceptor
otelStreamInterceptor grpc.StreamClientInterceptor
interceptorsInitialized = sync.Once{}
)
// otel interceptors must be created once to avoid memory leak
// see https://github.com/open-telemetry/opentelemetry-go-contrib/issues/4226 for details
func ensureInitialized() {
interceptorsInitialized.Do(func() {
otelUnaryInterceptor = otelgrpc.UnaryClientInterceptor()
otelStreamInterceptor = otelgrpc.StreamClientInterceptor()
})
}
func OTELUnaryClientInterceptor() grpc.UnaryClientInterceptor {
ensureInitialized()
return otelUnaryInterceptor
}
func OTELStreamClientInterceptor() grpc.StreamClientInterceptor {
ensureInitialized()
return otelStreamInterceptor
}

View File

@@ -8,6 +8,7 @@ import (
"encoding/json"
"errors"
"fmt"
executil "github.com/argoproj/argo-cd/v2/util/exec"
"io"
"net/http"
"net/url"
@@ -25,7 +26,6 @@ import (
"oras.land/oras-go/v2/registry/remote/auth"
"github.com/argoproj/argo-cd/v2/util/cache"
executil "github.com/argoproj/argo-cd/v2/util/exec"
argoio "github.com/argoproj/argo-cd/v2/util/io"
"github.com/argoproj/argo-cd/v2/util/io/files"
"github.com/argoproj/argo-cd/v2/util/proxy"
@@ -52,7 +52,7 @@ type indexCache interface {
type Client interface {
CleanChartCache(chart string, version string) error
ExtractChart(chart string, version string, passCredentials bool) (string, argoio.Closer, error)
ExtractChart(chart string, version string, passCredentials bool, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) (string, argoio.Closer, error)
GetIndex(noCache bool) (*Index, error)
GetTags(chart string, noCache bool) (*TagsList, error)
TestHelmOCI() (bool, error)
@@ -122,7 +122,21 @@ func (c *nativeHelmChart) CleanChartCache(chart string, version string) error {
return os.RemoveAll(cachePath)
}
func (c *nativeHelmChart) ExtractChart(chart string, version string, passCredentials bool) (string, argoio.Closer, error) {
func untarChart(tempDir string, cachedChartPath string, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) error {
if disableManifestMaxExtractedSize {
cmd := exec.Command("tar", "-zxvf", cachedChartPath)
cmd.Dir = tempDir
_, err := executil.Run(cmd)
return err
}
reader, err := os.Open(cachedChartPath)
if err != nil {
return err
}
return files.Untgz(tempDir, reader, manifestMaxExtractedSize, false)
}
func (c *nativeHelmChart) ExtractChart(chart string, version string, passCredentials bool, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) (string, argoio.Closer, error) {
// always use Helm V3 since we don't have chart content to determine correct Helm version
helmCmd, err := NewCmdWithVersion("", HelmV3, c.enableOci, c.proxy)
@@ -196,15 +210,14 @@ func (c *nativeHelmChart) ExtractChart(chart string, version string, passCredent
if len(infos) != 1 {
return "", nil, fmt.Errorf("expected 1 file, found %v", len(infos))
}
err = os.Rename(filepath.Join(tempDest, infos[0].Name()), cachedChartPath)
if err != nil {
return "", nil, err
}
}
cmd := exec.Command("tar", "-zxvf", cachedChartPath)
cmd.Dir = tempDir
_, err = executil.Run(cmd)
err = untarChart(tempDir, cachedChartPath, manifestMaxExtractedSize, disableManifestMaxExtractedSize)
if err != nil {
_ = os.RemoveAll(tempDir)
return "", nil, err

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"math"
"os"
"strings"
"testing"
@@ -71,7 +72,7 @@ func TestIndex(t *testing.T) {
func Test_nativeHelmChart_ExtractChart(t *testing.T) {
client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "")
path, closer, err := client.ExtractChart("argo-cd", "0.7.1", false)
path, closer, err := client.ExtractChart("argo-cd", "0.7.1", false, math.MaxInt64, true)
assert.NoError(t, err)
defer io.Close(closer)
info, err := os.Stat(path)
@@ -79,9 +80,15 @@ func Test_nativeHelmChart_ExtractChart(t *testing.T) {
assert.True(t, info.IsDir())
}
func Test_nativeHelmChart_ExtractChartWithLimiter(t *testing.T) {
client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "")
_, _, err := client.ExtractChart("argo-cd", "0.7.1", false, 100, false)
assert.Error(t, err, "error while iterating on tar reader: unexpected EOF")
}
func Test_nativeHelmChart_ExtractChart_insecure(t *testing.T) {
client := NewClient("https://argoproj.github.io/argo-helm", Creds{InsecureSkipVerify: true}, false, "")
path, closer, err := client.ExtractChart("argo-cd", "0.7.1", false)
path, closer, err := client.ExtractChart("argo-cd", "0.7.1", false, math.MaxInt64, true)
assert.NoError(t, err)
defer io.Close(closer)
info, err := os.Stat(path)

View File

@@ -29,7 +29,7 @@ func (_m *Client) CleanChartCache(chart string, version string) error {
}
// ExtractChart provides a mock function with given fields: chart, version
func (_m *Client) ExtractChart(chart string, version string, passCredentials bool) (string, io.Closer, error) {
func (_m *Client) ExtractChart(chart string, version string, passCredentials bool, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) (string, io.Closer, error) {
ret := _m.Called(chart, version)
var r0 string

View File

@@ -18,8 +18,8 @@ import (
const maxCookieLength = 4093
// max number of chunks a cookie can be broken into. To be compatible with
// widest range of browsers, we shouldn't create more than 30 cookies per domain
var maxCookieNumber = env.ParseNumFromEnv(common.EnvMaxCookieNumber, 10, 0, 30)
// widest range of browsers, you shouldn't create more than 30 cookies per domain
var maxCookieNumber = env.ParseNumFromEnv(common.EnvMaxCookieNumber, 20, 0, math.MaxInt64)
// MakeCookieMetadata generates a string representing a Web cookie. Yum!
func MakeCookieMetadata(key, value string, flags ...string) ([]string, error) {

View File

@@ -15,10 +15,18 @@ func TestCookieMaxLength(t *testing.T) {
// keys will be of format foo, foo-1, foo-2 ..
cookies, err = MakeCookieMetadata("foo", strings.Repeat("_", (maxCookieLength-5)*maxCookieNumber))
assert.EqualError(t, err, "the authentication token is 40880 characters long and requires 11 cookies but the max number of cookies is 10. Contact your Argo CD administrator to increase the max number of cookies")
assert.EqualError(t, err, "the authentication token is 81760 characters long and requires 21 cookies but the max number of cookies is 20. Contact your Argo CD administrator to increase the max number of cookies")
assert.Equal(t, 0, len(cookies))
}
func TestCookieWithAttributes(t *testing.T) {
flags := []string{"SameSite=lax", "httpOnly"}
cookies, err := MakeCookieMetadata("foo", "bar", flags...)
assert.NoError(t, err)
assert.Equal(t, "foo=bar; SameSite=lax; httpOnly", cookies[0])
}
func TestSplitCookie(t *testing.T) {
cookieValue := strings.Repeat("_", (maxCookieLength-6)*4)
cookies, err := MakeCookieMetadata("foo", cookieValue)