mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-03-16 05:18:47 +01:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fd6344537 | ||
|
|
0977f61554 | ||
|
|
3dd069b049 | ||
|
|
37da5e2ae5 | ||
|
|
12886657ac |
@@ -220,7 +220,7 @@ func NewCommand() *cobra.Command {
|
||||
command.Flags().StringVar(&shardingAlgorithm, "sharding-method", env.StringFromEnv(common.EnvControllerShardingAlgorithm, common.DefaultShardingAlgorithm), "Enables choice of sharding method. Supported sharding methods are : [legacy, round-robin] ")
|
||||
// global queue rate limit config
|
||||
command.Flags().Int64Var(&workqueueRateLimit.BucketSize, "wq-bucket-size", env.ParseInt64FromEnv("WORKQUEUE_BUCKET_SIZE", 500, 1, math.MaxInt64), "Set Workqueue Rate Limiter Bucket Size, default 500")
|
||||
command.Flags().Int64Var(&workqueueRateLimit.BucketQPS, "wq-bucket-qps", env.ParseInt64FromEnv("WORKQUEUE_BUCKET_QPS", 50, 1, math.MaxInt64), "Set Workqueue Rate Limiter Bucket QPS, default 50")
|
||||
command.Flags().Float64Var(&workqueueRateLimit.BucketQPS, "wq-bucket-qps", env.ParseFloat64FromEnv("WORKQUEUE_BUCKET_QPS", math.MaxFloat64, 1, math.MaxFloat64), "Set Workqueue Rate Limiter Bucket QPS, default set to MaxFloat64 which disables the bucket limiter")
|
||||
// individual item rate limit config
|
||||
// when WORKQUEUE_FAILURE_COOLDOWN is 0 per item rate limiting is disabled(default)
|
||||
command.Flags().DurationVar(&workqueueRateLimit.FailureCoolDown, "wq-cooldown-ns", time.Duration(env.ParseInt64FromEnv("WORKQUEUE_FAILURE_COOLDOWN_NS", 0, 0, (24*time.Hour).Nanoseconds())), "Set Workqueue Per Item Rate Limiter Cooldown duration in ns, default 0(per item rate limiter disabled)")
|
||||
|
||||
@@ -267,13 +267,13 @@ The final rate limiter uses a combination of both and calculates the final backo
|
||||
|
||||
### Global rate limits
|
||||
|
||||
This is enabled by default, it is a simple bucket based rate limiter that limits the number of items that can be queued per second.
|
||||
This is disabled by default, it is a simple bucket based rate limiter that limits the number of items that can be queued per second.
|
||||
This is useful to prevent a large number of apps from being queued at the same time.
|
||||
|
||||
To configure the bucket limiter you can set the following environment variables:
|
||||
|
||||
* `WORKQUEUE_BUCKET_SIZE` - The number of items that can be queued in a single burst. Defaults to 500.
|
||||
* `WORKQUEUE_BUCKET_QPS` - The number of items that can be queued per second. Defaults to 50.
|
||||
* `WORKQUEUE_BUCKET_QPS` - The number of items that can be queued per second. Defaults to MaxFloat64, which disables the limiter.
|
||||
|
||||
### Per item rate limits
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ argocd-application-controller [flags]
|
||||
--username string Username for basic authentication to the API server
|
||||
--wq-backoff-factor float Set Workqueue Per Item Rate Limiter Backoff Factor, default is 1.5 (default 1.5)
|
||||
--wq-basedelay-ns duration Set Workqueue Per Item Rate Limiter Base Delay duration in nanoseconds, default 1000000 (1ms) (default 1ms)
|
||||
--wq-bucket-qps int Set Workqueue Rate Limiter Bucket QPS, default 50 (default 50)
|
||||
--wq-bucket-qps float Set Workqueue Rate Limiter Bucket QPS, default set to MaxFloat64 which disables the bucket limiter (default 1.7976931348623157e+308)
|
||||
--wq-bucket-size int Set Workqueue Rate Limiter Bucket Size, default 500 (default 500)
|
||||
--wq-cooldown-ns duration Set Workqueue Per Item Rate Limiter Cooldown duration in ns, default 0(per item rate limiter disabled)
|
||||
--wq-maxdelay-ns duration Set Workqueue Per Item Rate Limiter Max Delay duration in nanoseconds, default 1000000000 (1s) (default 1s)
|
||||
|
||||
@@ -5,7 +5,7 @@ kind: Kustomization
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.10.2
|
||||
newTag: v2.10.3
|
||||
resources:
|
||||
- ./application-controller
|
||||
- ./dex
|
||||
|
||||
@@ -21026,7 +21026,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -21350,7 +21350,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -21402,7 +21402,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -21663,7 +21663,7 @@ spec:
|
||||
key: controller.diff.server.side
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -12,4 +12,4 @@ resources:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.10.2
|
||||
newTag: v2.10.3
|
||||
|
||||
@@ -12,7 +12,7 @@ patches:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.10.2
|
||||
newTag: v2.10.3
|
||||
resources:
|
||||
- ../../base/application-controller
|
||||
- ../../base/applicationset-controller
|
||||
|
||||
@@ -22389,7 +22389,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -22512,7 +22512,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -22594,7 +22594,7 @@ spec:
|
||||
key: notificationscontroller.selfservice.enabled
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -22949,7 +22949,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -23001,7 +23001,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -23320,7 +23320,7 @@ spec:
|
||||
key: server.api.content.types
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -23608,7 +23608,7 @@ spec:
|
||||
key: controller.diff.server.side
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -1668,7 +1668,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -1791,7 +1791,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -1873,7 +1873,7 @@ spec:
|
||||
key: notificationscontroller.selfservice.enabled
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -2228,7 +2228,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -2280,7 +2280,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -2599,7 +2599,7 @@ spec:
|
||||
key: server.api.content.types
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -2887,7 +2887,7 @@ spec:
|
||||
key: controller.diff.server.side
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -21484,7 +21484,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -21607,7 +21607,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -21689,7 +21689,7 @@ spec:
|
||||
key: notificationscontroller.selfservice.enabled
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -21995,7 +21995,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -22047,7 +22047,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -22364,7 +22364,7 @@ spec:
|
||||
key: server.api.content.types
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -22652,7 +22652,7 @@ spec:
|
||||
key: controller.diff.server.side
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -763,7 +763,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -886,7 +886,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -968,7 +968,7 @@ spec:
|
||||
key: notificationscontroller.selfservice.enabled
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -1274,7 +1274,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -1326,7 +1326,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -1643,7 +1643,7 @@ spec:
|
||||
key: server.api.content.types
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -1931,7 +1931,7 @@ spec:
|
||||
key: controller.diff.server.side
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.10.2
|
||||
image: quay.io/argoproj/argocd:v2.10.3
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
type AppControllerRateLimiterConfig struct {
|
||||
BucketSize int64
|
||||
BucketQPS int64
|
||||
BucketQPS float64
|
||||
FailureCoolDown time.Duration
|
||||
BaseDelay time.Duration
|
||||
MaxDelay time.Duration
|
||||
@@ -22,7 +22,8 @@ func GetDefaultAppRateLimiterConfig() *AppControllerRateLimiterConfig {
|
||||
return &AppControllerRateLimiterConfig{
|
||||
// global queue rate limit config
|
||||
500,
|
||||
50,
|
||||
// when WORKQUEUE_BUCKET_QPS is MaxFloat64 global bucket limiting is disabled(default)
|
||||
math.MaxFloat64,
|
||||
// individual item rate limit config
|
||||
// when WORKQUEUE_FAILURE_COOLDOWN is 0 per item rate limiting is disabled(default)
|
||||
0,
|
||||
|
||||
@@ -333,6 +333,15 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
|
||||
return nil, security.NamespaceNotPermittedError(appNs)
|
||||
}
|
||||
|
||||
// Don't let the app creator set the operation explicitly. Those requests should always go through the Sync API.
|
||||
if a.Operation != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"application": a.Name,
|
||||
argocommon.SecurityField: argocommon.SecurityLow,
|
||||
}).Warn("User attempted to set operation on application creation. This could have allowed them to bypass branch protection rules by setting manifests directly. Ignoring the set operation.")
|
||||
a.Operation = nil
|
||||
}
|
||||
|
||||
created, err := s.appclientset.ArgoprojV1alpha1().Applications(appNs).Create(ctx, a, metav1.CreateOptions{})
|
||||
if err == nil {
|
||||
s.logAppEvent(created, ctx, argo.EventReasonResourceCreated, "created application")
|
||||
|
||||
@@ -1439,6 +1439,27 @@ func TestCreateAppWithDestName(t *testing.T) {
|
||||
assert.Equal(t, app.Spec.Destination.Server, "https://cluster-api.example.com")
|
||||
}
|
||||
|
||||
// TestCreateAppWithOperation tests that an application created with an operation is created with the operation removed.
|
||||
// Avoids regressions of https://github.com/argoproj/argo-cd/security/advisories/GHSA-g623-jcgg-mhmm
|
||||
func TestCreateAppWithOperation(t *testing.T) {
|
||||
appServer := newTestAppServer(t)
|
||||
testApp := newTestAppWithDestName()
|
||||
testApp.Operation = &appsv1.Operation{
|
||||
Sync: &appsv1.SyncOperation{
|
||||
Manifests: []string{
|
||||
"test",
|
||||
},
|
||||
},
|
||||
}
|
||||
createReq := application.ApplicationCreateRequest{
|
||||
Application: testApp,
|
||||
}
|
||||
app, err := appServer.Create(context.Background(), &createReq)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, app)
|
||||
assert.Nil(t, app.Operation)
|
||||
}
|
||||
|
||||
func TestUpdateApp(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
|
||||
@@ -286,7 +286,7 @@ export const ApplicationParameters = (props: {
|
||||
} else if (props.details.type === 'Plugin') {
|
||||
attributes.push({
|
||||
title: 'NAME',
|
||||
view: <div style={{marginTop: 15, marginBottom: 5}}>{ValueEditor(app.spec.source.plugin && app.spec.source.plugin.name, null)}</div>,
|
||||
view: <div style={{marginTop: 15, marginBottom: 5}}>{ValueEditor(app.spec.source?.plugin?.name, null)}</div>,
|
||||
edit: (formApi: FormApi) => (
|
||||
<DataLoader load={() => services.authService.plugins()}>
|
||||
{(plugins: Plugin[]) => (
|
||||
@@ -299,12 +299,11 @@ export const ApplicationParameters = (props: {
|
||||
title: 'ENV',
|
||||
view: (
|
||||
<div style={{marginTop: 15}}>
|
||||
{app.spec.source.plugin &&
|
||||
(app.spec.source.plugin.env || []).map(val => (
|
||||
<span key={val.name} style={{display: 'block', marginBottom: 5}}>
|
||||
{NameValueEditor(val, null)}
|
||||
</span>
|
||||
))}
|
||||
{(app.spec.source?.plugin?.env || []).map(val => (
|
||||
<span key={val.name} style={{display: 'block', marginBottom: 5}}>
|
||||
{NameValueEditor(val, null)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
edit: (formApi: FormApi) => <FormField field='spec.source.plugin.env' formApi={formApi} component={ArrayInputField} />
|
||||
@@ -315,7 +314,7 @@ export const ApplicationParameters = (props: {
|
||||
parametersSet.add(announcement.name);
|
||||
}
|
||||
}
|
||||
if (app.spec.source.plugin?.parameters) {
|
||||
if (app.spec.source?.plugin?.parameters) {
|
||||
for (const appParameter of app.spec.source.plugin.parameters) {
|
||||
parametersSet.add(appParameter.name);
|
||||
}
|
||||
@@ -326,7 +325,7 @@ export const ApplicationParameters = (props: {
|
||||
}
|
||||
parametersSet.forEach(name => {
|
||||
const announcement = props.details.plugin.parametersAnnouncement?.find(param => param.name === name);
|
||||
const liveParam = app.spec.source.plugin?.parameters?.find(param => param.name === name);
|
||||
const liveParam = app.spec.source?.plugin?.parameters?.find(param => param.name === name);
|
||||
const pluginIcon =
|
||||
announcement && liveParam ? 'This parameter has been provided by plugin, but is overridden in application manifest.' : 'This parameter is provided by the plugin.';
|
||||
const isPluginPar = !!announcement;
|
||||
|
||||
@@ -30,6 +30,7 @@ import {EditAnnotations} from './edit-annotations';
|
||||
|
||||
import './application-summary.scss';
|
||||
import {DeepLinks} from '../../../shared/components/deep-links';
|
||||
import {ExternalLinks} from '../application-urls';
|
||||
|
||||
function swap(array: any[], a: number, b: number) {
|
||||
array = array.slice();
|
||||
@@ -326,20 +327,19 @@ export const ApplicationSummary = (props: ApplicationSummaryProps) => {
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
const urls = app.status.summary.externalURLs || [];
|
||||
const urls = ExternalLinks(app.status.summary.externalURLs);
|
||||
if (urls.length > 0) {
|
||||
attributes.push({
|
||||
title: 'URLs',
|
||||
view: (
|
||||
<React.Fragment>
|
||||
{urls
|
||||
.map(item => item.split('|'))
|
||||
.map((parts, i) => (
|
||||
<a key={i} href={parts.length > 1 ? parts[1] : parts[0]} target='__blank'>
|
||||
{parts[0]}
|
||||
{urls.map((url, i) => {
|
||||
return (
|
||||
<a key={i} href={url.ref} target='__blank'>
|
||||
{url.title}
|
||||
</a>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
)
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ExternalLink, InvalidExternalLinkError} from './application-urls';
|
||||
import { ExternalLink, ExternalLinks, InvalidExternalLinkError } from './application-urls';
|
||||
|
||||
test('rejects malicious URLs', () => {
|
||||
expect(() => {
|
||||
@@ -7,6 +7,16 @@ test('rejects malicious URLs', () => {
|
||||
expect(() => {
|
||||
const _ = new ExternalLink('data:text/html;<h1>hi</h1>');
|
||||
}).toThrowError(InvalidExternalLinkError);
|
||||
expect(() => {
|
||||
const _ = new ExternalLink('title|data:text/html;<h1>hi</h1>');
|
||||
}).toThrowError(InvalidExternalLinkError);
|
||||
expect(() => {
|
||||
const _ = new ExternalLink('data:title|data:text/html;<h1>hi</h1>');
|
||||
}).toThrowError(InvalidExternalLinkError);
|
||||
|
||||
expect(() => {
|
||||
const _ = new ExternalLink('data:title|https://localhost:8080/applications');
|
||||
}).not.toThrowError(InvalidExternalLinkError);
|
||||
});
|
||||
|
||||
test('allows absolute URLs', () => {
|
||||
@@ -18,3 +28,59 @@ test('allows relative URLs', () => {
|
||||
window.location = new URL('https://localhost:8080/applications');
|
||||
expect(new ExternalLink('/applications').ref).toEqual('/applications');
|
||||
});
|
||||
|
||||
|
||||
test('URLs format', () => {
|
||||
expect(new ExternalLink('https://localhost:8080/applications')).toEqual({
|
||||
ref: 'https://localhost:8080/applications',
|
||||
title: 'https://localhost:8080/applications',
|
||||
})
|
||||
expect(new ExternalLink('title|https://localhost:8080/applications')).toEqual({
|
||||
ref: 'https://localhost:8080/applications',
|
||||
title: 'title',
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
test('malicious URLs from list to be removed', () => {
|
||||
const urls: string[] = [
|
||||
'javascript:alert("hi")',
|
||||
'https://localhost:8080/applications',
|
||||
]
|
||||
const links = ExternalLinks(urls);
|
||||
|
||||
expect(links).toHaveLength(1);
|
||||
expect(links).toContainEqual({
|
||||
ref: 'https://localhost:8080/applications',
|
||||
title: 'https://localhost:8080/applications',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test('list to be sorted', () => {
|
||||
const urls: string[] = [
|
||||
'https://a',
|
||||
'https://b',
|
||||
'a|https://c',
|
||||
'z|https://c',
|
||||
'x|https://d',
|
||||
'x|https://c',
|
||||
]
|
||||
const links = ExternalLinks(urls);
|
||||
|
||||
// 'a|https://c',
|
||||
// 'x|https://c',
|
||||
// 'x|https://d',
|
||||
// 'z|https://c',
|
||||
// 'https://a',
|
||||
// 'https://b',
|
||||
expect(links).toHaveLength(6);
|
||||
expect(links[0].title).toEqual('a')
|
||||
expect(links[1].title).toEqual('x')
|
||||
expect(links[1].ref).toEqual('https://c')
|
||||
expect(links[2].title).toEqual('x')
|
||||
expect(links[2].ref).toEqual('https://d')
|
||||
expect(links[3].title).toEqual('z')
|
||||
expect(links[4].title).toEqual('https://a')
|
||||
expect(links[5].title).toEqual('https://b')
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ export class ExternalLink {
|
||||
}
|
||||
}
|
||||
|
||||
export const ApplicationURLs = ({urls}: {urls: string[]}) => {
|
||||
export const ExternalLinks = (urls?: string[]) => {
|
||||
const externalLinks: ExternalLink[] = [];
|
||||
for (const url of urls || []) {
|
||||
try {
|
||||
@@ -42,16 +42,26 @@ export const ApplicationURLs = ({urls}: {urls: string[]}) => {
|
||||
|
||||
// sorted alphabetically & links with titles first
|
||||
externalLinks.sort((a, b) => {
|
||||
if (a.title !== '' && b.title !== '') {
|
||||
const hasTitle = (x: ExternalLink): boolean => {
|
||||
return x.title !== x.ref && x.title !== '';
|
||||
};
|
||||
|
||||
if (hasTitle(a) && hasTitle(b) && a.title !== b.title) {
|
||||
return a.title > b.title ? 1 : -1;
|
||||
} else if (a.title === '') {
|
||||
} else if (hasTitle(b) && !hasTitle(a)) {
|
||||
return 1;
|
||||
} else if (b.title === '') {
|
||||
} else if (hasTitle(a) && !hasTitle(b)) {
|
||||
return -1;
|
||||
}
|
||||
return a.ref > b.ref ? 1 : -1;
|
||||
});
|
||||
|
||||
return externalLinks;
|
||||
};
|
||||
|
||||
export const ApplicationURLs = ({urls}: {urls: string[]}) => {
|
||||
const externalLinks: ExternalLink[] = ExternalLinks(urls);
|
||||
|
||||
return (
|
||||
((externalLinks || []).length > 0 && (
|
||||
<div className='applications-list__external-links-icon-container'>
|
||||
|
||||
Reference in New Issue
Block a user