Compare commits

...

23 Commits

Author SHA1 Message Date
github-actions[bot]
4dab5bd6a6 Bump version to 2.12.6 (#20455)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: crenshaw-dev <350466+crenshaw-dev@users.noreply.github.com>
2024-10-18 13:27:10 -04:00
gcp-cherry-pick-bot[bot]
358930be06 fix: don't disable buttons for multi-source apps (#20446) (#20448)
With #20381 multi-source apps were not taken into account 🤦

Fixes #20445.

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



* fix silly mistakes



---------

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

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



* chore(ui): some cr tweaks



* chore(ui): some cr tweaks



---------

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



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



* fix(controller): update info test case conditions




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



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



---------

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

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

* Update util/git/client_test.go

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

* rm bad metrics cherry pick

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

---------

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

* chore: add optional password setting for headless redis client

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

* fix: remove import cycle

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

* fix: add shared SetOptionalRedisPasswordFromKubeConfig method

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

* fix: export redis consts

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

* test: add test cases for SetOptionalRedisPasswordFromKubeConfig()

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

* chore: go mod tidy

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

* fix: use require instead of assert

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

* fix: Update common/common.go

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

---------

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

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

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

---------

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

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

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

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

This then triggers reconciliation of all objects watching the
ApplicationSet.

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

Fixes: #19757

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

Related to #18748,#19585 and #19587.



* docs: add note in projects doc.



---------

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



* feat(sourceNamespace): Separate exactMatch into patternMatch



---------

Signed-off-by: Arthur <arthur@arthurvardevanyan.com>
Co-authored-by: Arthur Vardevanyan <arthur@arthurvardevanyan.com>
2024-08-23 08:53:48 -04:00
56 changed files with 1605 additions and 352 deletions

View File

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

View File

@@ -1 +1 @@
2.12.2
2.12.6

View File

@@ -18,6 +18,7 @@ import (
"context"
"fmt"
"reflect"
"sort"
"strings"
"time"
@@ -580,7 +581,7 @@ func (r *ApplicationSetReconciler) applyTemplatePatch(app *argov1alpha1.Applicat
func ignoreNotAllowedNamespaces(namespaces []string) predicate.Predicate {
return predicate.Funcs{
CreateFunc: func(e event.CreateEvent) bool {
return glob.MatchStringInList(namespaces, e.Object.GetNamespace(), false)
return glob.MatchStringInList(namespaces, e.Object.GetNamespace(), glob.REGEXP)
},
}
}
@@ -1365,6 +1366,9 @@ func (r *ApplicationSetReconciler) updateResourcesStatus(ctx context.Context, lo
for _, status := range statusMap {
statuses = append(statuses, status)
}
sort.Slice(statuses, func(i, j int) bool {
return statuses[i].Name < statuses[j].Name
})
appset.Status.Resources = statuses
namespacedName := types.NamespacedName{Namespace: appset.Namespace, Name: appset.Name}

View File

@@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
"testing"
"time"
@@ -6351,6 +6352,102 @@ func TestUpdateResourceStatus(t *testing.T) {
}
}
func generateNAppResourceStatuses(n int) []v1alpha1.ResourceStatus {
var r []v1alpha1.ResourceStatus
for i := 0; i < n; i++ {
r = append(r, v1alpha1.ResourceStatus{
Name: "app" + strconv.Itoa(i),
Status: v1alpha1.SyncStatusCodeSynced,
Health: &v1alpha1.HealthStatus{
Status: health.HealthStatusHealthy,
Message: "OK",
},
},
)
}
return r
}
func generateNHealthyApps(n int) []v1alpha1.Application {
var r []v1alpha1.Application
for i := 0; i < n; i++ {
r = append(r, v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "app" + strconv.Itoa(i),
},
Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{
Status: v1alpha1.SyncStatusCodeSynced,
},
Health: v1alpha1.HealthStatus{
Status: health.HealthStatusHealthy,
Message: "OK",
},
},
})
}
return r
}
func TestResourceStatusAreOrdered(t *testing.T) {
scheme := runtime.NewScheme()
err := v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
err = v1alpha1.AddToScheme(scheme)
require.NoError(t, err)
for _, cc := range []struct {
name string
appSet v1alpha1.ApplicationSet
apps []v1alpha1.Application
expectedResources []v1alpha1.ResourceStatus
}{
{
name: "Ensures AppSet is always ordered",
appSet: v1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "argocd",
},
Status: v1alpha1.ApplicationSetStatus{
Resources: []v1alpha1.ResourceStatus{},
},
},
apps: generateNHealthyApps(10),
expectedResources: generateNAppResourceStatuses(10),
},
} {
t.Run(cc.name, func(t *testing.T) {
kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...)
argoDBMock := dbmocks.ArgoDB{}
argoObjs := []runtime.Object{}
client := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(&cc.appSet).WithObjects(&cc.appSet).Build()
r := ApplicationSetReconciler{
Client: client,
Scheme: scheme,
Recorder: record.NewFakeRecorder(1),
Generators: map[string]generators.Generator{},
ArgoDB: &argoDBMock,
ArgoAppClientset: appclientset.NewSimpleClientset(argoObjs...),
KubeClientset: kubeclientset,
}
err := r.updateResourcesStatus(context.TODO(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.apps)
require.NoError(t, err, "expected no errors, but errors occurred")
err = r.updateResourcesStatus(context.TODO(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.apps)
require.NoError(t, err, "expected no errors, but errors occurred")
err = r.updateResourcesStatus(context.TODO(), log.NewEntry(log.StandardLogger()), &cc.appSet, cc.apps)
require.NoError(t, err, "expected no errors, but errors occurred")
assert.Equal(t, cc.expectedResources, cc.appSet.Status.Resources, "expected resources did not match actual")
})
}
}
func TestOwnsHandler(t *testing.T) {
// progressive syncs do not affect create, delete, or generic
ownsHandler := getOwnsHandlerPredicates(true)

View File

@@ -106,14 +106,9 @@ func loadClusters(ctx context.Context, kubeClient *kubernetes.Clientset, appClie
}
redisOptions := &redis.Options{Addr: fmt.Sprintf("localhost:%d", port)}
secret, err := kubeClient.CoreV1().Secrets(namespace).Get(context.Background(), defaulRedisInitialPasswordSecretName, v1.GetOptions{})
if err == nil {
if _, ok := secret.Data[defaultResisInitialPasswordKey]; ok {
redisOptions.Password = string(secret.Data[defaultResisInitialPasswordKey])
}
if err = common.SetOptionalRedisPasswordFromKubeConfig(ctx, kubeClient, namespace, redisOptions); err != nil {
log.Warnf("Failed to fetch & set redis password for namespace %s: %v", namespace, err)
}
client := redis.NewClient(redisOptions)
compressionType, err := cacheutil.CompressionTypeFromString(redisCompressionStr)
if err != nil {

View File

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

View File

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

View File

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

View File

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

View File

@@ -2011,7 +2011,7 @@ func (ctrl *ApplicationController) shouldSelfHeal(app *appv1.Application) (bool,
// isAppNamespaceAllowed returns whether the application is allowed in the
// namespace it's residing in.
func (ctrl *ApplicationController) isAppNamespaceAllowed(app *appv1.Application) bool {
return app.Namespace == ctrl.namespace || glob.MatchStringInList(ctrl.applicationNamespaces, app.Namespace, false)
return app.Namespace == ctrl.namespace || glob.MatchStringInList(ctrl.applicationNamespaces, app.Namespace, glob.REGEXP)
}
func (ctrl *ApplicationController) canProcessApp(obj interface{}) bool {

View File

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

View File

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

View File

@@ -897,9 +897,7 @@ func useDiffCache(noCache bool, manifestInfos []*apiclient.ManifestResponse, sou
return false
}
currentSpec := app.BuildComparedToStatus()
specChanged := !reflect.DeepEqual(app.Status.Sync.ComparedTo, currentSpec)
if specChanged {
if !specEqualsCompareTo(app.Spec, app.Status.Sync.ComparedTo) {
log.WithField("useDiffCache", "false").Debug("specChanged")
return false
}
@@ -908,6 +906,25 @@ func useDiffCache(noCache bool, manifestInfos []*apiclient.ManifestResponse, sou
return true
}
// specEqualsCompareTo compares the application spec to the comparedTo status. It normalizes the destination to match
// the comparedTo destination before comparing. It does not mutate the original spec or comparedTo.
func specEqualsCompareTo(spec v1alpha1.ApplicationSpec, comparedTo v1alpha1.ComparedTo) bool {
// Make a copy to be sure we don't mutate the original.
specCopy := spec.DeepCopy()
currentSpec := specCopy.BuildComparedToStatus()
// The spec might have been augmented to include both server and name, so change it to match the comparedTo before
// comparing.
if comparedTo.Destination.Server == "" {
currentSpec.Destination.Server = ""
}
if comparedTo.Destination.Name == "" {
currentSpec.Destination.Name = ""
}
return reflect.DeepEqual(comparedTo, currentSpec)
}
func (m *appStateManager) persistRevisionHistory(
app *v1alpha1.Application,
revision string,

View File

@@ -1432,6 +1432,8 @@ func TestIsLiveResourceManaged(t *testing.T) {
}
func TestUseDiffCache(t *testing.T) {
t.Parallel()
type fixture struct {
testName string
noCache bool
@@ -1692,6 +1694,44 @@ func TestUseDiffCache(t *testing.T) {
expectedUseCache: false,
serverSideDiff: false,
},
{
// There are code paths that modify the ApplicationSpec and augment the destination field with both the
// destination server and name. Since both fields are populated in the app spec but not in the comparedTo,
// we need to make sure we correctly compare the fields and don't miss the cache.
testName: "will return true if the app spec destination contains both server and name, but otherwise matches comparedTo",
noCache: false,
manifestInfos: manifestInfos("rev1"),
sources: sources(),
app: app("httpbin", "rev1", false, &argoappv1.Application{
Spec: argoappv1.ApplicationSpec{
Destination: argoappv1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Name: "httpbin",
Namespace: "httpbin",
},
},
Status: argoappv1.ApplicationStatus{
Resources: []argoappv1.ResourceStatus{},
Sync: argoappv1.SyncStatus{
Status: argoappv1.SyncStatusCodeSynced,
ComparedTo: argoappv1.ComparedTo{
Destination: argoappv1.ApplicationDestination{
Server: "https://kubernetes.default.svc",
Namespace: "httpbin",
},
},
Revision: "rev1",
},
ReconciledAt: &metav1.Time{
Time: time.Now().Add(-time.Hour),
},
},
}),
manifestRevisions: []string{"rev1"},
statusRefreshTimeout: time.Hour * 24,
expectedUseCache: true,
serverSideDiff: true,
},
}
for _, tc := range cases {

View File

@@ -32,23 +32,41 @@ function initializeVersionDropdown() {
window[callbackName] = function(response) {
const div = document.createElement('div');
div.innerHTML = response.html;
document.querySelector(".md-header__inner > .md-header__title").appendChild(div);
const headerTitle = document.querySelector(".md-header__inner > .md-header__title");
if (headerTitle) {
headerTitle.appendChild(div);
}
const container = div.querySelector('.rst-versions');
if (!container) return; // Exit if container not found
// Add caret icon
var caret = document.createElement('div');
caret.innerHTML = "<i class='fa fa-caret-down dropdown-caret'></i>";
caret.classList.add('dropdown-caret');
div.querySelector('.rst-current-version').appendChild(caret);
const currentVersionElem = div.querySelector('.rst-current-version');
if (currentVersionElem) {
currentVersionElem.appendChild(caret);
}
div.querySelector('.rst-current-version').addEventListener('click', function() {
container.classList.toggle('shift-up');
});
// Add click listener to toggle dropdown
if (currentVersionElem && container) {
currentVersionElem.addEventListener('click', function() {
container.classList.toggle('shift-up');
});
}
// Sorting Logic
sortVersionLinks(container);
};
// Load CSS
var CSSLink = document.createElement('link');
CSSLink.rel = 'stylesheet';
CSSLink.href = '/assets/versions.css';
document.getElementsByTagName('head')[0].appendChild(CSSLink);
// Load JSONP Script
var script = document.createElement('script');
const currentVersion = getCurrentVersion();
script.src = 'https://argo-cd.readthedocs.io/_/api/v2/footer_html/?' +
@@ -56,6 +74,58 @@ function initializeVersionDropdown() {
document.getElementsByTagName('head')[0].appendChild(script);
}
// Function to sort version links
function sortVersionLinks(container) {
// Find all <dl> elements within the container
const dlElements = container.querySelectorAll('dl');
dlElements.forEach(dl => {
const dt = dl.querySelector('dt');
if (dt && dt.textContent.trim().toLowerCase() === 'versions') {
// Found the Versions <dl>
const ddElements = Array.from(dl.querySelectorAll('dd'));
// Define sorting criteria
ddElements.sort((a, b) => {
const aText = a.textContent.trim().toLowerCase();
const bText = b.textContent.trim().toLowerCase();
// Prioritize 'latest' and 'stable'
if (aText === 'latest') return -1;
if (bText === 'latest') return 1;
if (aText === 'stable') return -1;
if (bText === 'stable') return 1;
// Extract version numbers (e.g., release-2.9)
const aVersionMatch = aText.match(/release-(\d+(\.\d+)*)/);
const bVersionMatch = bText.match(/release-(\d+(\.\d+)*)/);
if (aVersionMatch && bVersionMatch) {
const aVersion = aVersionMatch[1].split('.').map(Number);
const bVersion = bVersionMatch[1].split('.').map(Number);
for (let i = 0; i < Math.max(aVersion.length, bVersion.length); i++) {
const aNum = aVersion[i] || 0;
const bNum = bVersion[i] || 0;
if (aNum > bNum) return -1;
if (aNum < bNum) return 1;
}
return 0;
}
// Fallback to alphabetical order
return aText.localeCompare(bText);
});
// Remove existing <dd> elements
ddElements.forEach(dd => dl.removeChild(dd));
// Append sorted <dd> elements
ddElements.forEach(dd => dl.appendChild(dd));
}
});
}
// VERSION WARNINGS
window.addEventListener("DOMContentLoaded", function() {
var margin = 30;

View File

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

View File

@@ -1,5 +1,5 @@
| Argo CD version | Kubernetes versions |
|-----------------|---------------------|
| 2.12 | |
| 2.12 | v1.29, v1.28, v1.27, v1.26 |
| 2.11 | v1.29, v1.28, v1.27, v1.26, v1.25 |
| 2.10 | v1.28, v1.27, v1.26, v1.25 |

View File

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

View File

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

3
go.mod
View File

@@ -187,6 +187,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.11.2
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/evanphx/json-patch/v5 v5.8.0 // indirect
@@ -249,7 +250,7 @@ require (
github.com/opsgenie/opsgenie-go-sdk-v2 v1.0.5 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/errors v0.9.1
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.5.0
github.com/prometheus/common v0.45.0 // indirect

2
go.sum
View File

@@ -843,6 +843,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.11.2 h1:/u628IuisSTwri5/UKloiIsH8+qF2Pu7xEQX+yIKg68=
github.com/dlclark/regexp2 v1.11.2/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c=
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=

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

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

View File

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

View File

@@ -21270,7 +21270,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -21388,7 +21388,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -21641,7 +21641,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -21693,7 +21693,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -21965,7 +21965,7 @@ spec:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
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.12.2
newTag: v2.12.6

View File

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

View File

@@ -22613,7 +22613,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -22736,7 +22736,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -22818,7 +22818,7 @@ spec:
key: notificationscontroller.selfservice.enabled
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -22937,7 +22937,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -23218,7 +23218,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -23270,7 +23270,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -23594,7 +23594,7 @@ spec:
key: server.api.content.types
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -23893,7 +23893,7 @@ spec:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -1688,7 +1688,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -1811,7 +1811,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -1893,7 +1893,7 @@ spec:
key: notificationscontroller.selfservice.enabled
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -2012,7 +2012,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -2293,7 +2293,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -2345,7 +2345,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -2669,7 +2669,7 @@ spec:
key: server.api.content.types
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2968,7 +2968,7 @@ spec:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -21730,7 +21730,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -21853,7 +21853,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -21935,7 +21935,7 @@ spec:
key: notificationscontroller.selfservice.enabled
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -22035,7 +22035,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -22288,7 +22288,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -22340,7 +22340,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -22662,7 +22662,7 @@ spec:
key: server.api.content.types
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -22961,7 +22961,7 @@ spec:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -805,7 +805,7 @@ spec:
key: applicationsetcontroller.enable.scm.providers
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -928,7 +928,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -1010,7 +1010,7 @@ spec:
key: notificationscontroller.selfservice.enabled
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -1110,7 +1110,7 @@ spec:
- argocd
- admin
- redis-initial-password
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: IfNotPresent
name: secret-init
securityContext:
@@ -1363,7 +1363,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -1415,7 +1415,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -1737,7 +1737,7 @@ spec:
key: server.api.content.types
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2036,7 +2036,7 @@ spec:
key: controller.ignore.normalizer.jq.timeout
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.12.2
image: quay.io/argoproj/argocd:v2.12.6
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

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

View File

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

View File

@@ -992,15 +992,15 @@ func (a *ApplicationStatus) GetRevisions() []string {
// BuildComparedToStatus will build a ComparedTo object based on the current
// Application state.
func (app *Application) BuildComparedToStatus() ComparedTo {
func (spec *ApplicationSpec) BuildComparedToStatus() ComparedTo {
ct := ComparedTo{
Destination: app.Spec.Destination,
IgnoreDifferences: app.Spec.IgnoreDifferences,
Destination: spec.Destination,
IgnoreDifferences: spec.IgnoreDifferences,
}
if app.Spec.HasMultipleSources() {
ct.Sources = app.Spec.Sources
if spec.HasMultipleSources() {
ct.Sources = spec.Sources
} else {
ct.Source = app.Spec.GetSource()
ct.Source = spec.GetSource()
}
return ct
}

View File

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

View File

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

View File

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

View File

@@ -890,18 +890,20 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
{
iconClassName: 'fa fa-info-circle',
title: <ActionMenuItem actionLabel='Details' />,
action: () => this.selectNode(fullName)
action: () => this.selectNode(fullName),
disabled: !app.spec.source && (!app.spec.sources || app.spec.sources.length === 0)
},
{
iconClassName: 'fa fa-file-medical',
title: <ActionMenuItem actionLabel='Diff' />,
action: () => this.selectNode(fullName, 0, 'diff'),
disabled: app.status.sync.status === appModels.SyncStatuses.Synced
disabled: app.status.sync.status === appModels.SyncStatuses.Synced || (!app.spec.source && (!app.spec.sources || app.spec.sources.length === 0))
},
{
iconClassName: 'fa fa-sync',
title: <ActionMenuItem actionLabel='Sync' />,
action: () => AppUtils.showDeploy('all', null, this.appContext.apis)
action: () => AppUtils.showDeploy('all', null, this.appContext.apis),
disabled: !app.spec.source && (!app.spec.sources || app.spec.sources.length === 0)
},
{
iconClassName: 'fa fa-info-circle',

View File

@@ -556,23 +556,24 @@ function gatherCoreSourceDetails(i: number, attributes: EditablePanelItem[], sou
)
});
} else {
const targetRevision = source ? source.targetRevision || 'HEAD' : 'Unknown';
attributes.push({
title: 'TARGET REVISION',
view: <Revision repoUrl={source.repoURL} revision={source.targetRevision || 'HEAD'} />,
edit: (formApi: FormApi) => <RevisionFormField helpIconTop={'0'} hideLabel={true} formApi={formApi} repoURL={source.repoURL} fieldValue={revisionField} />
view: <Revision repoUrl={source?.repoURL} revision={targetRevision} />,
edit: (formApi: FormApi) => <RevisionFormField helpIconTop={'0'} hideLabel={true} formApi={formApi} repoURL={source?.repoURL} fieldValue={revisionField} />
});
attributes.push({
title: 'PATH',
view: (
<Revision repoUrl={source.repoURL} revision={source.targetRevision || 'HEAD'} path={source.path} isForPath={true}>
{processPath(source.path)}
<Revision repoUrl={source?.repoURL} revision={targetRevision} path={source?.path} isForPath={true}>
{processPath(source?.path)}
</Revision>
),
edit: (formApi: FormApi) => <FormField formApi={formApi} field={sourcesPathField} component={Text} />
});
attributes.push({
title: 'REF',
view: <span>{source.ref}</span>,
view: <span>{source?.ref}</span>,
edit: (formApi: FormApi) => <FormField formApi={formApi} field={refField} component={Text} />
});
}

View File

@@ -112,7 +112,7 @@ export const ApplicationStatusPanel = ({application, showDiff, showOperation, sh
application.status.sync &&
(hasMultipleSources
? application.status.sync.revisions && application.status.sync.revisions[0] && application.spec.sources && !application.spec.sources[0].chart
: application.status.sync.revision && !application.spec.source.chart) && (
: application.status.sync.revision && !application.spec?.source?.chart) && (
<div className='application-status-panel__item-name'>
<RevisionMetadataPanel
appName={application.metadata.name}
@@ -160,7 +160,7 @@ export const ApplicationStatusPanel = ({application, showDiff, showOperation, sh
<RevisionMetadataPanel
appName={application.metadata.name}
appNamespace={application.metadata.namespace}
type={source.chart && 'helm'}
type={source?.chart && 'helm'}
revision={operationStateRevision}
versionId={utils.getAppCurrentVersion(application)}
/>

View File

@@ -172,7 +172,7 @@ export const ApplicationSummary = (props: ApplicationSummaryProps) => {
},
!hasMultipleSources && {
title: 'REPO URL',
view: <Repo url={source.repoURL} />,
view: <Repo url={source?.repoURL} />,
edit: (formApi: FormApi) => <FormField formApi={formApi} field='spec.source.repoURL' component={Text} />
},
...(!hasMultipleSources
@@ -180,11 +180,7 @@ export const ApplicationSummary = (props: ApplicationSummaryProps) => {
? [
{
title: 'CHART',
view: (
<span>
{source.chart}:{source.targetRevision}
</span>
),
view: <span>{source && `${source.chart}:${source.targetRevision}`}</span>,
edit: (formApi: FormApi) =>
hasMultipleSources ? (
helpTip('CHART is not editable for applications with multiple sources. You can edit them in the "Manifest" tab.')

View File

@@ -5,7 +5,7 @@ import {ApplicationSource as ApplicationSourceType} from '../../../shared/models
import './applications-source.scss';
export const ApplicationsSource = ({source}: {source: ApplicationSourceType}) => {
const sourceString = `${source.repoURL}/${source.path || source.chart}`;
const sourceString = source ? `${source.repoURL}/${source.path || source.chart}` : '';
return (
<Tooltip content={sourceString}>
<div className='application-source'>{sourceString}</div>

View File

@@ -108,6 +108,7 @@ export const ApplicationTiles = ({applications, syncApplication, refreshApplicat
<div className='applications-tiles argo-table-list argo-table-list--clickable' ref={appContainerRef}>
{applications.map((app, i) => {
const source = getAppDefaultSource(app);
const targetRevision = source ? source.targetRevision || 'HEAD' : 'Unknown';
return (
<div
key={AppUtils.appInstanceName(app)}
@@ -126,7 +127,7 @@ export const ApplicationTiles = ({applications, syncApplication, refreshApplicat
)} applications-tiles__item`}>
<div className='row '>
<div className={app.status.summary.externalURLs?.length > 0 ? 'columns small-10' : 'columns small-11'}>
<i className={'icon argo-icon-' + (source.chart != null ? 'helm' : 'git')} />
<i className={'icon argo-icon-' + (source?.chart != null ? 'helm' : 'git')} />
<Tooltip content={AppUtils.appInstanceName(app)}>
<span className='applications-list__title'>
{AppUtils.appQualifiedName(app, useAuthSettingsCtx?.appsInAnyNamespaceEnabled)}
@@ -208,8 +209,8 @@ export const ApplicationTiles = ({applications, syncApplication, refreshApplicat
Repository:
</div>
<div className='columns small-9'>
<Tooltip content={source.repoURL} zIndex={4}>
<span>{source.repoURL}</span>
<Tooltip content={source?.repoURL} zIndex={4}>
<span>{source?.repoURL}</span>
</Tooltip>
</div>
</div>
@@ -217,22 +218,22 @@ export const ApplicationTiles = ({applications, syncApplication, refreshApplicat
<div className='columns small-3' title='Target Revision:'>
Target Revision:
</div>
<div className='columns small-9'>{source.targetRevision || 'HEAD'}</div>
<div className='columns small-9'>{targetRevision}</div>
</div>
{source.path && (
{source?.path && (
<div className='row'>
<div className='columns small-3' title='Path:'>
Path:
</div>
<div className='columns small-9'>{source.path}</div>
<div className='columns small-9'>{source?.path}</div>
</div>
)}
{source.chart && (
{source?.chart && (
<div className='row'>
<div className='columns small-3' title='Chart:'>
Chart:
</div>
<div className='columns small-9'>{source.chart}</div>
<div className='columns small-9'>{source?.chart}</div>
</div>
)}
<div className='row'>

View File

@@ -706,10 +706,10 @@ export function renderResourceButtons(
export function syncStatusMessage(app: appModels.Application) {
const source = getAppDefaultSource(app);
const revision = getAppDefaultSyncRevision(app);
const rev = app.status.sync.revision || source.targetRevision || 'HEAD';
let message = source.targetRevision || 'HEAD';
const rev = app.status.sync.revision || (source ? source.targetRevision || 'HEAD' : 'Unknown');
let message = source ? source?.targetRevision || 'HEAD' : 'Unknown';
if (revision) {
if (revision && source) {
if (source.chart) {
message += ' (' + revision + ')';
} else if (revision.length >= 7 && !revision.startsWith(source.targetRevision)) {
@@ -953,23 +953,59 @@ export const OperationState = ({app, quiet}: {app: appModels.Application; quiet?
);
};
function isPodInitializedConditionTrue(status: any): boolean {
if (!status?.conditions) {
return false;
}
for (const condition of status.conditions) {
if (condition.type !== 'Initialized') {
continue;
}
return condition.status === 'True';
}
return false;
}
// isPodPhaseTerminal returns true if the pod's phase is terminal.
function isPodPhaseTerminal(phase: appModels.PodPhase): boolean {
return phase === appModels.PodPhase.PodFailed || phase === appModels.PodPhase.PodSucceeded;
}
export function getPodStateReason(pod: appModels.State): {message: string; reason: string; netContainerStatuses: any[]} {
let reason = pod.status.phase;
const podPhase = pod.status.phase;
let reason = podPhase;
let message = '';
if (pod.status.reason) {
reason = pod.status.reason;
}
let initializing = false;
let netContainerStatuses = pod.status.initContainerStatuses || [];
netContainerStatuses = netContainerStatuses.concat(pod.status.containerStatuses || []);
for (const condition of pod.status.conditions || []) {
if (condition.type === 'PodScheduled' && condition.reason === 'SchedulingGated') {
reason = 'SchedulingGated';
}
}
const initContainers: Record<string, any> = {};
for (const container of pod.spec.initContainers ?? []) {
initContainers[container.name] = container;
}
let initializing = false;
for (const container of (pod.status.initContainerStatuses || []).slice().reverse()) {
if (container.state.terminated && container.state.terminated.exitCode === 0) {
continue;
}
if (container.started && initContainers[container.name].restartPolicy === 'Always') {
continue;
}
if (container.state.terminated) {
if (container.state.terminated.reason) {
reason = `Init:ExitCode:${container.state.terminated.exitCode}`;
@@ -987,7 +1023,7 @@ export function getPodStateReason(pod: appModels.State): {message: string; reaso
break;
}
if (!initializing) {
if (!initializing || isPodInitializedConditionTrue(pod.status)) {
let hasRunning = false;
for (const container of pod.status.containerStatuses || []) {
if (container.state.waiting && container.state.waiting.reason) {
@@ -1019,7 +1055,7 @@ export function getPodStateReason(pod: appModels.State): {message: string; reaso
if ((pod as any).metadata.deletionTimestamp && pod.status.reason === 'NodeLost') {
reason = 'Unknown';
message = '';
} else if ((pod as any).metadata.deletionTimestamp) {
} else if ((pod as any).metadata.deletionTimestamp && !isPodPhaseTerminal(podPhase)) {
reason = 'Terminating';
message = '';
}
@@ -1044,7 +1080,7 @@ export const getPodReadinessGatesState = (pod: appModels.State): {nonExistingCon
for (const condition of podStatusConditions) {
existingConditions.set(condition.type, true);
// priority order of conditions
// eg. if there are multiple conditions set with same name then the one which comes first is evaluated
// e.g. if there are multiple conditions set with same name then the one which comes first is evaluated
if (podConditions.has(condition.type)) {
continue;
}
@@ -1091,10 +1127,10 @@ export function isAppNode(node: appModels.ResourceNode) {
export function getAppOverridesCount(app: appModels.Application) {
const source = getAppDefaultSource(app);
if (source.kustomize && source.kustomize.images) {
if (source?.kustomize?.images) {
return source.kustomize.images.length;
}
if (source.helm && source.helm.parameters) {
if (source?.helm?.parameters) {
return source.helm.parameters.length;
}
return 0;

View File

@@ -1680,7 +1680,7 @@
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.24"
"@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3":
"@jridgewell/resolve-uri@^3.0.3":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
@@ -1708,7 +1708,15 @@
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10":
"@jridgewell/source-map@^0.3.3":
version "0.3.6"
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a"
integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==
dependencies:
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.25"
"@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.14"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
@@ -1734,7 +1742,7 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
version "0.3.25"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0"
integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
@@ -1742,14 +1750,6 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@jridgewell/trace-mapping@^0.3.17":
version "0.3.18"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6"
integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==
dependencies:
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"
"@jridgewell/trace-mapping@^0.3.9":
version "0.3.14"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed"
@@ -2037,26 +2037,10 @@
dependencies:
deepmerge "*"
"@types/eslint-scope@^3.7.3":
version "3.7.3"
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224"
integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==
dependencies:
"@types/eslint" "*"
"@types/estree" "*"
"@types/eslint@*":
version "8.4.1"
resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.1.tgz#c48251553e8759db9e656de3efc846954ac32304"
integrity sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==
dependencies:
"@types/estree" "*"
"@types/json-schema" "*"
"@types/estree@*", "@types/estree@^1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194"
integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==
"@types/estree@^1.0.5":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18":
version "4.17.28"
@@ -2160,11 +2144,6 @@
"@types/tough-cookie" "*"
parse5 "^7.0.0"
"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
version "7.0.11"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
"@types/json-schema@^7.0.15":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
@@ -2175,6 +2154,11 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818"
integrity sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg==
"@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
version "7.0.11"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
"@types/lodash-es@^4.17.6":
version "4.17.6"
resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.6.tgz#c2ed4c8320ffa6f11b43eb89e9eaeec65966a0a0"
@@ -2483,10 +2467,10 @@
"@typescript-eslint/types" "7.8.0"
eslint-visitor-keys "^3.4.3"
"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5":
version "1.11.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24"
integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==
"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1":
version "1.12.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb"
integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==
dependencies:
"@webassemblyjs/helper-numbers" "1.11.6"
"@webassemblyjs/helper-wasm-bytecode" "1.11.6"
@@ -2501,10 +2485,10 @@
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768"
integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==
"@webassemblyjs/helper-buffer@1.11.6":
version "1.11.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093"
integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==
"@webassemblyjs/helper-buffer@1.12.1":
version "1.12.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz#6df20d272ea5439bf20ab3492b7fb70e9bfcb3f6"
integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==
"@webassemblyjs/helper-numbers@1.11.6":
version "1.11.6"
@@ -2520,15 +2504,15 @@
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9"
integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==
"@webassemblyjs/helper-wasm-section@1.11.6":
version "1.11.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577"
integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==
"@webassemblyjs/helper-wasm-section@1.12.1":
version "1.12.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz#3da623233ae1a60409b509a52ade9bc22a37f7bf"
integrity sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==
dependencies:
"@webassemblyjs/ast" "1.11.6"
"@webassemblyjs/helper-buffer" "1.11.6"
"@webassemblyjs/ast" "1.12.1"
"@webassemblyjs/helper-buffer" "1.12.1"
"@webassemblyjs/helper-wasm-bytecode" "1.11.6"
"@webassemblyjs/wasm-gen" "1.11.6"
"@webassemblyjs/wasm-gen" "1.12.1"
"@webassemblyjs/ieee754@1.11.6":
version "1.11.6"
@@ -2549,59 +2533,59 @@
resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a"
integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==
"@webassemblyjs/wasm-edit@^1.11.5":
version "1.11.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab"
integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==
"@webassemblyjs/wasm-edit@^1.12.1":
version "1.12.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b"
integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==
dependencies:
"@webassemblyjs/ast" "1.11.6"
"@webassemblyjs/helper-buffer" "1.11.6"
"@webassemblyjs/ast" "1.12.1"
"@webassemblyjs/helper-buffer" "1.12.1"
"@webassemblyjs/helper-wasm-bytecode" "1.11.6"
"@webassemblyjs/helper-wasm-section" "1.11.6"
"@webassemblyjs/wasm-gen" "1.11.6"
"@webassemblyjs/wasm-opt" "1.11.6"
"@webassemblyjs/wasm-parser" "1.11.6"
"@webassemblyjs/wast-printer" "1.11.6"
"@webassemblyjs/helper-wasm-section" "1.12.1"
"@webassemblyjs/wasm-gen" "1.12.1"
"@webassemblyjs/wasm-opt" "1.12.1"
"@webassemblyjs/wasm-parser" "1.12.1"
"@webassemblyjs/wast-printer" "1.12.1"
"@webassemblyjs/wasm-gen@1.11.6":
version "1.11.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268"
integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==
"@webassemblyjs/wasm-gen@1.12.1":
version "1.12.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz#a6520601da1b5700448273666a71ad0a45d78547"
integrity sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==
dependencies:
"@webassemblyjs/ast" "1.11.6"
"@webassemblyjs/ast" "1.12.1"
"@webassemblyjs/helper-wasm-bytecode" "1.11.6"
"@webassemblyjs/ieee754" "1.11.6"
"@webassemblyjs/leb128" "1.11.6"
"@webassemblyjs/utf8" "1.11.6"
"@webassemblyjs/wasm-opt@1.11.6":
version "1.11.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2"
integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==
"@webassemblyjs/wasm-opt@1.12.1":
version "1.12.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz#9e6e81475dfcfb62dab574ac2dda38226c232bc5"
integrity sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==
dependencies:
"@webassemblyjs/ast" "1.11.6"
"@webassemblyjs/helper-buffer" "1.11.6"
"@webassemblyjs/wasm-gen" "1.11.6"
"@webassemblyjs/wasm-parser" "1.11.6"
"@webassemblyjs/ast" "1.12.1"
"@webassemblyjs/helper-buffer" "1.12.1"
"@webassemblyjs/wasm-gen" "1.12.1"
"@webassemblyjs/wasm-parser" "1.12.1"
"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5":
version "1.11.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1"
integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==
"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1":
version "1.12.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937"
integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==
dependencies:
"@webassemblyjs/ast" "1.11.6"
"@webassemblyjs/ast" "1.12.1"
"@webassemblyjs/helper-api-error" "1.11.6"
"@webassemblyjs/helper-wasm-bytecode" "1.11.6"
"@webassemblyjs/ieee754" "1.11.6"
"@webassemblyjs/leb128" "1.11.6"
"@webassemblyjs/utf8" "1.11.6"
"@webassemblyjs/wast-printer@1.11.6":
version "1.11.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20"
integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==
"@webassemblyjs/wast-printer@1.12.1":
version "1.12.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz#bcecf661d7d1abdaf989d8341a4833e33e2b31ac"
integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==
dependencies:
"@webassemblyjs/ast" "1.11.6"
"@webassemblyjs/ast" "1.12.1"
"@xtuc/long" "4.2.2"
"@webpack-cli/configtest@^1.1.1":
@@ -2652,10 +2636,10 @@ acorn-globals@^7.0.0:
acorn "^8.1.0"
acorn-walk "^8.0.2"
acorn-import-assertions@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac"
integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==
acorn-import-attributes@^1.9.5:
version "1.9.5"
resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef"
integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==
acorn-jsx@^5.3.2:
version "5.3.2"
@@ -2672,6 +2656,11 @@ acorn@^8.1.0, acorn@^8.11.3, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==
acorn@^8.8.2:
version "8.12.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248"
integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
add@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/add/-/add-2.0.6.tgz#248f0a9f6e5a528ef2295dbeec30532130ae2235"
@@ -3169,10 +3158,10 @@ binary-extensions@^2.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
body-parser@1.20.2:
version "1.20.2"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
body-parser@1.20.3:
version "1.20.3"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6"
integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==
dependencies:
bytes "3.1.2"
content-type "~1.0.5"
@@ -3182,7 +3171,7 @@ body-parser@1.20.2:
http-errors "2.0.0"
iconv-lite "0.4.24"
on-finished "2.4.1"
qs "6.11.0"
qs "6.13.0"
raw-body "2.5.2"
type-is "~1.6.18"
unpipe "1.0.0"
@@ -3226,7 +3215,7 @@ braces@^3.0.1, braces@^3.0.2, braces@~3.0.2:
dependencies:
fill-range "^7.1.1"
browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.17.5, browserslist@^4.6.0:
browserslist@^4.16.6, browserslist@^4.17.5, browserslist@^4.6.0:
version "4.20.2"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88"
integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==
@@ -3237,6 +3226,16 @@ browserslist@^4.14.5, browserslist@^4.16.6, browserslist@^4.17.5, browserslist@^
node-releases "^2.0.2"
picocolors "^1.0.0"
browserslist@^4.21.10:
version "4.23.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800"
integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==
dependencies:
caniuse-lite "^1.0.30001646"
electron-to-chromium "^1.5.4"
node-releases "^2.0.18"
update-browserslist-db "^1.1.0"
browserslist@^4.22.2:
version "4.23.0"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab"
@@ -3369,6 +3368,11 @@ caniuse-lite@^1.0.30001587:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001620.tgz#78bb6f35b8fe315b96b8590597094145d0b146b4"
integrity sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==
caniuse-lite@^1.0.30001646:
version "1.0.30001662"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001662.tgz#3574b22dfec54a3f3b6787331da1040fe8e763ec"
integrity sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==
chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
@@ -4181,9 +4185,9 @@ domhandler@^4.0.0, domhandler@^4.2.0:
domelementtype "^2.2.0"
dompurify@^2.2.8:
version "2.3.6"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.6.tgz#2e019d7d7617aacac07cbbe3d88ae3ad354cf875"
integrity sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg==
version "2.5.6"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.5.6.tgz#8402b501611eaa7fb3786072297fcbe2787f8592"
integrity sha512-zUTaUBO8pY4+iJMPE1B9XlO2tXVYIcEA4SNGtvDELzTSCQO7RzH+j7S180BmhmJId78lqGU2z19vgVx2Sxs/PQ==
domutils@^2.5.2, domutils@^2.6.0:
version "2.7.0"
@@ -4217,6 +4221,11 @@ electron-to-chromium@^1.4.84:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.89.tgz#33c06592812a17a7131873f4596579084ce33ff8"
integrity sha512-z1Axg0Fu54fse8wN4fd+GAINdU5mJmLtcl6bqIcYyzNVGONcfHAeeJi88KYMQVKalhXlYuVPzKkFIU5VD0raUw==
electron-to-chromium@^1.5.4:
version "1.5.27"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.27.tgz#5203ce5d6054857d84ba84d3681cbe59132ade78"
integrity sha512-o37j1vZqCoEgBuWWXLHQgTN/KDKe7zwpiY5CPeq2RvUqOyJw9xnrULzZAEVQ5p4h+zjMk7hgtOoPdnLxr7m/jw==
emittery@^0.13.1:
version "0.13.1"
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad"
@@ -4242,10 +4251,15 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
enhanced-resolve@^5.14.1:
version "5.14.1"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz#de684b6803724477a4af5d74ccae5de52c25f6b3"
integrity sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==
encodeurl@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
enhanced-resolve@^5.17.1:
version "5.17.1"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15"
integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==
dependencies:
graceful-fs "^4.2.4"
tapable "^2.2.0"
@@ -4918,36 +4932,36 @@ expect@^29.0.0, expect@^29.7.0:
jest-util "^29.7.0"
express@^4.17.1:
version "4.19.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==
version "4.20.0"
resolved "https://registry.yarnpkg.com/express/-/express-4.20.0.tgz#f1d08e591fcec770c07be4767af8eb9bcfd67c48"
integrity sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"
body-parser "1.20.2"
body-parser "1.20.3"
content-disposition "0.5.4"
content-type "~1.0.4"
cookie "0.6.0"
cookie-signature "1.0.6"
debug "2.6.9"
depd "2.0.0"
encodeurl "~1.0.2"
encodeurl "~2.0.0"
escape-html "~1.0.3"
etag "~1.8.1"
finalhandler "1.2.0"
fresh "0.5.2"
http-errors "2.0.0"
merge-descriptors "1.0.1"
merge-descriptors "1.0.3"
methods "~1.1.2"
on-finished "2.4.1"
parseurl "~1.3.3"
path-to-regexp "0.1.7"
path-to-regexp "0.1.10"
proxy-addr "~2.0.7"
qs "6.11.0"
range-parser "~1.2.1"
safe-buffer "5.2.1"
send "0.18.0"
serve-static "1.15.0"
send "0.19.0"
serve-static "1.16.0"
setprototypeof "1.2.0"
statuses "2.0.1"
type-is "~1.6.18"
@@ -5402,6 +5416,11 @@ graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96"
integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==
graceful-fs@^4.2.11:
version "4.2.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
graphemer@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
@@ -6121,7 +6140,7 @@ is-wsl@^2.2.0:
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==
isarray@^2.0.5:
version "2.0.5"
@@ -6984,10 +7003,10 @@ memfs@^3.4.3:
dependencies:
fs-monkey "^1.0.4"
merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
merge-descriptors@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
merge-stream@^2.0.0:
version "2.0.0"
@@ -7262,6 +7281,11 @@ node-releases@^2.0.14:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b"
integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==
node-releases@^2.0.18:
version "2.0.18"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f"
integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==
node-releases@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01"
@@ -7649,15 +7673,15 @@ path-parse@^1.0.6, path-parse@^1.0.7:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
path-to-regexp@0.1.10:
version "0.1.10"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b"
integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==
path-to-regexp@^1.7.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
version "1.9.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.9.0.tgz#5dc0753acbf8521ca2e0f137b4578b917b10cf24"
integrity sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==
dependencies:
isarray "0.0.1"
@@ -7976,6 +8000,13 @@ qs@6.11.0, qs@^6.11.0:
dependencies:
side-channel "^1.0.4"
qs@6.13.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
dependencies:
side-channel "^1.0.6"
querystring@0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
@@ -9068,7 +9099,7 @@ schema-utils@^2.7.1:
ajv "^6.12.4"
ajv-keywords "^3.5.2"
schema-utils@^3.1.1, schema-utils@^3.1.2:
schema-utils@^3.1.1:
version "3.1.2"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.2.tgz#36c10abca6f7577aeae136c804b0c741edeadc99"
integrity sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==
@@ -9077,6 +9108,15 @@ schema-utils@^3.1.1, schema-utils@^3.1.2:
ajv "^6.12.5"
ajv-keywords "^3.5.2"
schema-utils@^3.2.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe"
integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==
dependencies:
"@types/json-schema" "^7.0.8"
ajv "^6.12.5"
ajv-keywords "^3.5.2"
schema-utils@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7"
@@ -9159,6 +9199,25 @@ send@0.18.0:
range-parser "~1.2.1"
statuses "2.0.1"
send@0.19.0:
version "0.19.0"
resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8"
integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
dependencies:
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
fresh "0.5.2"
http-errors "2.0.0"
mime "1.6.0"
ms "2.1.3"
on-finished "2.4.1"
range-parser "~1.2.1"
statuses "2.0.1"
serialize-javascript@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4"
@@ -9186,10 +9245,10 @@ serve-index@^1.9.1:
mime-types "~2.1.17"
parseurl "~1.3.2"
serve-static@1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
serve-static@1.16.0:
version "1.16.0"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.0.tgz#2bf4ed49f8af311b519c46f272bf6ac3baf38a92"
integrity sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==
dependencies:
encodeurl "~1.0.2"
escape-html "~1.0.3"
@@ -9784,16 +9843,16 @@ teeny-request@7.1.1:
stream-events "^1.0.5"
uuid "^8.0.0"
terser-webpack-plugin@^5.3.7:
version "5.3.9"
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1"
integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==
terser-webpack-plugin@^5.3.10:
version "5.3.10"
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199"
integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==
dependencies:
"@jridgewell/trace-mapping" "^0.3.17"
"@jridgewell/trace-mapping" "^0.3.20"
jest-worker "^27.4.5"
schema-utils "^3.1.1"
serialize-javascript "^6.0.1"
terser "^5.16.8"
terser "^5.26.0"
terser@^5.10.0:
version "5.14.2"
@@ -9805,13 +9864,13 @@ terser@^5.10.0:
commander "^2.20.0"
source-map-support "~0.5.20"
terser@^5.16.8:
version "5.17.6"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.6.tgz#d810e75e1bb3350c799cd90ebefe19c9412c12de"
integrity sha512-V8QHcs8YuyLkLHsJO5ucyff1ykrLVsR4dNnS//L5Y3NiSXpbK1J+WMVUs67eI0KTxs9JtHhgEQpXQVHlHI92DQ==
terser@^5.26.0:
version "5.33.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.33.0.tgz#8f9149538c7468ffcb1246cfec603c16720d2db1"
integrity sha512-JuPVaB7s1gdFKPKTelwUyRq5Sid2A3Gko2S0PncwdBq7kN9Ti9HPWDQ06MPsEDGsZeVESjKEnyGy68quBk1w6g==
dependencies:
"@jridgewell/source-map" "^0.3.2"
acorn "^8.5.0"
"@jridgewell/source-map" "^0.3.3"
acorn "^8.8.2"
commander "^2.20.0"
source-map-support "~0.5.20"
@@ -10129,6 +10188,14 @@ update-browserslist-db@^1.0.13:
escalade "^3.1.2"
picocolors "^1.0.1"
update-browserslist-db@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e"
integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==
dependencies:
escalade "^3.1.2"
picocolors "^1.0.1"
uri-js@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
@@ -10241,10 +10308,10 @@ warning@^4.0.1, warning@^4.0.2:
dependencies:
loose-envify "^1.0.0"
watchpack@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
watchpack@^2.4.1:
version "2.4.2"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da"
integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==
dependencies:
glob-to-regexp "^0.4.1"
graceful-fs "^4.1.2"
@@ -10360,34 +10427,33 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack@^5.84.1:
version "5.84.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.84.1.tgz#d4493acdeca46b26ffc99d86d784cabfeb925a15"
integrity sha512-ZP4qaZ7vVn/K8WN/p990SGATmrL1qg4heP/MrVneczYtpDGJWlrgZv55vxaV2ul885Kz+25MP2kSXkPe3LZfmg==
webpack@^5.94.0:
version "5.94.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f"
integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^1.0.0"
"@webassemblyjs/ast" "^1.11.5"
"@webassemblyjs/wasm-edit" "^1.11.5"
"@webassemblyjs/wasm-parser" "^1.11.5"
"@types/estree" "^1.0.5"
"@webassemblyjs/ast" "^1.12.1"
"@webassemblyjs/wasm-edit" "^1.12.1"
"@webassemblyjs/wasm-parser" "^1.12.1"
acorn "^8.7.1"
acorn-import-assertions "^1.9.0"
browserslist "^4.14.5"
acorn-import-attributes "^1.9.5"
browserslist "^4.21.10"
chrome-trace-event "^1.0.2"
enhanced-resolve "^5.14.1"
enhanced-resolve "^5.17.1"
es-module-lexer "^1.2.1"
eslint-scope "5.1.1"
events "^3.2.0"
glob-to-regexp "^0.4.1"
graceful-fs "^4.2.9"
graceful-fs "^4.2.11"
json-parse-even-better-errors "^2.3.1"
loader-runner "^4.2.0"
mime-types "^2.1.27"
neo-async "^2.6.2"
schema-utils "^3.1.2"
schema-utils "^3.2.0"
tapable "^2.1.1"
terser-webpack-plugin "^5.3.7"
watchpack "^2.4.0"
terser-webpack-plugin "^5.3.10"
watchpack "^2.4.1"
webpack-sources "^3.2.3"
websocket-driver@>=0.5.1:

View File

@@ -1132,7 +1132,7 @@ func GetAppEventLabels(app *argoappv1.Application, projLister applicationsv1.App
// Filter out event labels to include
inKeys := settingsManager.GetIncludeEventLabelKeys()
for k, v := range labels {
found := glob.MatchStringInList(inKeys, k, false)
found := glob.MatchStringInList(inKeys, k, glob.GLOB)
if found {
eventLabels[k] = v
}
@@ -1141,7 +1141,7 @@ func GetAppEventLabels(app *argoappv1.Application, projLister applicationsv1.App
// Remove excluded event labels
exKeys := settingsManager.GetExcludeEventLabelKeys()
for k := range eventLabels {
found := glob.MatchStringInList(exKeys, k, false)
found := glob.MatchStringInList(exKeys, k, glob.GLOB)
if found {
delete(eventLabels, k)
}

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
log "github.com/sirupsen/logrus"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
@@ -28,12 +29,15 @@ func Normalize(live, config *unstructured.Unstructured, trustedManagers []string
liveCopy := live.DeepCopy()
configCopy := config.DeepCopy()
normalized := false
results, err := newTypedResults(liveCopy, configCopy, pt)
// error might happen if the resources are not parsable and so cannot be normalized
if err != nil {
return nil, nil, fmt.Errorf("error building typed results: %w", err)
log.Debugf("error building typed results: %v", err)
return liveCopy, configCopy, nil
}
normalized := false
for _, mf := range live.GetManagedFields() {
if trustedManager(mf.Manager, trustedManagers) {
err := normalize(mf, results)

View File

@@ -141,6 +141,16 @@ func TestNormalize(t *testing.T) {
assert.Len(t, vwcConfig.Webhooks, 1)
assert.Equal(t, "", string(vwcConfig.Webhooks[0].ClientConfig.CABundle))
})
t.Run("does not fail if object fails validation schema", func(t *testing.T) {
desiredState := StrToUnstructured(testdata.DesiredDeploymentYaml)
require.NoError(t, unstructured.SetNestedField(desiredState.Object, "spec", "hello", "world"))
liveState := StrToUnstructured(testdata.LiveDeploymentWithManagedReplicaYaml)
pt := parser.Type("io.k8s.api.apps.v1.Deployment")
_, _, err := managedfields.Normalize(liveState, desiredState, []string{}, &pt)
require.NoError(t, err)
})
}
func validateNestedFloat64(t *testing.T, expected float64, obj *unstructured.Unstructured, fields ...string) {

View File

@@ -630,11 +630,7 @@ func (m *nativeGitClient) lsRemote(revision string) (string, error) {
revision = "HEAD"
}
semverSha, err := m.resolveSemverRevision(revision, refs)
if err != nil {
return "", err
}
semverSha := m.resolveSemverRevision(revision, refs)
if semverSha != "" {
return semverSha, nil
}
@@ -682,21 +678,29 @@ func (m *nativeGitClient) lsRemote(revision string) (string, error) {
// If we get here, revision string had non hexadecimal characters (indicating its a branch, tag,
// or symbolic ref) and we were unable to resolve it to a commit SHA.
return "", fmt.Errorf("Unable to resolve '%s' to a commit SHA", revision)
return "", fmt.Errorf("unable to resolve '%s' to a commit SHA", revision)
}
// resolveSemverRevision is a part of the lsRemote method workflow.
// When the user configure correctly the Git repository revision and the revision is a valid semver constraint
// only the for loop in this function will run, otherwise the lsRemote loop will try to resolve the revision.
// Some examples to illustrate the actual behavior, if:
// * The revision is "v0.1.*"/"0.1.*" or "v0.1.2"/"0.1.2" and there's a tag matching that constraint only this function loop will run;
// * The revision is "v0.1.*"/"0.1.*" or "0.1.2"/"0.1.2" and there is no tag matching that constraint this function loop and lsRemote loop will run for backward compatibility;
// * The revision is "custom-tag" only the lsRemote loop will run because that revision is an invalid semver;
// * The revision is "master-branch" only the lsRemote loop will run because that revision is an invalid semver;
func (m *nativeGitClient) resolveSemverRevision(revision string, refs []*plumbing.Reference) (string, error) {
// When the user correctly configures the Git repository revision, and that revision is a valid semver constraint, we
// use this logic path rather than the standard lsRemote revision resolution loop.
// Some examples to illustrate the actual behavior - if the revision is:
// * "v0.1.2"/"0.1.2" or "v0.1"/"0.1", then this is not a constraint, it's a pinned version - so we fall back to the standard tag matching in the lsRemote loop.
// * "v0.1.*"/"0.1.*", and there's a tag matching that constraint, then we find the latest matching version and return its commit hash.
// * "v0.1.*"/"0.1.*", and there is *no* tag matching that constraint, then we fall back to the standard tag matching in the lsRemote loop.
// * "custom-tag", only the lsRemote loop will run - because that revision is an invalid semver;
// * "master-branch", only the lsRemote loop will run because that revision is an invalid semver;
func (m *nativeGitClient) resolveSemverRevision(revision string, refs []*plumbing.Reference) string {
if _, err := semver.NewVersion(revision); err == nil {
// If the revision is a valid version, then we know it isn't a constraint; it's just a pin.
// In which case, we should use standard tag resolution mechanisms.
return ""
}
constraint, err := semver.NewConstraint(revision)
if err != nil {
return "", nil
log.Debugf("Revision '%s' is not a valid semver constraint, skipping semver resolution.", revision)
return ""
}
maxVersion := semver.New(0, 0, 0, "", "")
@@ -709,12 +713,9 @@ func (m *nativeGitClient) resolveSemverRevision(revision string, refs []*plumbin
tag := ref.Name().Short()
version, err := semver.NewVersion(tag)
if err != nil {
if errors.Is(err, semver.ErrInvalidSemVer) {
log.Debugf("Invalid semantic version: %s", tag)
continue
}
return "", fmt.Errorf("error parsing version for tag: %w", err)
log.Debugf("Error parsing version for tag: '%s': %v", tag, err)
// Skip this tag and continue to the next one
continue
}
if constraint.Check(version) {
@@ -726,10 +727,11 @@ func (m *nativeGitClient) resolveSemverRevision(revision string, refs []*plumbin
}
if maxVersionHash.IsZero() {
return "", nil
return ""
}
return maxVersionHash.String(), nil
log.Debugf("Semver constraint '%s' resolved to tag '%s', at reference '%s'", revision, maxVersion.Original(), maxVersionHash.String())
return maxVersionHash.String()
}
// CommitSHA returns current commit sha from `git rev-parse HEAD`

View File

@@ -173,6 +173,148 @@ func Test_ChangedFiles(t *testing.T) {
assert.ElementsMatch(t, []string{"README"}, changedFiles)
}
func Test_SemverTags(t *testing.T) {
tempDir := t.TempDir()
client, err := NewClientExt(fmt.Sprintf("file://%s", tempDir), tempDir, NopCreds{}, true, false, "")
require.NoError(t, err)
err = client.Init()
require.NoError(t, err)
mapTagRefs := map[string]string{}
for _, tag := range []string{
"v1.0.0-rc1",
"v1.0.0-rc2",
"v1.0.0",
"v1.0",
"v1.0.1",
"v1.1.0",
"2024-apple",
"2024-banana",
} {
err = runCmd(client.Root(), "git", "commit", "-m", tag+" commit", "--allow-empty")
require.NoError(t, err)
// Create an rc semver tag
err = runCmd(client.Root(), "git", "tag", tag)
require.NoError(t, err)
sha, err := client.LsRemote("HEAD")
require.NoError(t, err)
mapTagRefs[tag] = sha
}
for _, tc := range []struct {
name string
ref string
expected string
error bool
}{{
name: "pinned rc version",
ref: "v1.0.0-rc1",
expected: mapTagRefs["v1.0.0-rc1"],
}, {
name: "lt rc constraint",
ref: "< v1.0.0-rc3",
expected: mapTagRefs["v1.0.0-rc2"],
}, {
name: "pinned major version",
ref: "v1.0.0",
expected: mapTagRefs["v1.0.0"],
}, {
name: "pinned patch version",
ref: "v1.0.1",
expected: mapTagRefs["v1.0.1"],
}, {
name: "pinned minor version",
ref: "v1.1.0",
expected: mapTagRefs["v1.1.0"],
}, {
name: "patch wildcard constraint",
ref: "v1.0.*",
expected: mapTagRefs["v1.0.1"],
}, {
name: "patch tilde constraint",
ref: "~v1.0.0",
expected: mapTagRefs["v1.0.1"],
}, {
name: "minor wildcard constraint",
ref: "v1.*",
expected: mapTagRefs["v1.1.0"],
}, {
// The semver library allows for using both * and x as the wildcard modifier.
name: "alternative minor wildcard constraint",
ref: "v1.x",
expected: mapTagRefs["v1.1.0"],
}, {
name: "minor gte constraint",
ref: ">= v1.0.0",
expected: mapTagRefs["v1.1.0"],
}, {
name: "multiple constraints",
ref: "> v1.0.0 < v1.1.0",
expected: mapTagRefs["v1.0.1"],
}, {
// We treat non-specific semver versions as regular tags, rather than constraints.
name: "non-specific version",
ref: "v1.0",
expected: mapTagRefs["v1.0"],
}, {
// Which means a missing tag will raise an error.
name: "missing non-specific version",
ref: "v1.1",
error: true,
}, {
// This is NOT a semver constraint, so it should always resolve to itself - because specifying a tag should
// return the commit for that tag.
// semver/v3 has the unfortunate semver-ish behaviour where any tag starting with a number is considered to be
// "semver-ish", where that number is the semver major version, and the rest then gets coerced into a beta
// version string. This can cause unexpected behaviour with constraints logic.
// In this case, if the tag is being incorrectly coerced into semver (for being semver-ish), it will incorrectly
// return the commit for the 2024-banana tag; which we want to avoid.
name: "apple non-semver tag",
ref: "2024-apple",
expected: mapTagRefs["2024-apple"],
}, {
name: "banana non-semver tag",
ref: "2024-banana",
expected: mapTagRefs["2024-banana"],
}, {
// A semver version (without constraints) should ONLY match itself.
// We do not want "2024-apple" to get "semver-ish'ed" into matching "2024.0.0-apple"; they're different tags.
name: "no semver tag coercion",
ref: "2024.0.0-apple",
error: true,
}, {
// No minor versions are specified, so we would expect a major version of 2025 or more.
// This is because if we specify > 11 in semver, we would not expect 11.1.0 to pass; it should be 12.0.0 or more.
// Similarly, if we were to specify > 11.0, we would expect 11.1.0 or more.
name: "semver constraints on non-semver tags",
ref: "> 2024-apple",
error: true,
}, {
// However, if one specifies the minor/patch versions, semver constraints can be used to match non-semver tags.
// 2024-banana is considered as "2024.0.0-banana" in semver-ish, and banana > apple, so it's a match.
// Note: this is more for documentation and future reference than real testing, as it seems like quite odd behaviour.
name: "semver constraints on non-semver tags",
ref: "> 2024.0.0-apple",
expected: mapTagRefs["2024-banana"],
}} {
t.Run(tc.name, func(t *testing.T) {
commitSHA, err := client.LsRemote(tc.ref)
if tc.error {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.True(t, IsCommitSHA(commitSHA))
assert.Equal(t, tc.expected, commitSHA)
})
}
}
func Test_nativeGitClient_Submodule(t *testing.T) {
tempDir, err := os.MkdirTemp("", "")
require.NoError(t, err)

View File

@@ -232,15 +232,10 @@ func TestLsRemote(t *testing.T) {
expectedCommit: "ff87d8cb9e669d3738434733ecba3c6dd2c64d70",
},
{
name: "should resolve a pined tag with semantic versioning",
name: "should resolve a pinned tag with semantic versioning",
revision: "v0.8.0",
expectedCommit: "d7c04ae24c16f8ec611b0331596fbc595537abe9",
},
{
name: "should resolve a pined tag with semantic versioning without the 'v' prefix",
revision: "0.8.0",
expectedCommit: "d7c04ae24c16f8ec611b0331596fbc595537abe9",
},
{
name: "should resolve a range tag with semantic versioning",
revision: "v0.8.*", // it should resolve to v0.8.2
@@ -298,7 +293,7 @@ func TestLsRemote(t *testing.T) {
for _, revision := range xfail {
_, err := clnt.LsRemote(revision)
assert.ErrorContains(t, err, "Unable to resolve")
assert.ErrorContains(t, err, "unable to resolve")
}
})
}

View File

@@ -31,26 +31,28 @@ func Test_Match(t *testing.T) {
func Test_MatchList(t *testing.T) {
tests := []struct {
name string
input string
list []string
exact bool
result bool
name string
input string
list []string
patternMatch string
result bool
}{
{"Exact name in list", "test", []string{"test"}, true, true},
{"Exact name not in list", "test", []string{"other"}, true, false},
{"Exact name not in list, multiple elements", "test", []string{"some", "other"}, true, false},
{"Exact name not in list, list empty", "test", []string{}, true, false},
{"Exact name not in list, empty element", "test", []string{""}, true, false},
{"Glob name in list, but exact wanted", "test", []string{"*"}, true, false},
{"Glob name in list with simple wildcard", "test", []string{"*"}, false, true},
{"Glob name in list without wildcard", "test", []string{"test"}, false, true},
{"Glob name in list, multiple elements", "test", []string{"other*", "te*"}, false, true},
{"Exact name in list", "test", []string{"test"}, EXACT, true},
{"Exact name not in list", "test", []string{"other"}, EXACT, false},
{"Exact name not in list, multiple elements", "test", []string{"some", "other"}, EXACT, false},
{"Exact name not in list, list empty", "test", []string{}, EXACT, false},
{"Exact name not in list, empty element", "test", []string{""}, EXACT, false},
{"Glob name in list, but exact wanted", "test", []string{"*"}, EXACT, false},
{"Glob name in list with simple wildcard", "test", []string{"*"}, GLOB, true},
{"Glob name in list without wildcard", "test", []string{"test"}, GLOB, true},
{"Glob name in list, multiple elements", "test", []string{"other*", "te*"}, GLOB, true},
{"match everything but specified word: fail", "disallowed", []string{"/^((?!disallowed).)*$/"}, REGEXP, false},
{"match everything but specified word: pass", "allowed", []string{"/^((?!disallowed).)*$/"}, REGEXP, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
res := MatchStringInList(tt.list, tt.input, tt.exact)
res := MatchStringInList(tt.list, tt.input, tt.patternMatch)
assert.Equal(t, tt.result, res)
})
}

View File

@@ -1,10 +1,30 @@
package glob
// MatchStringInList will return true if item is contained in list. If
// exactMatch is set to false, list may contain globs to be matched.
func MatchStringInList(list []string, item string, exactMatch bool) bool {
import (
"strings"
"github.com/argoproj/argo-cd/v2/util/regex"
)
const (
EXACT = "exact"
GLOB = "glob"
REGEXP = "regexp"
)
// MatchStringInList will return true if item is contained in list.
// patternMatch; can be set to exact, glob, regexp.
// If patternMatch; is set to exact, the item must be an exact match.
// If patternMatch; is set to glob, the item must match a glob pattern.
// If patternMatch; is set to regexp, the item must match a regular expression or glob.
func MatchStringInList(list []string, item string, patternMatch string) bool {
for _, ll := range list {
if item == ll || (!exactMatch && Match(ll, item)) {
// If string is wrapped in "/", assume it is a regular expression.
if patternMatch == REGEXP && strings.HasPrefix(ll, "/") && strings.HasSuffix(ll, "/") && regex.Match(ll[1:len(ll)-1], item) {
return true
} else if (patternMatch == REGEXP || patternMatch == GLOB) && Match(ll, item) {
return true
} else if patternMatch == EXACT && item == ll {
return true
}
}

20
util/regex/regex.go Normal file
View File

@@ -0,0 +1,20 @@
package regex
import (
"github.com/dlclark/regexp2"
log "github.com/sirupsen/logrus"
)
func Match(pattern, text string) bool {
compiledRegex, err := regexp2.Compile(pattern, 0)
if err != nil {
log.Warnf("failed to compile pattern %s due to error %v", pattern, err)
return false
}
regexMatch, err := compiledRegex.MatchString(text)
if err != nil {
log.Warnf("failed to match pattern %s due to error %v", pattern, err)
return false
}
return regexMatch
}

View File

@@ -7,7 +7,7 @@ import (
)
func IsNamespaceEnabled(namespace string, serverNamespace string, enabledNamespaces []string) bool {
return namespace == serverNamespace || glob.MatchStringInList(enabledNamespaces, namespace, false)
return namespace == serverNamespace || glob.MatchStringInList(enabledNamespaces, namespace, glob.REGEXP)
}
func NamespaceNotPermittedError(namespace string) error {

View File

@@ -49,6 +49,20 @@ func Test_IsNamespaceEnabled(t *testing.T) {
[]string{"allowed"},
false,
},
{
"match everything but specified word: fail",
"disallowed",
"argocd",
[]string{"/^((?!disallowed).)*$/"},
false,
},
{
"match everything but specified word: pass",
"allowed",
"argocd",
[]string{"/^((?!disallowed).)*$/"},
true,
},
}
for _, tc := range testCases {

View File

@@ -278,7 +278,7 @@ func (a *ArgoCDWebhookHandler) HandleEvent(payload interface{}) {
// nor in the list of enabled namespaces.
var filteredApps []v1alpha1.Application
for _, app := range apps.Items {
if app.Namespace == a.ns || glob.MatchStringInList(a.appNs, app.Namespace, false) {
if app.Namespace == a.ns || glob.MatchStringInList(a.appNs, app.Namespace, glob.REGEXP) {
filteredApps = append(filteredApps, app)
}
}