Compare commits

...

19 Commits

Author SHA1 Message Date
argo-bot
f8cbd6bf43 Bump version to 1.7.4 2020-09-05 02:35:26 +00:00
argo-bot
8afbccd8f6 Bump version to 1.7.4 2020-09-05 02:35:17 +00:00
Alexander Matyushentsev
b06536de42 fix: automatically stop watch API requests when page is hidden (#4269) 2020-09-04 14:37:06 -07:00
Alexander Matyushentsev
28e82bccea fix: upgrade gitops-engine dependency (issues #4242, #1881) (#4268) 2020-09-04 14:20:53 -07:00
Alexander Matyushentsev
b815759112 fix: application stream API should not return 'ADDED' events if resource version is provided (#4260) 2020-09-04 14:20:14 -07:00
Mikhail Mazurskiy
37ef7f43e8 fix: return parsing error (#3942)
Don't assume that a file is not a Kubernetes
resource if there was no previous objects parsed
2020-09-04 14:20:03 -07:00
Alexander Matyushentsev
0c511ca6b7 fix: JS error when using cluster filter in the /application view (#4247) 2020-09-04 14:20:00 -07:00
Alexander Matyushentsev
79849a1388 fix: improve applications list page client side performance (#4244) 2020-09-02 16:02:10 -07:00
argo-bot
b4c79ccb88 Bump version to 1.7.3 2020-09-01 23:07:14 +00:00
argo-bot
3d91e911cf Bump version to 1.7.3 2020-09-01 23:07:04 +00:00
Alexander Matyushentsev
4f92c28eea fix: application details page crash when app is deleted (#4229) 2020-09-01 15:26:48 -07:00
Alexander Matyushentsev
d08dba171e fix: api-server unnecessary normalize projects on every start (#4219) 2020-09-01 13:09:05 -07:00
Alexander Matyushentsev
fe9d71d47a refactor: load only project names in UI (#4217) 2020-09-01 13:08:58 -07:00
jannfis
79ffa9fb9f fix: Re-create already initialized ARGOCD_GNUPGHOME on startup (#4214) (#4223) 2020-09-01 13:08:45 -07:00
Alexander Matyushentsev
918a19d69c feat: support gzip compression in api server (#4218) 2020-09-01 10:50:08 -07:00
chrisob
ed77b994e3 fix: Add openshift as a dex connector type which requires a redirectURI (#4222) 2020-09-01 10:33:29 -07:00
Alexander Matyushentsev
fba91aec51 refactor: Replace status.observedAt with redis pub/sub channels for resource tree updates (#1340) (#4208) 2020-08-31 14:01:10 -07:00
Alexander Matyushentsev
8a7fa9d665 fix: cache inconsistency of child resources (#4053) (#4202) 2020-08-31 14:01:05 -07:00
Oleg Sucharevich
26fda7ce52 feat: do not include kube-api check in application liveness flow (#4163)
* feat: do not include kube-api liveness check in application liveness flow
2020-08-31 14:01:01 -07:00
45 changed files with 760 additions and 293 deletions

View File

@@ -1 +1 @@
1.7.2
1.7.4

View File

@@ -2227,6 +2227,56 @@
}
}
},
"/api/v1/stream/applications/{applicationName}/resource-tree": {
"get": {
"tags": [
"ApplicationService"
],
"summary": "Watch returns stream of application resource tree",
"operationId": "WatchResourceTree",
"parameters": [
{
"type": "string",
"name": "applicationName",
"in": "path",
"required": true
},
{
"type": "string",
"name": "namespace",
"in": "query"
},
{
"type": "string",
"name": "name",
"in": "query"
},
{
"type": "string",
"name": "version",
"in": "query"
},
{
"type": "string",
"name": "group",
"in": "query"
},
{
"type": "string",
"name": "kind",
"in": "query"
}
],
"responses": {
"200": {
"description": "A successful response.(streaming responses)",
"schema": {
"$ref": "#/x-stream-definitions/v1alpha1ApplicationTree"
}
}
}
}
},
"/api/version": {
"get": {
"tags": [

View File

@@ -58,6 +58,7 @@ func NewCommand() *cobra.Command {
repoServerAddress string
dexServerAddress string
disableAuth bool
enableGZip bool
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
cacheSrc func() (*servercache.Cache, error)
frameOptions string
@@ -115,6 +116,7 @@ func NewCommand() *cobra.Command {
RepoClientset: repoclientset,
DexServerAddr: dexServerAddress,
DisableAuth: disableAuth,
EnableGZip: enableGZip,
TLSConfigCustomizer: tlsConfigCustomizer,
Cache: cache,
XFrameOptions: frameOptions,
@@ -146,6 +148,7 @@ func NewCommand() *cobra.Command {
command.Flags().StringVar(&repoServerAddress, "repo-server", common.DefaultRepoServerAddr, "Repo server address")
command.Flags().StringVar(&dexServerAddress, "dex-server", common.DefaultDexServerAddr, "Dex server address")
command.Flags().BoolVar(&disableAuth, "disable-auth", false, "Disable client authentication")
command.Flags().BoolVar(&enableGZip, "enable-gzip", false, "Enable GZIP compression")
command.AddCommand(cli.NewVersionCmd(cliName))
command.Flags().IntVar(&listenPort, "port", common.DefaultPortAPIServer, "Listen on given port")
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortArgoCDAPIServerMetrics, "Start metrics on given port")

View File

@@ -177,8 +177,7 @@ func NewApplicationController(
})
metricsAddr := fmt.Sprintf("0.0.0.0:%d", metricsPort)
ctrl.metricsServer = metrics.NewMetricsServer(metricsAddr, appLister, func() error {
_, err := kubeClientset.Discovery().ServerVersion()
return err
return nil
})
stateCache := statecache.NewLiveStateCache(db, appInformer, ctrl.settingsMgr, kubectl, ctrl.metricsServer, ctrl.handleObjectUpdated)
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectl, ctrl.settingsMgr, stateCache, projInformer, ctrl.metricsServer)
@@ -1031,8 +1030,6 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
appv1.ApplicationConditionComparisonError: true,
})
}
now := metav1.Now()
app.Status.ObservedAt = &now
ctrl.persistAppStatus(origApp, &app.Status)
return
}
@@ -1056,7 +1053,7 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
revision = app.Status.Sync.Revision
}
observedAt := metav1.Now()
now := metav1.Now()
compareResult := ctrl.appStateManager.CompareAppState(app, project, revision, app.Spec.Source, refreshType == appv1.RefreshTypeHard, localManifests)
for k, v := range compareResult.timings {
logCtx = logCtx.WithField(k, v.Milliseconds())
@@ -1089,9 +1086,8 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
}
if app.Status.ReconciledAt == nil || comparisonLevel == CompareWithLatest {
app.Status.ReconciledAt = &observedAt
app.Status.ReconciledAt = &now
}
app.Status.ObservedAt = &observedAt
app.Status.Sync = *compareResult.syncStatus
app.Status.Health = *compareResult.healthStatus
app.Status.Resources = compareResult.resources

View File

@@ -962,7 +962,7 @@ func TestUpdateReconciledAt(t *testing.T) {
_, updated, err = unstructured.NestedString(receivedPatch, "status", "observedAt")
assert.NoError(t, err)
assert.True(t, updated)
assert.False(t, updated)
})
t.Run("NotUpdatedOnPartialReconciliation", func(t *testing.T) {
@@ -978,7 +978,7 @@ func TestUpdateReconciledAt(t *testing.T) {
_, updated, err = unstructured.NestedString(receivedPatch, "status", "observedAt")
assert.NoError(t, err)
assert.True(t, updated)
assert.False(t, updated)
})
}

3
go.mod
View File

@@ -8,7 +8,7 @@ require (
github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 // indirect
github.com/alicebob/miniredis v2.5.0+incompatible
github.com/argoproj/gitops-engine v0.1.3-0.20200826062957-2cf3a72c659c
github.com/argoproj/gitops-engine v0.1.3-0.20200904164417-c04f859da9b2
github.com/argoproj/pkg v0.0.0-20200624215116-23e74cb168fe
github.com/casbin/casbin v1.9.1
github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 // indirect
@@ -35,6 +35,7 @@ require (
github.com/google/go-jsonnet v0.16.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/google/uuid v1.1.1
github.com/gorilla/handlers v1.5.0
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0

9
go.sum
View File

@@ -57,8 +57,8 @@ github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+Dx
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/argoproj/gitops-engine v0.1.3-0.20200826062957-2cf3a72c659c h1:sX3CD5rYfYe9l/tcVq6Zp09byT29EFOSDTggY8Zimgg=
github.com/argoproj/gitops-engine v0.1.3-0.20200826062957-2cf3a72c659c/go.mod h1:LhzAS5UB6MusZ8MJj1dys1Em5xGPxEIZHdp2oz81ViY=
github.com/argoproj/gitops-engine v0.1.3-0.20200904164417-c04f859da9b2 h1:/tFET94tzxQ8Uz5fQBI4nqa3pI5JRfcHBLhiwcIJaBg=
github.com/argoproj/gitops-engine v0.1.3-0.20200904164417-c04f859da9b2/go.mod h1:jeiFokbkgLXjvx3eXB0MddyxsmftWsFTY7paLH4ZqCg=
github.com/argoproj/pkg v0.0.0-20200624215116-23e74cb168fe h1:HjTM7H8Z+J1xt340LNpH9q4jc8pXeqzkC8QKIjQphp4=
github.com/argoproj/pkg v0.0.0-20200624215116-23e74cb168fe/go.mod h1:2EZ44RG/CcgtPTwrRR0apOc7oU6UIw8GjCUJWZ8X3bM=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -155,7 +155,6 @@ github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw=
github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
@@ -165,6 +164,8 @@ github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwo
github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
@@ -341,6 +342,8 @@ github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTV
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v1.5.0 h1:4wjo3sf9azi99c8hTmyaxp9y5S+pFszsy3pP0rAw/lw=
github.com/gorilla/handlers v1.5.0/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=

View File

@@ -12,4 +12,4 @@ bases:
images:
- name: argoproj/argocd
newName: argoproj/argocd
newTag: v1.7.2
newTag: v1.7.4

View File

@@ -821,7 +821,7 @@ spec:
type: object
type: array
observedAt:
description: ObservedAt indicates when the application state was updated without querying latest git state
description: 'ObservedAt indicates when the application state was updated without querying latest git state Deprecated: controller no longer updates ObservedAt field'
format: date-time
type: string
operationState:

View File

@@ -18,4 +18,4 @@ bases:
images:
- name: argoproj/argocd
newName: argoproj/argocd
newTag: v1.7.2
newTag: v1.7.4

View File

@@ -922,8 +922,9 @@ spec:
type: object
type: array
observedAt:
description: ObservedAt indicates when the application state was updated
without querying latest git state
description: 'ObservedAt indicates when the application state was updated
without querying latest git state Deprecated: controller no longer
updates ObservedAt field'
format: date-time
type: string
operationState:
@@ -3082,7 +3083,7 @@ spec:
- "10"
- --redis
- argocd-redis-ha-haproxy:6379
image: argoproj/argocd:v1.7.2
image: argoproj/argocd:v1.7.4
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -3138,7 +3139,7 @@ spec:
- -n
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:v1.7.2
image: argoproj/argocd:v1.7.4
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -3188,7 +3189,7 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis-ha-haproxy:6379
image: argoproj/argocd:v1.7.2
image: argoproj/argocd:v1.7.4
imagePullPolicy: Always
name: argocd-repo-server
ports:
@@ -3263,7 +3264,7 @@ spec:
env:
- name: ARGOCD_API_SERVER_REPLICAS
value: "2"
image: argoproj/argocd:v1.7.2
image: argoproj/argocd:v1.7.4
imagePullPolicy: Always
name: argocd-server
ports:

View File

@@ -922,8 +922,9 @@ spec:
type: object
type: array
observedAt:
description: ObservedAt indicates when the application state was updated
without querying latest git state
description: 'ObservedAt indicates when the application state was updated
without querying latest git state Deprecated: controller no longer
updates ObservedAt field'
format: date-time
type: string
operationState:
@@ -2997,7 +2998,7 @@ spec:
- "10"
- --redis
- argocd-redis-ha-haproxy:6379
image: argoproj/argocd:v1.7.2
image: argoproj/argocd:v1.7.4
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -3053,7 +3054,7 @@ spec:
- -n
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:v1.7.2
image: argoproj/argocd:v1.7.4
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -3103,7 +3104,7 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis-ha-haproxy:6379
image: argoproj/argocd:v1.7.2
image: argoproj/argocd:v1.7.4
imagePullPolicy: Always
name: argocd-repo-server
ports:
@@ -3178,7 +3179,7 @@ spec:
env:
- name: ARGOCD_API_SERVER_REPLICAS
value: "2"
image: argoproj/argocd:v1.7.2
image: argoproj/argocd:v1.7.4
imagePullPolicy: Always
name: argocd-server
ports:

View File

@@ -922,8 +922,9 @@ spec:
type: object
type: array
observedAt:
description: ObservedAt indicates when the application state was updated
without querying latest git state
description: 'ObservedAt indicates when the application state was updated
without querying latest git state Deprecated: controller no longer
updates ObservedAt field'
format: date-time
type: string
operationState:
@@ -2582,7 +2583,7 @@ spec:
- "20"
- --operation-processors
- "10"
image: argoproj/argocd:v1.7.2
image: argoproj/argocd:v1.7.4
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2638,7 +2639,7 @@ spec:
- -n
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:v1.7.2
image: argoproj/argocd:v1.7.4
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -2707,7 +2708,7 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis:6379
image: argoproj/argocd:v1.7.2
image: argoproj/argocd:v1.7.4
imagePullPolicy: Always
name: argocd-repo-server
ports:
@@ -2762,7 +2763,7 @@ spec:
- argocd-server
- --staticassets
- /shared/app
image: argoproj/argocd:v1.7.2
image: argoproj/argocd:v1.7.4
imagePullPolicy: Always
name: argocd-server
ports:

View File

@@ -922,8 +922,9 @@ spec:
type: object
type: array
observedAt:
description: ObservedAt indicates when the application state was updated
without querying latest git state
description: 'ObservedAt indicates when the application state was updated
without querying latest git state Deprecated: controller no longer
updates ObservedAt field'
format: date-time
type: string
operationState:
@@ -2497,7 +2498,7 @@ spec:
- "20"
- --operation-processors
- "10"
image: argoproj/argocd:v1.7.2
image: argoproj/argocd:v1.7.4
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2553,7 +2554,7 @@ spec:
- -n
- /usr/local/bin/argocd-util
- /shared
image: argoproj/argocd:v1.7.2
image: argoproj/argocd:v1.7.4
imagePullPolicy: Always
name: copyutil
volumeMounts:
@@ -2622,7 +2623,7 @@ spec:
- argocd-repo-server
- --redis
- argocd-redis:6379
image: argoproj/argocd:v1.7.2
image: argoproj/argocd:v1.7.4
imagePullPolicy: Always
name: argocd-repo-server
ports:
@@ -2677,7 +2678,7 @@ spec:
- argocd-server
- --staticassets
- /shared/app
image: argoproj/argocd:v1.7.2
image: argoproj/argocd:v1.7.4
imagePullPolicy: Always
name: argocd-server
ports:

View File

@@ -1898,140 +1898,142 @@ func init() {
}
var fileDescriptor_df6e82b174b5eaec = []byte{
// 2121 bytes of a gzipped FileDescriptorProto
// 2146 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xd4, 0x5a, 0xcd, 0x6f, 0x1c, 0x49,
0x15, 0xa7, 0xc6, 0x63, 0xcf, 0xf8, 0x39, 0xbb, 0xc9, 0xd6, 0x6e, 0x42, 0x6f, 0x67, 0xe2, 0x8c,
0x2a, 0x89, 0xe3, 0x38, 0x71, 0x4f, 0x6c, 0x02, 0x2c, 0x06, 0x29, 0xc4, 0x9b, 0xe0, 0x18, 0x9c,
0x60, 0xda, 0x09, 0x2b, 0x21, 0x21, 0xd4, 0xdb, 0x5d, 0x1e, 0x37, 0x9e, 0xe9, 0x6e, 0xba, 0x7b,
0x26, 0x1a, 0xa2, 0x1c, 0x58, 0x24, 0xc4, 0x01, 0x81, 0x10, 0x1c, 0x00, 0xf1, 0xb1, 0x82, 0x2b,
0x37, 0xe0, 0xc2, 0x61, 0x6f, 0xa0, 0x1c, 0x11, 0xbb, 0xe7, 0x08, 0x59, 0xfc, 0x01, 0x9c, 0x38,
0xa3, 0xaa, 0xae, 0xea, 0xae, 0x9e, 0xf4, 0xf4, 0x4c, 0xd6, 0xc3, 0x21, 0xb7, 0xa9, 0x57, 0xd5,
0xef, 0xfd, 0xde, 0x47, 0xfd, 0xaa, 0xea, 0x69, 0xe0, 0x62, 0x44, 0xc3, 0x3e, 0x0d, 0x5b, 0x56,
0x10, 0x74, 0x5c, 0xdb, 0x8a, 0x5d, 0xdf, 0x53, 0x7f, 0x1b, 0x41, 0xe8, 0xc7, 0x3e, 0x5e, 0x50,
0x44, 0xfa, 0x1b, 0x6d, 0xbf, 0xed, 0x73, 0x79, 0x8b, 0xfd, 0x4a, 0x96, 0xe8, 0x8d, 0xb6, 0xef,
0xb7, 0x3b, 0xb4, 0x65, 0x05, 0x6e, 0xcb, 0xf2, 0x3c, 0x3f, 0xe6, 0x8b, 0x23, 0x31, 0x4b, 0x0e,
0xdf, 0x8a, 0x0c, 0xd7, 0xe7, 0xb3, 0xb6, 0x1f, 0xd2, 0x56, 0x7f, 0xad, 0xd5, 0xa6, 0x1e, 0x0d,
0xad, 0x98, 0x3a, 0x62, 0xcd, 0x8d, 0x6c, 0x4d, 0xd7, 0xb2, 0x0f, 0x5c, 0x8f, 0x86, 0x83, 0x56,
0x70, 0xd8, 0x66, 0x82, 0xa8, 0xd5, 0xa5, 0xb1, 0x55, 0xf4, 0xd5, 0x76, 0xdb, 0x8d, 0x0f, 0x7a,
0xef, 0x1a, 0xb6, 0xdf, 0x6d, 0x59, 0x21, 0x07, 0xf6, 0x6d, 0xfe, 0x63, 0xd5, 0x76, 0xb2, 0xaf,
0x55, 0xf7, 0xfa, 0x6b, 0x56, 0x27, 0x38, 0xb0, 0x9e, 0x57, 0xb5, 0x59, 0xa6, 0x2a, 0xa4, 0x81,
0x2f, 0x62, 0xc5, 0x7f, 0xba, 0xb1, 0x1f, 0x0e, 0x94, 0x9f, 0x89, 0x0e, 0xf2, 0x57, 0x04, 0xa7,
0x6e, 0x65, 0xc6, 0xbe, 0xd6, 0xa3, 0xe1, 0x00, 0x63, 0xa8, 0x7a, 0x56, 0x97, 0x6a, 0xa8, 0x89,
0x96, 0xe7, 0x4d, 0xfe, 0x1b, 0x6b, 0x50, 0x0b, 0xe9, 0x7e, 0x48, 0xa3, 0x03, 0xad, 0xc2, 0xc5,
0x72, 0x88, 0x97, 0xa0, 0xc6, 0x2c, 0x53, 0x3b, 0xd6, 0x66, 0x9a, 0x33, 0xcb, 0xf3, 0x9b, 0x27,
0x8e, 0x9e, 0x9d, 0xaf, 0xef, 0x26, 0xa2, 0xc8, 0x94, 0x93, 0xd8, 0x80, 0x93, 0x21, 0x8d, 0xfc,
0x5e, 0x68, 0xd3, 0xaf, 0xd3, 0x30, 0x72, 0x7d, 0x4f, 0xab, 0x32, 0x4d, 0x9b, 0xd5, 0xa7, 0xcf,
0xce, 0x7f, 0xc2, 0x1c, 0x9e, 0xc4, 0x4d, 0xa8, 0x47, 0xb4, 0x43, 0xed, 0xd8, 0x0f, 0xb5, 0x59,
0x65, 0x61, 0x2a, 0x25, 0x5b, 0x70, 0xda, 0xa4, 0x7d, 0x97, 0xad, 0xbe, 0x47, 0x63, 0xcb, 0xb1,
0x62, 0x6b, 0xd8, 0x81, 0x4a, 0xea, 0x80, 0x0e, 0xf5, 0x50, 0x2c, 0xd6, 0x2a, 0x5c, 0x9e, 0x8e,
0x59, 0x14, 0x16, 0x95, 0x28, 0x98, 0x02, 0xc9, 0x9d, 0x3e, 0xf5, 0xe2, 0x68, 0xb4, 0xca, 0x75,
0x78, 0x4d, 0x82, 0xbe, 0x6f, 0x75, 0x69, 0x14, 0x58, 0x36, 0x4d, 0x74, 0x0b, 0xa8, 0xcf, 0x4f,
0xe3, 0x65, 0x38, 0xa1, 0x0a, 0xb5, 0x19, 0x65, 0x79, 0x6e, 0x06, 0x2f, 0xc1, 0x82, 0x1c, 0x3f,
0xdc, 0xbe, 0xad, 0x55, 0x95, 0x85, 0xea, 0x04, 0xd9, 0x05, 0x4d, 0xc1, 0x7e, 0xcf, 0xf2, 0xdc,
0x7d, 0x1a, 0xc5, 0xa3, 0x51, 0x37, 0x73, 0x81, 0x50, 0xe2, 0x9a, 0x86, 0xe3, 0x34, 0xbc, 0x9e,
0x8f, 0x46, 0xe0, 0x7b, 0x11, 0x25, 0x1f, 0xa0, 0x9c, 0xa5, 0xb7, 0x43, 0x6a, 0xc5, 0xd4, 0xa4,
0xdf, 0xe9, 0xd1, 0x28, 0xc6, 0x1e, 0xa8, 0x9b, 0x8e, 0x1b, 0x5c, 0x58, 0xff, 0x92, 0x91, 0x95,
0xa8, 0x21, 0x4b, 0x94, 0xff, 0xf8, 0x96, 0xed, 0x18, 0xc1, 0x61, 0xdb, 0x60, 0xd5, 0x6e, 0xa8,
0x1b, 0x58, 0x56, 0xbb, 0xa1, 0x58, 0x92, 0x5e, 0x2b, 0xeb, 0xf0, 0x19, 0x98, 0xeb, 0x05, 0x11,
0x0d, 0x63, 0xee, 0x43, 0xdd, 0x14, 0x23, 0x96, 0xe6, 0xbe, 0xd5, 0x71, 0x1d, 0x2b, 0x66, 0xb1,
0x65, 0x33, 0xe9, 0x98, 0xbc, 0x9f, 0x77, 0xe0, 0x61, 0xe0, 0x28, 0x0e, 0x1c, 0xfc, 0x1f, 0x1d,
0xc8, 0x43, 0x57, 0x21, 0x56, 0x86, 0x20, 0xde, 0xcd, 0x21, 0xbc, 0x4d, 0x3b, 0x34, 0x43, 0x58,
0x94, 0x4c, 0x0d, 0x6a, 0xb6, 0x15, 0xd9, 0x96, 0x23, 0x55, 0xc9, 0x21, 0x79, 0x5a, 0x85, 0x33,
0x8a, 0xaa, 0xbd, 0x81, 0x67, 0x97, 0x29, 0x1a, 0x5b, 0x15, 0xb8, 0x01, 0x73, 0x4e, 0x38, 0x30,
0x7b, 0x5e, 0x12, 0x57, 0x31, 0x2f, 0x64, 0x58, 0x87, 0xd9, 0x20, 0xec, 0x79, 0x94, 0xef, 0x69,
0x39, 0x99, 0x88, 0xb0, 0x0d, 0xf5, 0x28, 0x66, 0xcc, 0xd5, 0x1e, 0xf0, 0x9d, 0xbc, 0xb0, 0xbe,
0x75, 0x8c, 0xb8, 0x32, 0x4f, 0xf6, 0x84, 0x3a, 0x33, 0x55, 0x8c, 0x63, 0x98, 0x97, 0xbb, 0x22,
0xd2, 0x6a, 0xcd, 0x99, 0xe5, 0x85, 0xf5, 0xdd, 0x63, 0x5a, 0xf9, 0x6a, 0xc0, 0xf8, 0x56, 0x21,
0x04, 0xe1, 0x56, 0x66, 0x08, 0x37, 0x60, 0xbe, 0x2b, 0x76, 0x5c, 0xa4, 0xd5, 0x19, 0xfd, 0x99,
0x99, 0x00, 0x3f, 0x84, 0x59, 0xd7, 0xdb, 0xf7, 0x23, 0x6d, 0x9e, 0xe3, 0xb9, 0x79, 0x0c, 0x3c,
0xdb, 0xde, 0xbe, 0x6f, 0x26, 0xda, 0xb0, 0x07, 0xaf, 0x84, 0x34, 0x0e, 0x07, 0x32, 0x0a, 0x1a,
0xf0, 0xa0, 0xde, 0x3d, 0x86, 0x7a, 0x53, 0xd5, 0x67, 0xe6, 0xd5, 0x93, 0x3f, 0x23, 0x68, 0x3c,
0xb7, 0x6f, 0xf6, 0x02, 0x5a, 0x5a, 0x50, 0x0e, 0x54, 0xa3, 0x80, 0xda, 0x9c, 0x0f, 0x17, 0xd6,
0xbf, 0x3c, 0x9d, 0x8d, 0xc4, 0x8c, 0x8a, 0x24, 0x70, 0xed, 0xa5, 0xdb, 0xbd, 0x0b, 0x9f, 0x54,
0x3e, 0xdd, 0xb5, 0x62, 0xfb, 0xa0, 0x0c, 0x30, 0xab, 0x60, 0xb6, 0x26, 0xc7, 0xe0, 0x89, 0x08,
0x13, 0x98, 0xe7, 0x3f, 0x1e, 0x0c, 0x82, 0x3c, 0x65, 0x67, 0x62, 0xf2, 0x03, 0x04, 0xba, 0xba,
0xe7, 0xfd, 0x4e, 0xe7, 0x5d, 0xcb, 0x3e, 0x2c, 0x37, 0x59, 0x71, 0x1d, 0x6e, 0x6f, 0x66, 0x13,
0x98, 0xbe, 0xa3, 0x67, 0xe7, 0x2b, 0xdb, 0xb7, 0xcd, 0x8a, 0xeb, 0x7c, 0xfc, 0xed, 0x46, 0x3e,
0x1a, 0x02, 0x22, 0x8a, 0xb5, 0x0c, 0x08, 0x81, 0x79, 0xaf, 0xf0, 0x04, 0xcb, 0xc4, 0x2f, 0x70,
0x72, 0x2d, 0x42, 0xad, 0x9f, 0x9e, 0xf0, 0xd9, 0x22, 0x29, 0x64, 0xe0, 0xdb, 0xa1, 0xdf, 0x0b,
0xb4, 0x59, 0x35, 0xd2, 0x5c, 0x84, 0x35, 0xa8, 0x1e, 0xba, 0x9e, 0xa3, 0xcd, 0x29, 0x53, 0x5c,
0x42, 0x7e, 0x59, 0x81, 0xf3, 0x05, 0x6e, 0x8d, 0xcd, 0xeb, 0x4b, 0xe0, 0x5b, 0x56, 0x7b, 0xb5,
0x31, 0xb5, 0x57, 0x2f, 0xae, 0xbd, 0xff, 0x22, 0x68, 0x16, 0xc4, 0x66, 0xfc, 0xf9, 0xf1, 0x92,
0x04, 0x67, 0xdf, 0x0f, 0x6d, 0xaa, 0xd5, 0xd2, 0x5a, 0x47, 0x66, 0x22, 0x22, 0xff, 0x41, 0xa0,
0x49, 0x6f, 0x6f, 0xd9, 0xdc, 0xf7, 0x9e, 0xf7, 0xb2, 0x3b, 0xdc, 0x80, 0x39, 0x8b, 0xfb, 0x92,
0x2b, 0x07, 0x21, 0x23, 0x3f, 0x44, 0x70, 0x36, 0xef, 0x72, 0xb4, 0xe3, 0x46, 0xb1, 0xbc, 0xa6,
0x61, 0x17, 0x6a, 0xc9, 0xca, 0x48, 0x43, 0xfc, 0xd8, 0xd9, 0x3e, 0xd6, 0xb9, 0xa0, 0x1a, 0x92,
0xee, 0x09, 0xfd, 0xe4, 0x26, 0x9c, 0x2d, 0x24, 0x1a, 0x81, 0xa4, 0x09, 0x75, 0x79, 0x16, 0x26,
0x39, 0x90, 0x77, 0x0a, 0x29, 0x25, 0x7f, 0xab, 0xe4, 0x39, 0xda, 0x77, 0x76, 0xfc, 0x76, 0xc9,
0x8d, 0x7b, 0x92, 0xec, 0x69, 0x50, 0x0b, 0x7c, 0x27, 0x4b, 0x9c, 0x29, 0x87, 0xec, 0x6b, 0xdb,
0xf7, 0x62, 0x8b, 0x3d, 0xd5, 0x72, 0xf9, 0xca, 0xc4, 0x2c, 0xf7, 0x91, 0xeb, 0xd9, 0x74, 0x8f,
0xda, 0xbe, 0xe7, 0x44, 0x3c, 0x71, 0x33, 0x32, 0xf7, 0xea, 0x0c, 0xbe, 0x0b, 0xf3, 0x7c, 0xfc,
0xc0, 0xed, 0x52, 0x6d, 0x8e, 0x9f, 0xc0, 0x2b, 0x46, 0xf2, 0x26, 0x34, 0xd4, 0x37, 0x61, 0x16,
0x61, 0xf6, 0x26, 0x34, 0xfa, 0x6b, 0x06, 0xfb, 0xc2, 0xcc, 0x3e, 0x66, 0xb8, 0x62, 0xcb, 0xed,
0xec, 0xb8, 0x1e, 0xbf, 0xba, 0x64, 0x06, 0x33, 0x31, 0xab, 0x89, 0x7d, 0xbf, 0xd3, 0xf1, 0x1f,
0x71, 0x0a, 0x48, 0x8f, 0x83, 0x44, 0x46, 0xbe, 0x0b, 0xf5, 0x1d, 0xbf, 0x7d, 0xc7, 0x8b, 0xc3,
0x01, 0xab, 0x49, 0xe6, 0x0e, 0xf5, 0xf2, 0x41, 0x97, 0x42, 0x7c, 0x1f, 0xe6, 0x63, 0xb7, 0x4b,
0xf7, 0x62, 0xab, 0x1b, 0x88, 0xd3, 0xf9, 0x05, 0x70, 0xa7, 0xc8, 0xa4, 0x0a, 0xd2, 0x82, 0x37,
0xd3, 0x8b, 0xd2, 0x03, 0x1a, 0x76, 0x5d, 0xcf, 0x2a, 0xe5, 0x1c, 0xb2, 0x96, 0xab, 0x1a, 0x76,
0xd1, 0x7a, 0xc7, 0xf5, 0x1c, 0xff, 0xd1, 0xe8, 0xbc, 0x93, 0x7f, 0xe6, 0x1f, 0x68, 0xca, 0x37,
0x69, 0xb1, 0xdd, 0x85, 0x57, 0x58, 0x59, 0xf6, 0xa9, 0x98, 0x10, 0xc5, 0x4f, 0x72, 0x75, 0x5d,
0xa8, 0xc3, 0xcc, 0x7f, 0x88, 0x77, 0xe0, 0xa4, 0x15, 0x45, 0x6e, 0xdb, 0xa3, 0x8e, 0xd4, 0x55,
0x99, 0x58, 0xd7, 0xf0, 0xa7, 0xc9, 0x0d, 0x9d, 0xaf, 0xe0, 0xe5, 0xc8, 0x6f, 0xe8, 0x7c, 0x48,
0xbe, 0x8f, 0xe0, 0x74, 0xa1, 0x12, 0x16, 0x02, 0x4e, 0x0d, 0x22, 0x04, 0x82, 0x05, 0xeb, 0x91,
0x7d, 0x40, 0x9d, 0x5e, 0x87, 0xca, 0xf7, 0xab, 0x1c, 0xb3, 0x39, 0xa7, 0x97, 0x64, 0x40, 0xd4,
0x7c, 0x3a, 0xc6, 0x8b, 0x00, 0x5d, 0xcb, 0xeb, 0x59, 0x1d, 0x0e, 0xa1, 0xca, 0x21, 0x28, 0x12,
0xd2, 0x00, 0xbd, 0x28, 0x7d, 0xe2, 0xcd, 0xf7, 0x11, 0x82, 0x57, 0xe5, 0xbe, 0x16, 0xf9, 0x31,
0xe0, 0xa4, 0x12, 0x86, 0xfb, 0x69, 0xaa, 0x04, 0x31, 0x0f, 0x4f, 0x0e, 0xef, 0x59, 0x54, 0xbc,
0x67, 0x93, 0x9c, 0xcf, 0x28, 0xd3, 0xc9, 0x8e, 0xcf, 0x31, 0x2c, 0x2a, 0x65, 0x58, 0x34, 0x9a,
0x61, 0xd1, 0xd0, 0x5d, 0x62, 0x00, 0xda, 0x3d, 0xcb, 0xb3, 0xda, 0xd4, 0x49, 0x9d, 0x4b, 0x0b,
0xe9, 0x9b, 0x30, 0xeb, 0xc6, 0xb4, 0x2b, 0x0b, 0x68, 0x6b, 0x0a, 0xec, 0x79, 0xdb, 0xdd, 0xdf,
0x37, 0x13, 0xad, 0xeb, 0x1f, 0x36, 0x00, 0xab, 0x59, 0xa7, 0x61, 0xdf, 0xb5, 0x29, 0xfe, 0x09,
0x82, 0x2a, 0xa3, 0x71, 0x7c, 0x6e, 0x54, 0x91, 0xf1, 0xe8, 0xeb, 0x53, 0xba, 0x48, 0x33, 0x53,
0xa4, 0xf1, 0xde, 0x87, 0xff, 0xfe, 0x59, 0xe5, 0x0c, 0x7e, 0x83, 0xf7, 0xb9, 0xfa, 0x6b, 0x6a,
0xdb, 0x29, 0xc2, 0x3f, 0x42, 0x80, 0xc5, 0xc1, 0xa2, 0x74, 0x43, 0xf0, 0xd5, 0x51, 0xf8, 0x0a,
0xba, 0x26, 0xfa, 0x39, 0x85, 0x58, 0x0c, 0xdb, 0x0f, 0x29, 0xa3, 0x11, 0xbe, 0x80, 0x03, 0x58,
0xe1, 0x00, 0x2e, 0x62, 0x52, 0x04, 0xa0, 0xf5, 0x98, 0x15, 0xc0, 0x93, 0x16, 0x4d, 0xec, 0xfe,
0x0e, 0xc1, 0xec, 0x3b, 0xfc, 0x42, 0x34, 0x26, 0x42, 0xbb, 0xd3, 0x89, 0x10, 0xb7, 0xc5, 0xa1,
0x92, 0x0b, 0x1c, 0xe6, 0x39, 0x7c, 0x56, 0xc2, 0x8c, 0xe2, 0x90, 0x5a, 0xdd, 0x1c, 0xda, 0xeb,
0x08, 0xff, 0x1e, 0xc1, 0x5c, 0xd2, 0x14, 0xc1, 0x97, 0x46, 0x41, 0xcc, 0x35, 0x4d, 0xf4, 0x29,
0xb5, 0x17, 0xc8, 0x15, 0x0e, 0xf0, 0x02, 0x29, 0x4c, 0xe4, 0x46, 0xae, 0xf9, 0xf0, 0x53, 0x04,
0x33, 0x5b, 0x74, 0x6c, 0x99, 0x4d, 0x0b, 0xd9, 0x73, 0xa1, 0x2b, 0xc8, 0x30, 0xfe, 0x03, 0x82,
0x37, 0xb7, 0x68, 0x5c, 0x4c, 0xf0, 0x78, 0x79, 0x3c, 0xeb, 0x8a, 0x6a, 0xbb, 0x3a, 0xc1, 0xca,
0x94, 0xd9, 0x5a, 0x1c, 0xd9, 0x15, 0x7c, 0xb9, 0xac, 0xf6, 0xa2, 0x81, 0x67, 0x3f, 0x12, 0x38,
0xfe, 0x8e, 0xe0, 0xd4, 0x70, 0xbb, 0x11, 0xe7, 0x8f, 0x84, 0xc2, 0x6e, 0xa4, 0xfe, 0x95, 0x63,
0x31, 0x48, 0x5e, 0x23, 0xb9, 0xc5, 0x61, 0x7f, 0x1e, 0x7f, 0xae, 0x0c, 0xb6, 0xec, 0xd9, 0x44,
0xad, 0xc7, 0xf2, 0xe7, 0x13, 0xde, 0x91, 0xe6, 0x98, 0xdf, 0x43, 0x70, 0x62, 0x8b, 0xc6, 0xf7,
0xd2, 0x36, 0xc5, 0xc8, 0x6a, 0xcd, 0x35, 0x13, 0xf5, 0x86, 0xa1, 0xb4, 0x8f, 0xe5, 0x54, 0x1a,
0xcf, 0x55, 0x0e, 0xec, 0x32, 0xbe, 0x54, 0x06, 0x2c, 0x6b, 0x8d, 0x7c, 0x80, 0x60, 0x2e, 0x69,
0x24, 0x8c, 0x36, 0x9f, 0x6b, 0xd0, 0x4d, 0xad, 0x24, 0xef, 0x70, 0xa0, 0x37, 0xf5, 0xeb, 0xc5,
0x40, 0xd5, 0xef, 0x65, 0xc8, 0x0c, 0x8e, 0x3e, 0xbf, 0x91, 0xfe, 0x84, 0x00, 0xb2, 0x4e, 0x08,
0xbe, 0x52, 0xee, 0x84, 0xd2, 0x2d, 0xd1, 0xa7, 0xd8, 0x0b, 0x21, 0x06, 0x77, 0x66, 0x59, 0x6f,
0x96, 0x56, 0x71, 0x40, 0xed, 0x8d, 0xa4, 0x5f, 0xf2, 0x1b, 0x04, 0xb3, 0xfc, 0xc5, 0x8c, 0x2f,
0x8e, 0x02, 0xac, 0x3e, 0xa8, 0xa7, 0x16, 0xf4, 0x25, 0x8e, 0xb3, 0xb9, 0x5e, 0xc6, 0x03, 0x1b,
0x68, 0x05, 0xf7, 0x61, 0x2e, 0x79, 0xb4, 0x8e, 0xae, 0x8a, 0xdc, 0xa3, 0x56, 0x6f, 0x96, 0x1c,
0x47, 0x49, 0x61, 0x0a, 0x0a, 0x5a, 0x29, 0xa5, 0xa0, 0xf7, 0x11, 0x54, 0x19, 0x4b, 0xe0, 0x0b,
0x65, 0x1c, 0x32, 0xed, 0xa8, 0x5c, 0xe5, 0xd0, 0x2e, 0x91, 0xe6, 0x38, 0x0e, 0x62, 0xa1, 0xf9,
0x05, 0x82, 0x53, 0xc3, 0x97, 0x16, 0x7c, 0x76, 0x88, 0x7f, 0xd4, 0x9b, 0x9a, 0x9e, 0x0f, 0xe1,
0xa8, 0x0b, 0x0f, 0xf9, 0x22, 0x47, 0xb1, 0x81, 0xdf, 0x1a, 0xbb, 0x21, 0xee, 0xcb, 0x4d, 0xcc,
0x14, 0xad, 0x66, 0x5d, 0xd0, 0xbf, 0x20, 0x38, 0x21, 0xf5, 0x3e, 0x08, 0x29, 0x2d, 0x87, 0x35,
0xa5, 0xfa, 0x67, 0x86, 0xc8, 0x17, 0x38, 0xf6, 0xcf, 0xe0, 0x1b, 0x13, 0x62, 0x97, 0x98, 0x57,
0x63, 0x06, 0xf3, 0x8f, 0x08, 0xea, 0xb2, 0x4f, 0x87, 0x2f, 0x8f, 0xac, 0xa4, 0x7c, 0x27, 0x6f,
0x6a, 0xd9, 0x17, 0x27, 0x10, 0xb9, 0x58, 0x4a, 0xe5, 0xc2, 0x38, 0xab, 0x80, 0x9f, 0x23, 0xc0,
0xe9, 0x15, 0x3d, 0xbd, 0xb4, 0xe3, 0xa5, 0x9c, 0xa9, 0x91, 0x6f, 0x31, 0xfd, 0xf2, 0xd8, 0x75,
0x79, 0x2a, 0x5f, 0x29, 0xa5, 0x72, 0x3f, 0xb5, 0xff, 0x63, 0x04, 0x0b, 0x5b, 0x34, 0xbd, 0x27,
0x96, 0x04, 0x32, 0xdf, 0x89, 0xd4, 0x97, 0xc7, 0x2f, 0x14, 0x88, 0xae, 0x71, 0x44, 0x4b, 0xb8,
0x3c, 0x54, 0x12, 0xc0, 0xaf, 0x11, 0xbc, 0x22, 0x58, 0x4c, 0x48, 0xae, 0x8d, 0xb3, 0x94, 0x23,
0xbd, 0xc9, 0x71, 0x7d, 0x8a, 0xe3, 0x5a, 0x25, 0x13, 0xe1, 0xda, 0x10, 0x0d, 0xbd, 0xdf, 0x22,
0x78, 0x5d, 0xbd, 0x58, 0x8b, 0x26, 0xce, 0xc7, 0x8d, 0x5b, 0x49, 0x2f, 0x88, 0xdc, 0xe0, 0xf8,
0x0c, 0x7c, 0x6d, 0x12, 0x7c, 0x2d, 0xd1, 0xd6, 0xc1, 0xbf, 0x42, 0xf0, 0x1a, 0x6f, 0xa3, 0xa9,
0x8a, 0x87, 0x08, 0x79, 0x54, 0xd3, 0x6d, 0x02, 0x42, 0x16, 0x7b, 0x96, 0xbc, 0x10, 0xa8, 0x0d,
0xd1, 0xfe, 0x62, 0x0f, 0xa5, 0x57, 0xe5, 0x11, 0x20, 0xb2, 0xbb, 0x3a, 0x2e, 0x70, 0x2f, 0x7a,
0x64, 0x88, 0x72, 0x5b, 0x99, 0xac, 0xdc, 0xbe, 0x87, 0xa0, 0x26, 0x3a, 0x57, 0x25, 0xa7, 0xaa,
0xd2, 0xda, 0xd2, 0x4f, 0xe7, 0x56, 0xc9, 0xce, 0x0d, 0xf9, 0x2c, 0x37, 0xbb, 0x86, 0x5b, 0x65,
0x66, 0x03, 0xdf, 0x89, 0x5a, 0x8f, 0x45, 0x4b, 0xeb, 0x49, 0xab, 0xe3, 0xb7, 0xa3, 0xeb, 0x68,
0xf3, 0xed, 0xa7, 0x47, 0x8b, 0xe8, 0x1f, 0x47, 0x8b, 0xe8, 0x5f, 0x47, 0x8b, 0xe8, 0x1b, 0x9f,
0x9e, 0xe0, 0x4f, 0x06, 0x76, 0xc7, 0xa5, 0x5e, 0xac, 0x9a, 0xf8, 0x5f, 0x00, 0x00, 0x00, 0xff,
0xff, 0x00, 0x68, 0x8b, 0x98, 0x5d, 0x21, 0x00, 0x00,
0x15, 0xa7, 0xc6, 0x63, 0xcf, 0xf8, 0x39, 0xd9, 0x24, 0xb5, 0x9b, 0xd0, 0xdb, 0x71, 0x9c, 0x51,
0xe5, 0xcb, 0x71, 0xe2, 0x9e, 0xd8, 0x04, 0x58, 0xbc, 0xa0, 0x10, 0x27, 0xc1, 0x31, 0x38, 0xc1,
0xb4, 0x13, 0x22, 0x21, 0x21, 0xd4, 0xdb, 0x5d, 0x1e, 0x37, 0x9e, 0xe9, 0x6e, 0xba, 0x7b, 0x26,
0x1a, 0xa2, 0x1c, 0x58, 0x24, 0xc4, 0x01, 0x81, 0x10, 0x1c, 0x58, 0xc4, 0xc7, 0x0a, 0xae, 0xdc,
0x80, 0x0b, 0x87, 0xbd, 0x20, 0x50, 0x8e, 0x08, 0xf6, 0x1c, 0x21, 0x8b, 0x3f, 0x80, 0x13, 0x67,
0x54, 0xd5, 0x55, 0xdd, 0xd5, 0x93, 0x9e, 0x9e, 0xc9, 0x7a, 0x10, 0xca, 0x6d, 0xea, 0x55, 0xf5,
0x7b, 0xbf, 0xf7, 0xea, 0x57, 0xef, 0x55, 0x3d, 0x0d, 0x9c, 0x8f, 0x68, 0xd8, 0xa3, 0x61, 0xd3,
0x0a, 0x82, 0xb6, 0x6b, 0x5b, 0xb1, 0xeb, 0x7b, 0xea, 0x6f, 0x23, 0x08, 0xfd, 0xd8, 0xc7, 0x73,
0x8a, 0x48, 0x7f, 0xa3, 0xe5, 0xb7, 0x7c, 0x2e, 0x6f, 0xb2, 0x5f, 0xc9, 0x12, 0x7d, 0xbe, 0xe5,
0xfb, 0xad, 0x36, 0x6d, 0x5a, 0x81, 0xdb, 0xb4, 0x3c, 0xcf, 0x8f, 0xf9, 0xe2, 0x48, 0xcc, 0x92,
0xfd, 0xb7, 0x22, 0xc3, 0xf5, 0xf9, 0xac, 0xed, 0x87, 0xb4, 0xd9, 0x5b, 0x69, 0xb6, 0xa8, 0x47,
0x43, 0x2b, 0xa6, 0x8e, 0x58, 0x73, 0x3d, 0x5b, 0xd3, 0xb1, 0xec, 0x3d, 0xd7, 0xa3, 0x61, 0xbf,
0x19, 0xec, 0xb7, 0x98, 0x20, 0x6a, 0x76, 0x68, 0x6c, 0x15, 0x7d, 0xb5, 0xd9, 0x72, 0xe3, 0xbd,
0xee, 0x3b, 0x86, 0xed, 0x77, 0x9a, 0x56, 0xc8, 0x81, 0x7d, 0x93, 0xff, 0x58, 0xb6, 0x9d, 0xec,
0x6b, 0xd5, 0xbd, 0xde, 0x8a, 0xd5, 0x0e, 0xf6, 0xac, 0x17, 0x55, 0xad, 0x97, 0xa9, 0x0a, 0x69,
0xe0, 0x8b, 0x58, 0xf1, 0x9f, 0x6e, 0xec, 0x87, 0x7d, 0xe5, 0x67, 0xa2, 0x83, 0xfc, 0x09, 0xc1,
0xf1, 0x9b, 0x99, 0xb1, 0xaf, 0x74, 0x69, 0xd8, 0xc7, 0x18, 0xaa, 0x9e, 0xd5, 0xa1, 0x1a, 0x6a,
0xa0, 0xc5, 0x59, 0x93, 0xff, 0xc6, 0x1a, 0xd4, 0x42, 0xba, 0x1b, 0xd2, 0x68, 0x4f, 0xab, 0x70,
0xb1, 0x1c, 0xe2, 0x8b, 0x50, 0x63, 0x96, 0xa9, 0x1d, 0x6b, 0x53, 0x8d, 0xa9, 0xc5, 0xd9, 0xf5,
0x23, 0x07, 0xcf, 0xcf, 0xd6, 0xb7, 0x13, 0x51, 0x64, 0xca, 0x49, 0x6c, 0xc0, 0xb1, 0x90, 0x46,
0x7e, 0x37, 0xb4, 0xe9, 0x57, 0x69, 0x18, 0xb9, 0xbe, 0xa7, 0x55, 0x99, 0xa6, 0xf5, 0xea, 0xb3,
0xe7, 0x67, 0x3f, 0x66, 0x0e, 0x4e, 0xe2, 0x06, 0xd4, 0x23, 0xda, 0xa6, 0x76, 0xec, 0x87, 0xda,
0xb4, 0xb2, 0x30, 0x95, 0x92, 0x0d, 0x38, 0x69, 0xd2, 0x9e, 0xcb, 0x56, 0xdf, 0xa3, 0xb1, 0xe5,
0x58, 0xb1, 0x35, 0xe8, 0x40, 0x25, 0x75, 0x40, 0x87, 0x7a, 0x28, 0x16, 0x6b, 0x15, 0x2e, 0x4f,
0xc7, 0x2c, 0x0a, 0x0b, 0x4a, 0x14, 0x4c, 0x81, 0xe4, 0x4e, 0x8f, 0x7a, 0x71, 0x34, 0x5c, 0xe5,
0x2a, 0x9c, 0x90, 0xa0, 0xef, 0x5b, 0x1d, 0x1a, 0x05, 0x96, 0x4d, 0x13, 0xdd, 0x02, 0xea, 0x8b,
0xd3, 0x78, 0x11, 0x8e, 0xa8, 0x42, 0x6d, 0x4a, 0x59, 0x9e, 0x9b, 0xc1, 0x17, 0x61, 0x4e, 0x8e,
0x1f, 0x6e, 0xde, 0xd6, 0xaa, 0xca, 0x42, 0x75, 0x82, 0x6c, 0x83, 0xa6, 0x60, 0xbf, 0x67, 0x79,
0xee, 0x2e, 0x8d, 0xe2, 0xe1, 0xa8, 0x1b, 0xb9, 0x40, 0x28, 0x71, 0x4d, 0xc3, 0x71, 0x12, 0x5e,
0xcf, 0x47, 0x23, 0xf0, 0xbd, 0x88, 0x92, 0x0f, 0x50, 0xce, 0xd2, 0xad, 0x90, 0x5a, 0x31, 0x35,
0xe9, 0xb7, 0xba, 0x34, 0x8a, 0xb1, 0x07, 0xea, 0xa1, 0xe3, 0x06, 0xe7, 0x56, 0xbf, 0x60, 0x64,
0x14, 0x35, 0x24, 0x45, 0xf9, 0x8f, 0x6f, 0xd8, 0x8e, 0x11, 0xec, 0xb7, 0x0c, 0xc6, 0x76, 0x43,
0x3d, 0xc0, 0x92, 0xed, 0x86, 0x62, 0x49, 0x7a, 0xad, 0xac, 0xc3, 0xa7, 0x60, 0xa6, 0x1b, 0x44,
0x34, 0x8c, 0xb9, 0x0f, 0x75, 0x53, 0x8c, 0xd8, 0x36, 0xf7, 0xac, 0xb6, 0xeb, 0x58, 0x31, 0x8b,
0x2d, 0x9b, 0x49, 0xc7, 0xe4, 0xfd, 0xbc, 0x03, 0x0f, 0x03, 0x47, 0x71, 0x60, 0xef, 0x7f, 0xe8,
0x40, 0x1e, 0xba, 0x0a, 0xb1, 0x32, 0x00, 0xf1, 0x6e, 0x0e, 0xe1, 0x6d, 0xda, 0xa6, 0x19, 0xc2,
0xa2, 0xcd, 0xd4, 0xa0, 0x66, 0x5b, 0x91, 0x6d, 0x39, 0x52, 0x95, 0x1c, 0x92, 0x67, 0x55, 0x38,
0xa5, 0xa8, 0xda, 0xe9, 0x7b, 0x76, 0x99, 0xa2, 0x91, 0xac, 0xc0, 0xf3, 0x30, 0xe3, 0x84, 0x7d,
0xb3, 0xeb, 0x25, 0x71, 0x15, 0xf3, 0x42, 0x86, 0x75, 0x98, 0x0e, 0xc2, 0xae, 0x47, 0xf9, 0x99,
0x96, 0x93, 0x89, 0x08, 0xdb, 0x50, 0x8f, 0x62, 0x96, 0xb9, 0x5a, 0x7d, 0x7e, 0x92, 0xe7, 0x56,
0x37, 0x0e, 0x11, 0x57, 0xe6, 0xc9, 0x8e, 0x50, 0x67, 0xa6, 0x8a, 0x71, 0x0c, 0xb3, 0xf2, 0x54,
0x44, 0x5a, 0xad, 0x31, 0xb5, 0x38, 0xb7, 0xba, 0x7d, 0x48, 0x2b, 0x5f, 0x0e, 0x58, 0xbe, 0x55,
0x12, 0x82, 0x70, 0x2b, 0x33, 0x84, 0xe7, 0x61, 0xb6, 0x23, 0x4e, 0x5c, 0xa4, 0xd5, 0x59, 0xfa,
0x33, 0x33, 0x01, 0x7e, 0x08, 0xd3, 0xae, 0xb7, 0xeb, 0x47, 0xda, 0x2c, 0xc7, 0x73, 0xe3, 0x10,
0x78, 0x36, 0xbd, 0x5d, 0xdf, 0x4c, 0xb4, 0x61, 0x0f, 0x8e, 0x86, 0x34, 0x0e, 0xfb, 0x32, 0x0a,
0x1a, 0xf0, 0xa0, 0xde, 0x3d, 0x84, 0x7a, 0x53, 0xd5, 0x67, 0xe6, 0xd5, 0x93, 0x3f, 0x20, 0x98,
0x7f, 0xe1, 0xdc, 0xec, 0x04, 0xb4, 0x94, 0x50, 0x0e, 0x54, 0xa3, 0x80, 0xda, 0x3c, 0x1f, 0xce,
0xad, 0x7e, 0x71, 0x32, 0x07, 0x89, 0x19, 0x15, 0x9b, 0xc0, 0xb5, 0x97, 0x1e, 0xf7, 0x0e, 0x7c,
0x5c, 0xf9, 0x74, 0xdb, 0x8a, 0xed, 0xbd, 0x32, 0xc0, 0x8c, 0xc1, 0x6c, 0x4d, 0x2e, 0x83, 0x27,
0x22, 0x4c, 0x60, 0x96, 0xff, 0x78, 0xd0, 0x0f, 0xf2, 0x29, 0x3b, 0x13, 0x93, 0xef, 0x21, 0xd0,
0xd5, 0x33, 0xef, 0xb7, 0xdb, 0xef, 0x58, 0xf6, 0x7e, 0xb9, 0xc9, 0x8a, 0xeb, 0x70, 0x7b, 0x53,
0xeb, 0xc0, 0xf4, 0x1d, 0x3c, 0x3f, 0x5b, 0xd9, 0xbc, 0x6d, 0x56, 0x5c, 0xe7, 0xa3, 0x1f, 0x37,
0xf2, 0xe1, 0x00, 0x10, 0x41, 0xd6, 0x32, 0x20, 0x04, 0x66, 0xbd, 0xc2, 0x0a, 0x96, 0x89, 0x5f,
0xa2, 0x72, 0x2d, 0x40, 0xad, 0x97, 0x56, 0xf8, 0x6c, 0x91, 0x14, 0x32, 0xf0, 0xad, 0xd0, 0xef,
0x06, 0xda, 0xb4, 0x1a, 0x69, 0x2e, 0xc2, 0x1a, 0x54, 0xf7, 0x5d, 0xcf, 0xd1, 0x66, 0x94, 0x29,
0x2e, 0x21, 0xef, 0x55, 0xe0, 0x6c, 0x81, 0x5b, 0x23, 0xf7, 0xf5, 0x15, 0xf0, 0x2d, 0xe3, 0x5e,
0x6d, 0x04, 0xf7, 0xea, 0xc5, 0xdc, 0xfb, 0x0f, 0x82, 0x46, 0x41, 0x6c, 0x46, 0xd7, 0x8f, 0x57,
0x24, 0x38, 0xbb, 0x7e, 0x68, 0x53, 0xad, 0x96, 0x72, 0x1d, 0x99, 0x89, 0x88, 0xfc, 0x1b, 0x81,
0x26, 0xbd, 0xbd, 0x69, 0x73, 0xdf, 0xbb, 0xde, 0xab, 0xee, 0xf0, 0x3c, 0xcc, 0x58, 0xdc, 0x97,
0x1c, 0x1d, 0x84, 0x8c, 0x7c, 0x1f, 0xc1, 0xe9, 0xbc, 0xcb, 0xd1, 0x96, 0x1b, 0xc5, 0xf2, 0x9a,
0x86, 0x5d, 0xa8, 0x25, 0x2b, 0x23, 0x0d, 0xf1, 0xb2, 0xb3, 0x79, 0xa8, 0xba, 0xa0, 0x1a, 0x92,
0xee, 0x09, 0xfd, 0xe4, 0x06, 0x9c, 0x2e, 0x4c, 0x34, 0x02, 0x49, 0x03, 0xea, 0xb2, 0x16, 0x26,
0x7b, 0x20, 0xef, 0x14, 0x52, 0x4a, 0xfe, 0x52, 0xc9, 0xe7, 0x68, 0xdf, 0xd9, 0xf2, 0x5b, 0x25,
0x37, 0xee, 0x71, 0x76, 0x4f, 0x83, 0x5a, 0xe0, 0x3b, 0xd9, 0xc6, 0x99, 0x72, 0xc8, 0xbe, 0xb6,
0x7d, 0x2f, 0xb6, 0xd8, 0x53, 0x2d, 0xb7, 0x5f, 0x99, 0x98, 0xed, 0x7d, 0xe4, 0x7a, 0x36, 0xdd,
0xa1, 0xb6, 0xef, 0x39, 0x11, 0xdf, 0xb8, 0x29, 0xb9, 0xf7, 0xea, 0x0c, 0xbe, 0x0b, 0xb3, 0x7c,
0xfc, 0xc0, 0xed, 0x50, 0x6d, 0x86, 0x57, 0xe0, 0x25, 0x23, 0x79, 0x13, 0x1a, 0xea, 0x9b, 0x30,
0x8b, 0x30, 0x7b, 0x13, 0x1a, 0xbd, 0x15, 0x83, 0x7d, 0x61, 0x66, 0x1f, 0x33, 0x5c, 0xb1, 0xe5,
0xb6, 0xb7, 0x5c, 0x8f, 0x5f, 0x5d, 0x32, 0x83, 0x99, 0x98, 0x71, 0x62, 0xd7, 0x6f, 0xb7, 0xfd,
0xc7, 0x3c, 0x05, 0xa4, 0xe5, 0x20, 0x91, 0x91, 0x6f, 0x43, 0x7d, 0xcb, 0x6f, 0xdd, 0xf1, 0xe2,
0xb0, 0xcf, 0x38, 0xc9, 0xdc, 0xa1, 0x5e, 0x3e, 0xe8, 0x52, 0x88, 0xef, 0xc3, 0x6c, 0xec, 0x76,
0xe8, 0x4e, 0x6c, 0x75, 0x02, 0x51, 0x9d, 0x5f, 0x02, 0x77, 0x8a, 0x4c, 0xaa, 0x20, 0x4d, 0x78,
0x33, 0xbd, 0x28, 0x3d, 0xa0, 0x61, 0xc7, 0xf5, 0xac, 0xd2, 0x9c, 0x43, 0x56, 0x72, 0xac, 0x61,
0x17, 0xad, 0x47, 0xae, 0xe7, 0xf8, 0x8f, 0x87, 0xef, 0x3b, 0xf9, 0x7b, 0xfe, 0x81, 0xa6, 0x7c,
0x93, 0x92, 0xed, 0x2e, 0x1c, 0x65, 0xb4, 0xec, 0x51, 0x31, 0x21, 0xc8, 0x4f, 0x72, 0xbc, 0x2e,
0xd4, 0x61, 0xe6, 0x3f, 0xc4, 0x5b, 0x70, 0xcc, 0x8a, 0x22, 0xb7, 0xe5, 0x51, 0x47, 0xea, 0xaa,
0x8c, 0xad, 0x6b, 0xf0, 0xd3, 0xe4, 0x86, 0xce, 0x57, 0x70, 0x3a, 0xf2, 0x1b, 0x3a, 0x1f, 0x92,
0xef, 0x22, 0x38, 0x59, 0xa8, 0x84, 0x85, 0x80, 0xa7, 0x06, 0x11, 0x02, 0x91, 0x05, 0xeb, 0x91,
0xbd, 0x47, 0x9d, 0x6e, 0x9b, 0xca, 0xf7, 0xab, 0x1c, 0xb3, 0x39, 0xa7, 0x9b, 0xec, 0x80, 0xe0,
0x7c, 0x3a, 0xc6, 0x0b, 0x00, 0x1d, 0xcb, 0xeb, 0x5a, 0x6d, 0x0e, 0xa1, 0xca, 0x21, 0x28, 0x12,
0x32, 0x0f, 0x7a, 0xd1, 0xf6, 0x89, 0x37, 0xdf, 0x87, 0x08, 0x5e, 0x93, 0xe7, 0x5a, 0xec, 0x8f,
0x01, 0xc7, 0x94, 0x30, 0xdc, 0x4f, 0xb7, 0x4a, 0x24, 0xe6, 0xc1, 0xc9, 0xc1, 0x33, 0x8b, 0x8a,
0xcf, 0x6c, 0xb2, 0xe7, 0x53, 0xca, 0x74, 0x72, 0xe2, 0x73, 0x19, 0x16, 0x95, 0x66, 0x58, 0x34,
0x3c, 0xc3, 0xa2, 0x81, 0xbb, 0x44, 0x1f, 0xb4, 0x7b, 0x96, 0x67, 0xb5, 0xa8, 0x93, 0x3a, 0x97,
0x12, 0xe9, 0xeb, 0x30, 0xed, 0xc6, 0xb4, 0x23, 0x09, 0xb4, 0x31, 0x81, 0xec, 0x79, 0xdb, 0xdd,
0xdd, 0x35, 0x13, 0xad, 0xab, 0xef, 0x2d, 0x00, 0x56, 0x77, 0x9d, 0x86, 0x3d, 0xd7, 0xa6, 0xf8,
0x47, 0x08, 0xaa, 0x2c, 0x8d, 0xe3, 0x33, 0xc3, 0x48, 0xc6, 0xa3, 0xaf, 0x4f, 0xe8, 0x22, 0xcd,
0x4c, 0x91, 0xf9, 0x77, 0xff, 0xf1, 0xaf, 0x9f, 0x54, 0x4e, 0xe1, 0x37, 0x78, 0x9f, 0xab, 0xb7,
0xa2, 0xb6, 0x9d, 0x22, 0xfc, 0x03, 0x04, 0x58, 0x14, 0x16, 0xa5, 0x1b, 0x82, 0xaf, 0x0c, 0xc3,
0x57, 0xd0, 0x35, 0xd1, 0xcf, 0x28, 0x89, 0xc5, 0xb0, 0xfd, 0x90, 0xb2, 0x34, 0xc2, 0x17, 0x70,
0x00, 0x4b, 0x1c, 0xc0, 0x79, 0x4c, 0x8a, 0x00, 0x34, 0x9f, 0x30, 0x02, 0x3c, 0x6d, 0xd2, 0xc4,
0xee, 0xaf, 0x11, 0x4c, 0x3f, 0xe2, 0x17, 0xa2, 0x11, 0x11, 0xda, 0x9e, 0x4c, 0x84, 0xb8, 0x2d,
0x0e, 0x95, 0x9c, 0xe3, 0x30, 0xcf, 0xe0, 0xd3, 0x12, 0x66, 0x14, 0x87, 0xd4, 0xea, 0xe4, 0xd0,
0x5e, 0x43, 0xf8, 0x37, 0x08, 0x66, 0x92, 0xa6, 0x08, 0xbe, 0x30, 0x0c, 0x62, 0xae, 0x69, 0xa2,
0x4f, 0xa8, 0xbd, 0x40, 0x2e, 0x73, 0x80, 0xe7, 0x48, 0xe1, 0x46, 0xae, 0xe5, 0x9a, 0x0f, 0x3f,
0x46, 0x30, 0xb5, 0x41, 0x47, 0xd2, 0x6c, 0x52, 0xc8, 0x5e, 0x08, 0x5d, 0xc1, 0x0e, 0xe3, 0xdf,
0x22, 0x78, 0x73, 0x83, 0xc6, 0xc5, 0x09, 0x1e, 0x2f, 0x8e, 0xce, 0xba, 0x82, 0x6d, 0x57, 0xc6,
0x58, 0x99, 0x66, 0xb6, 0x26, 0x47, 0x76, 0x19, 0x5f, 0x2a, 0xe3, 0x5e, 0xd4, 0xf7, 0xec, 0xc7,
0x02, 0xc7, 0x5f, 0x11, 0x1c, 0x1f, 0x6c, 0x37, 0xe2, 0x7c, 0x49, 0x28, 0xec, 0x46, 0xea, 0x5f,
0x3a, 0x54, 0x06, 0xc9, 0x6b, 0x24, 0x37, 0x39, 0xec, 0xb7, 0xf1, 0x67, 0xca, 0x60, 0xcb, 0x9e,
0x4d, 0xd4, 0x7c, 0x22, 0x7f, 0x3e, 0xe5, 0x1d, 0x69, 0x8e, 0xf9, 0x5d, 0x04, 0x47, 0x36, 0x68,
0x7c, 0x2f, 0x6d, 0x53, 0x0c, 0x65, 0x6b, 0xae, 0x99, 0xa8, 0xcf, 0x1b, 0x4a, 0xfb, 0x58, 0x4e,
0xa5, 0xf1, 0x5c, 0xe6, 0xc0, 0x2e, 0xe1, 0x0b, 0x65, 0xc0, 0xb2, 0xd6, 0xc8, 0x07, 0x08, 0x66,
0x92, 0x46, 0xc2, 0x70, 0xf3, 0xb9, 0x06, 0xdd, 0xc4, 0x28, 0x79, 0x87, 0x03, 0xbd, 0xa1, 0x5f,
0x2b, 0x06, 0xaa, 0x7e, 0x2f, 0x43, 0x66, 0x70, 0xf4, 0xf9, 0x83, 0xf4, 0x7b, 0x04, 0x90, 0x75,
0x42, 0xf0, 0xe5, 0x72, 0x27, 0x94, 0x6e, 0x89, 0x3e, 0xc1, 0x5e, 0x08, 0x31, 0xb8, 0x33, 0x8b,
0x7a, 0xa3, 0x94, 0xc5, 0x01, 0xb5, 0xd7, 0x92, 0x7e, 0xc9, 0x2f, 0x11, 0x4c, 0xf3, 0x17, 0x33,
0x3e, 0x3f, 0x0c, 0xb0, 0xfa, 0xa0, 0x9e, 0x58, 0xd0, 0x2f, 0x72, 0x9c, 0x8d, 0xd5, 0xb2, 0x3c,
0xb0, 0x86, 0x96, 0x70, 0x0f, 0x66, 0x92, 0x47, 0xeb, 0x70, 0x56, 0xe4, 0x1e, 0xb5, 0x7a, 0xa3,
0xa4, 0x1c, 0x25, 0xc4, 0x14, 0x29, 0x68, 0xa9, 0x34, 0x05, 0xbd, 0x8f, 0xa0, 0xca, 0xb2, 0x04,
0x3e, 0x57, 0x96, 0x43, 0x26, 0x1d, 0x95, 0x2b, 0x1c, 0xda, 0x05, 0xd2, 0x18, 0x95, 0x83, 0x58,
0x68, 0x7e, 0x86, 0xe0, 0xf8, 0xe0, 0xa5, 0x05, 0x9f, 0x1e, 0xc8, 0x3f, 0xea, 0x4d, 0x4d, 0xcf,
0x87, 0x70, 0xd8, 0x85, 0x87, 0x7c, 0x9e, 0xa3, 0x58, 0xc3, 0x6f, 0x8d, 0x3c, 0x10, 0xf7, 0xe5,
0x21, 0x66, 0x8a, 0x96, 0xb3, 0x2e, 0xe8, 0x1f, 0x11, 0x1c, 0x91, 0x7a, 0x1f, 0x84, 0x94, 0x96,
0xc3, 0x9a, 0x10, 0xff, 0x99, 0x21, 0xf2, 0x59, 0x8e, 0xfd, 0x53, 0xf8, 0xfa, 0x98, 0xd8, 0x25,
0xe6, 0xe5, 0x98, 0xc1, 0xfc, 0x33, 0x82, 0x13, 0x8f, 0x12, 0xba, 0xff, 0x3f, 0xc0, 0xdf, 0xe2,
0xe0, 0x3f, 0x87, 0xdf, 0x2e, 0xb9, 0x57, 0x8c, 0xf2, 0xe1, 0x1a, 0xc2, 0xbf, 0x43, 0x50, 0x97,
0xdd, 0x46, 0x7c, 0x69, 0xe8, 0x79, 0xc8, 0xf7, 0x23, 0x27, 0xc6, 0x61, 0x51, 0x47, 0xc9, 0xf9,
0xd2, 0x82, 0x24, 0x8c, 0x33, 0x1e, 0xff, 0x14, 0x01, 0x4e, 0x1f, 0x1a, 0xe9, 0xd3, 0x03, 0x5f,
0xcc, 0x99, 0x1a, 0xfa, 0xa2, 0xd4, 0x2f, 0x8d, 0x5c, 0x97, 0x2f, 0x48, 0x4b, 0xa5, 0x05, 0xc9,
0x4f, 0xed, 0xff, 0x10, 0xc1, 0xdc, 0x06, 0x4d, 0x6f, 0xbb, 0x25, 0x81, 0xcc, 0xf7, 0x53, 0xf5,
0xc5, 0xd1, 0x0b, 0x05, 0xa2, 0xab, 0x1c, 0xd1, 0x45, 0x5c, 0x1e, 0x2a, 0x09, 0xe0, 0x17, 0x08,
0x8e, 0x6e, 0xab, 0xe4, 0xc4, 0x57, 0x47, 0x59, 0xca, 0xa5, 0xee, 0xf1, 0x71, 0x7d, 0x82, 0xe3,
0x5a, 0x26, 0x63, 0xe1, 0x5a, 0x13, 0x6d, 0xc9, 0x5f, 0x21, 0x78, 0x5d, 0x7d, 0x1e, 0x88, 0x56,
0xd4, 0x47, 0x8d, 0x5b, 0x49, 0x47, 0x8b, 0x5c, 0xe7, 0xf8, 0x0c, 0x7c, 0x75, 0x1c, 0x7c, 0x4d,
0xd1, 0x9c, 0xc2, 0x3f, 0x47, 0x70, 0x82, 0x37, 0x03, 0x55, 0xc5, 0x03, 0x65, 0x65, 0x58, 0xeb,
0x70, 0x8c, 0xb2, 0x22, 0x32, 0x0f, 0x79, 0x29, 0x50, 0x6b, 0xa2, 0x89, 0xc7, 0x9e, 0x7b, 0xaf,
0xc9, 0x42, 0x26, 0x76, 0x77, 0x79, 0x54, 0xe0, 0x5e, 0xb6, 0xf0, 0x09, 0xba, 0x2d, 0x8d, 0x47,
0xb7, 0xef, 0x20, 0xa8, 0x89, 0xfe, 0x5b, 0xc9, 0xdd, 0x40, 0x69, 0xd0, 0xe9, 0x27, 0x73, 0xab,
0x64, 0xff, 0x89, 0x7c, 0x9a, 0x9b, 0x5d, 0xc1, 0xcd, 0x32, 0xb3, 0x81, 0xef, 0x44, 0xcd, 0x27,
0xa2, 0x31, 0xf7, 0xb4, 0xd9, 0xf6, 0x5b, 0xd1, 0x35, 0xb4, 0x7e, 0xeb, 0xd9, 0xc1, 0x02, 0xfa,
0xdb, 0xc1, 0x02, 0xfa, 0xe7, 0xc1, 0x02, 0xfa, 0xda, 0x27, 0xc7, 0xf8, 0xab, 0x84, 0xdd, 0x76,
0xa9, 0x17, 0xab, 0x26, 0xfe, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x67, 0x2b, 0xcb, 0xbc, 0x23, 0x22,
0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@@ -2074,6 +2076,8 @@ type ApplicationServiceClient interface {
Sync(ctx context.Context, in *ApplicationSyncRequest, opts ...grpc.CallOption) (*v1alpha1.Application, error)
ManagedResources(ctx context.Context, in *ResourcesQuery, opts ...grpc.CallOption) (*ManagedResourcesResponse, error)
ResourceTree(ctx context.Context, in *ResourcesQuery, opts ...grpc.CallOption) (*v1alpha1.ApplicationTree, error)
// Watch returns stream of application resource tree
WatchResourceTree(ctx context.Context, in *ResourcesQuery, opts ...grpc.CallOption) (ApplicationService_WatchResourceTreeClient, error)
// Rollback syncs an application to its target state
Rollback(ctx context.Context, in *ApplicationRollbackRequest, opts ...grpc.CallOption) (*v1alpha1.Application, error)
// TerminateOperation terminates the currently running operation
@@ -2256,6 +2260,38 @@ func (c *applicationServiceClient) ResourceTree(ctx context.Context, in *Resourc
return out, nil
}
func (c *applicationServiceClient) WatchResourceTree(ctx context.Context, in *ResourcesQuery, opts ...grpc.CallOption) (ApplicationService_WatchResourceTreeClient, error) {
stream, err := c.cc.NewStream(ctx, &_ApplicationService_serviceDesc.Streams[1], "/application.ApplicationService/WatchResourceTree", opts...)
if err != nil {
return nil, err
}
x := &applicationServiceWatchResourceTreeClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type ApplicationService_WatchResourceTreeClient interface {
Recv() (*v1alpha1.ApplicationTree, error)
grpc.ClientStream
}
type applicationServiceWatchResourceTreeClient struct {
grpc.ClientStream
}
func (x *applicationServiceWatchResourceTreeClient) Recv() (*v1alpha1.ApplicationTree, error) {
m := new(v1alpha1.ApplicationTree)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *applicationServiceClient) Rollback(ctx context.Context, in *ApplicationRollbackRequest, opts ...grpc.CallOption) (*v1alpha1.Application, error) {
out := new(v1alpha1.Application)
err := c.cc.Invoke(ctx, "/application.ApplicationService/Rollback", in, out, opts...)
@@ -2320,7 +2356,7 @@ func (c *applicationServiceClient) DeleteResource(ctx context.Context, in *Appli
}
func (c *applicationServiceClient) PodLogs(ctx context.Context, in *ApplicationPodLogsQuery, opts ...grpc.CallOption) (ApplicationService_PodLogsClient, error) {
stream, err := c.cc.NewStream(ctx, &_ApplicationService_serviceDesc.Streams[1], "/application.ApplicationService/PodLogs", opts...)
stream, err := c.cc.NewStream(ctx, &_ApplicationService_serviceDesc.Streams[2], "/application.ApplicationService/PodLogs", opts...)
if err != nil {
return nil, err
}
@@ -2381,6 +2417,8 @@ type ApplicationServiceServer interface {
Sync(context.Context, *ApplicationSyncRequest) (*v1alpha1.Application, error)
ManagedResources(context.Context, *ResourcesQuery) (*ManagedResourcesResponse, error)
ResourceTree(context.Context, *ResourcesQuery) (*v1alpha1.ApplicationTree, error)
// Watch returns stream of application resource tree
WatchResourceTree(*ResourcesQuery, ApplicationService_WatchResourceTreeServer) error
// Rollback syncs an application to its target state
Rollback(context.Context, *ApplicationRollbackRequest) (*v1alpha1.Application, error)
// TerminateOperation terminates the currently running operation
@@ -2446,6 +2484,9 @@ func (*UnimplementedApplicationServiceServer) ManagedResources(ctx context.Conte
func (*UnimplementedApplicationServiceServer) ResourceTree(ctx context.Context, req *ResourcesQuery) (*v1alpha1.ApplicationTree, error) {
return nil, status.Errorf(codes.Unimplemented, "method ResourceTree not implemented")
}
func (*UnimplementedApplicationServiceServer) WatchResourceTree(req *ResourcesQuery, srv ApplicationService_WatchResourceTreeServer) error {
return status.Errorf(codes.Unimplemented, "method WatchResourceTree not implemented")
}
func (*UnimplementedApplicationServiceServer) Rollback(ctx context.Context, req *ApplicationRollbackRequest) (*v1alpha1.Application, error) {
return nil, status.Errorf(codes.Unimplemented, "method Rollback not implemented")
}
@@ -2748,6 +2789,27 @@ func _ApplicationService_ResourceTree_Handler(srv interface{}, ctx context.Conte
return interceptor(ctx, in, info, handler)
}
func _ApplicationService_WatchResourceTree_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(ResourcesQuery)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(ApplicationServiceServer).WatchResourceTree(m, &applicationServiceWatchResourceTreeServer{stream})
}
type ApplicationService_WatchResourceTreeServer interface {
Send(*v1alpha1.ApplicationTree) error
grpc.ServerStream
}
type applicationServiceWatchResourceTreeServer struct {
grpc.ServerStream
}
func (x *applicationServiceWatchResourceTreeServer) Send(m *v1alpha1.ApplicationTree) error {
return x.ServerStream.SendMsg(m)
}
func _ApplicationService_Rollback_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ApplicationRollbackRequest)
if err := dec(in); err != nil {
@@ -2990,6 +3052,11 @@ var _ApplicationService_serviceDesc = grpc.ServiceDesc{
Handler: _ApplicationService_Watch_Handler,
ServerStreams: true,
},
{
StreamName: "WatchResourceTree",
Handler: _ApplicationService_WatchResourceTree_Handler,
ServerStreams: true,
},
{
StreamName: "PodLogs",
Handler: _ApplicationService_PodLogs_Handler,

View File

@@ -559,6 +559,52 @@ func request_ApplicationService_ResourceTree_0(ctx context.Context, marshaler ru
}
var (
filter_ApplicationService_WatchResourceTree_0 = &utilities.DoubleArray{Encoding: map[string]int{"applicationName": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
)
func request_ApplicationService_WatchResourceTree_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationServiceClient, req *http.Request, pathParams map[string]string) (ApplicationService_WatchResourceTreeClient, runtime.ServerMetadata, error) {
var protoReq ResourcesQuery
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["applicationName"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "applicationName")
}
protoReq.ApplicationName, err = runtime.StringP(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "applicationName", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ApplicationService_WatchResourceTree_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
stream, err := client.WatchResourceTree(ctx, &protoReq)
if err != nil {
return nil, metadata, err
}
header, err := stream.Header()
if err != nil {
return nil, metadata, err
}
metadata.HeaderMD = header
return stream, metadata, nil
}
func request_ApplicationService_Rollback_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ApplicationRollbackRequest
var metadata runtime.ServerMetadata
@@ -1222,6 +1268,26 @@ func RegisterApplicationServiceHandlerClient(ctx context.Context, mux *runtime.S
})
mux.Handle("GET", pattern_ApplicationService_WatchResourceTree_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ApplicationService_WatchResourceTree_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_ApplicationService_WatchResourceTree_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_ApplicationService_Rollback_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@@ -1416,6 +1482,8 @@ var (
pattern_ApplicationService_ResourceTree_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "applications", "applicationName", "resource-tree"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_ApplicationService_WatchResourceTree_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"api", "v1", "stream", "applications", "applicationName", "resource-tree"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_ApplicationService_Rollback_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "applications", "name", "rollback"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_ApplicationService_TerminateOperation_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "applications", "name", "operation"}, "", runtime.AssumeColonVerbOpt(true)))
@@ -1464,6 +1532,8 @@ var (
forward_ApplicationService_ResourceTree_0 = runtime.ForwardResponseMessage
forward_ApplicationService_WatchResourceTree_0 = runtime.ForwardResponseStream
forward_ApplicationService_Rollback_0 = runtime.ForwardResponseMessage
forward_ApplicationService_TerminateOperation_0 = runtime.ForwardResponseMessage

View File

@@ -11,6 +11,7 @@ import (
func init() {
forward_ApplicationService_PodLogs_0 = http.StreamForwarder
forward_ApplicationService_WatchResourceTree_0 = http.StreamForwarder
forward_ApplicationService_Watch_0 = http.NewStreamForwarder(func(message proto.Message) (string, error) {
event, ok := message.(*v1alpha1.ApplicationWatchEvent)
if !ok {

View File

@@ -0,0 +1,9 @@
package project
import (
"github.com/argoproj/pkg/grpc/http"
)
func init() {
forward_ProjectService_List_0 = http.UnaryForwarder
}

View File

@@ -283,6 +283,7 @@ message ApplicationStatus {
optional OperationState operationState = 7;
// ObservedAt indicates when the application state was updated without querying latest git state
// Deprecated: controller no longer updates ObservedAt field
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time observedAt = 8;
optional string sourceType = 9;

View File

@@ -1058,7 +1058,7 @@ func schema_pkg_apis_application_v1alpha1_ApplicationStatus(ref common.Reference
},
"observedAt": {
SchemaProps: spec.SchemaProps{
Description: "ObservedAt indicates when the application state was updated without querying latest git state",
Description: "ObservedAt indicates when the application state was updated without querying latest git state Deprecated: controller no longer updates ObservedAt field",
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
},
},

View File

@@ -425,6 +425,7 @@ type ApplicationStatus struct {
ReconciledAt *metav1.Time `json:"reconciledAt,omitempty" protobuf:"bytes,6,opt,name=reconciledAt"`
OperationState *OperationState `json:"operationState,omitempty" protobuf:"bytes,7,opt,name=operationState"`
// ObservedAt indicates when the application state was updated without querying latest git state
// Deprecated: controller no longer updates ObservedAt field
ObservedAt *metav1.Time `json:"observedAt,omitempty" protobuf:"bytes,8,opt,name=observedAt"`
SourceType ApplicationSourceType `json:"sourceType,omitempty" protobuf:"bytes,9,opt,name=sourceType"`
Summary ApplicationSummary `json:"summary,omitempty" protobuf:"bytes,10,opt,name=summary"`
@@ -2603,7 +2604,7 @@ func jwtTokensCombine(tokens1 []JWTToken, tokens2 []JWTToken) []JWTToken {
tokensMap[token.ID] = token
}
tokens := []JWTToken{}
var tokens []JWTToken
for _, v := range tokensMap {
tokens = append(tokens, v)
}

View File

@@ -1695,6 +1695,11 @@ func TestProjectNormalize(t *testing.T) {
assert.Nil(t, p.Spec.Roles)
assert.Nil(t, p.Status.JWTTokensByRole)
})
t.Run("HasRoles_NoTokens", func(t *testing.T) {
p := AppProject{Spec: AppProjectSpec{Roles: []ProjectRole{{Name: "test-role"}}}}
needNormalize := p.NormalizeJWTTokens()
assert.False(t, needNormalize)
})
t.Run("SpecRolesToken-StatusRolesTokenEmpty", func(t *testing.T) {
p := AppProject{Spec: AppProjectSpec{Roles: []ProjectRole{{Name: "test-role", JWTTokens: testTokens}}}}
needNormalize := p.NormalizeJWTTokens()

View File

@@ -572,14 +572,7 @@ func findManifests(appPath string, repoRoot string, env *v1alpha1.Env, directory
} else {
yamlObjs, err := kube.SplitYAML(out)
if err != nil {
if len(yamlObjs) > 0 {
// If we get here, we had a multiple objects in a single YAML file which had some
// valid k8s objects, but errors parsing others (within the same file). It's very
// likely the user messed up a portion of the YAML, so report on that.
return status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", f.Name(), err)
}
// Otherwise, it might be a unrelated YAML file which we will ignore
return nil
return status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", f.Name(), err)
}
objs = append(objs, yamlObjs...)
}

View File

@@ -61,6 +61,7 @@ type Server struct {
kubeclientset kubernetes.Interface
appclientset appclientset.Interface
appLister applisters.ApplicationNamespaceLister
appInformer cache.SharedIndexInformer
appBroadcaster *broadcasterHandler
repoClientset apiclient.Clientset
kubectl kube.Kubectl
@@ -93,6 +94,7 @@ func NewServer(
ns: namespace,
appclientset: appclientset,
appLister: appLister,
appInformer: appInformer,
appBroadcaster: appBroadcaster,
kubeclientset: kubeclientset,
cache: cache,
@@ -132,6 +134,9 @@ func (s *Server) List(ctx context.Context, q *application.ApplicationQuery) (*ap
return newItems[i].Name < newItems[j].Name
})
appList := appv1.ApplicationList{
ListMeta: metav1.ListMeta{
ResourceVersion: s.appInformer.LastSyncResourceVersion(),
},
Items: newItems,
}
return &appList, nil
@@ -636,12 +641,15 @@ func (s *Server) Watch(q *application.ApplicationQuery, ws application.Applicati
}
events := make(chan *appv1.ApplicationWatchEvent)
apps, err := s.appLister.List(selector)
if err != nil {
return err
}
for i := range apps {
sendIfPermitted(*apps[i], watch.Added)
if q.ResourceVersion == "" {
// mimic watch API behavior: send ADDED events if no resource version provided
apps, err := s.appLister.List(selector)
if err != nil {
return err
}
for i := range apps {
sendIfPermitted(*apps[i], watch.Added)
}
}
unsubscribe := s.appBroadcaster.Subscribe(events)
defer unsubscribe()
@@ -901,6 +909,17 @@ func (s *Server) ResourceTree(ctx context.Context, q *application.ResourcesQuery
return s.getAppResources(ctx, a)
}
func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application.ApplicationService_WatchResourceTreeServer) error {
return s.cache.OnAppResourcesTreeChanged(ws.Context(), q.GetApplicationName(), func() error {
var tree appv1.ApplicationTree
err := s.cache.GetAppResourcesTree(q.GetApplicationName(), &tree)
if err != nil {
return err
}
return ws.Send(&tree)
})
}
func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMetadataQuery) (*v1alpha1.RevisionMetadata, error) {
a, err := s.appLister.Get(q.GetName())
if err != nil {

View File

@@ -294,6 +294,12 @@ service ApplicationService {
rpc ResourceTree(ResourcesQuery) returns (github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.ApplicationTree) {
option (google.api.http).get = "/api/v1/applications/{applicationName}/resource-tree";
}
// Watch returns stream of application resource tree
rpc WatchResourceTree(ResourcesQuery) returns (stream github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.ApplicationTree) {
option (google.api.http).get = "/api/v1/stream/applications/{applicationName}/resource-tree";
}
// Rollback syncs an application to its target state
rpc Rollback(ApplicationRollbackRequest) returns (github.com.argoproj.argo_cd.pkg.apis.application.v1alpha1.Application) {
option (google.api.http) = {

View File

@@ -1,6 +1,7 @@
package cache
import (
"context"
"fmt"
"time"
@@ -57,6 +58,10 @@ func (c *Cache) GetAppResourcesTree(appName string, res *appv1.ApplicationTree)
return c.cache.GetAppResourcesTree(appName, res)
}
func (c *Cache) OnAppResourcesTreeChanged(ctx context.Context, appName string, callback func() error) error {
return c.cache.OnAppResourcesTreeChanged(ctx, appName, callback)
}
func (c *Cache) GetAppManagedResources(appName string, res *[]*appv1.ResourceDiff) error {
return c.cache.GetAppManagedResources(appName, res)
}

View File

@@ -412,7 +412,6 @@ func (s *Server) NormalizeProjs() error {
return status.Errorf(codes.Internal, "Error retrieving project list: %s", err.Error())
}
for _, proj := range projList.Items {
// if !apierr.IsConflict(err), retry 3 times
for i := 0; i < 3; i++ {
if proj.NormalizeJWTTokens() {
_, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Update(context.Background(), &proj, metav1.UpdateOptions{})
@@ -432,6 +431,8 @@ func (s *Server) NormalizeProjs() error {
if i == 2 {
return status.Errorf(codes.Internal, "Failed normalize project %s", proj.Name)
}
} else {
break
}
}
}

View File

@@ -22,6 +22,7 @@ import (
jwt "github.com/dgrijalva/jwt-go"
"github.com/go-redis/redis"
golang_proto "github.com/golang/protobuf/proto"
"github.com/gorilla/handlers"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
@@ -45,11 +46,6 @@ import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
cacheutil "github.com/argoproj/argo-cd/util/cache"
"github.com/argoproj/argo-cd/util/healthz"
"github.com/argoproj/argo-cd/util/swagger"
"github.com/argoproj/argo-cd/util/webhook"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/pkg/apiclient"
accountpkg "github.com/argoproj/argo-cd/pkg/apiclient/account"
@@ -85,18 +81,22 @@ import (
"github.com/argoproj/argo-cd/server/version"
"github.com/argoproj/argo-cd/util"
"github.com/argoproj/argo-cd/util/assets"
cacheutil "github.com/argoproj/argo-cd/util/cache"
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/dex"
dexutil "github.com/argoproj/argo-cd/util/dex"
"github.com/argoproj/argo-cd/util/env"
grpc_util "github.com/argoproj/argo-cd/util/grpc"
"github.com/argoproj/argo-cd/util/healthz"
httputil "github.com/argoproj/argo-cd/util/http"
"github.com/argoproj/argo-cd/util/jwt/zjwt"
"github.com/argoproj/argo-cd/util/oidc"
"github.com/argoproj/argo-cd/util/rbac"
util_session "github.com/argoproj/argo-cd/util/session"
settings_util "github.com/argoproj/argo-cd/util/settings"
"github.com/argoproj/argo-cd/util/swagger"
tlsutil "github.com/argoproj/argo-cd/util/tls"
"github.com/argoproj/argo-cd/util/webhook"
)
const maxConcurrentLoginRequestsCountEnv = "ARGOCD_MAX_CONCURRENT_LOGIN_REQUESTS_COUNT"
@@ -159,6 +159,7 @@ type ArgoCDServer struct {
type ArgoCDServerOpts struct {
DisableAuth bool
EnableGZip bool
Insecure bool
ListenPort int
MetricsPort int
@@ -520,7 +521,19 @@ func (a *ArgoCDServer) newGRPCServer() *grpc.Server {
}
sessionService := session.NewServer(a.sessionMgr, a, a.policyEnforcer, loginRateLimiter)
projectLock := util.NewKeyLock()
applicationService := application.NewServer(a.Namespace, a.KubeClientset, a.AppClientset, a.appLister, a.appInformer, a.RepoClientset, a.Cache, kubectl, db, a.enf, projectLock, a.settingsMgr)
applicationService := application.NewServer(
a.Namespace,
a.KubeClientset,
a.AppClientset,
a.appLister,
a.appInformer,
a.RepoClientset,
a.Cache,
kubectl,
db,
a.enf,
projectLock,
a.settingsMgr)
projectService := project.NewServer(a.Namespace, a.KubeClientset, a.AppClientset, a.enf, projectLock, a.sessionMgr, a.policyEnforcer)
settingsService := settings.NewServer(a.settingsMgr, a, a.DisableAuth)
accountService := account.NewServer(a.sessionMgr, a.settingsMgr, a.enf)
@@ -577,13 +590,23 @@ func withRootPath(handler http.Handler, a *ArgoCDServer) http.Handler {
mux.Handle("/"+root+"/", http.StripPrefix("/"+root, handler))
healthz.ServeHealthCheck(mux, func() error {
_, err := a.KubeClientset.(*kubernetes.Clientset).ServerVersion()
return err
return nil
})
return mux
}
func compressHandler(handler http.Handler) http.Handler {
compr := handlers.CompressHandler(handler)
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
if request.Header.Get("Accept") == "text/event-stream" {
handler.ServeHTTP(writer, request)
} else {
compr.ServeHTTP(writer, request)
}
})
}
// newHTTPServer returns the HTTP server to serve HTTP/HTTPS requests. This is implemented
// using grpc-gateway as a proxy to the gRPC server.
func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandler http.Handler) *http.Server {
@@ -629,7 +652,13 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
gwMuxOpts := runtime.WithMarshalerOption(runtime.MIMEWildcard, new(grpc_util.JSONMarshaler))
gwCookieOpts := runtime.WithForwardResponseOption(a.translateGrpcCookieHeader)
gwmux := runtime.NewServeMux(gwMuxOpts, gwCookieOpts)
mux.Handle("/api/", gwmux)
var handler http.Handler = gwmux
if a.EnableGZip {
handler = compressHandler(handler)
}
mux.Handle("/api/", handler)
mustRegisterGWHandler(versionpkg.RegisterVersionServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts)
mustRegisterGWHandler(clusterpkg.RegisterClusterServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts)
mustRegisterGWHandler(applicationpkg.RegisterApplicationServiceHandlerFromEndpoint, ctx, gwmux, endpoint, dOpts)
@@ -645,8 +674,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
// Swagger UI
swagger.ServeSwaggerUI(mux, assets.SwaggerJSON, "/swagger-ui", a.RootPath)
healthz.ServeHealthCheck(mux, func() error {
_, err := a.KubeClientset.(*kubernetes.Clientset).ServerVersion()
return err
return nil
})
// Dex reverse proxy and client app and OAuth2 login/callback

View File

@@ -120,7 +120,7 @@ export const ApplicationCreatePanel = (props: {
key='creation-deps'
load={() =>
Promise.all([
services.projects.list().then(projects => projects.map(proj => proj.metadata.name).sort()),
services.projects.list('items.metadata.name').then(projects => projects.map(proj => proj.metadata.name).sort()),
services.clusters.list().then(clusters => clusters.sort()),
services.repos.list()
]).then(([projects, clusters, reposInfo]) => ({projects, clusters, reposInfo}))

View File

@@ -38,7 +38,7 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{nam
apis: PropTypes.object
};
private refreshRequested = new BehaviorSubject(null);
private appChanged = new BehaviorSubject<appModels.Application>(null);
constructor(props: RouteComponentProps<{name: string}>) {
super(props);
@@ -459,7 +459,13 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{nam
</React.Fragment>
),
disabled: !!refreshing,
action: () => !refreshing && services.applications.get(app.metadata.name, 'normal')
action: () => {
if (!refreshing) {
services.applications.get(app.metadata.name, 'normal');
AppUtils.setAppRefreshing(app);
this.appChanged.next(app);
}
}
}
];
}
@@ -475,42 +481,43 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{nam
}
private loadAppInfo(name: string): Observable<{application: appModels.Application; tree: appModels.ApplicationTree}> {
return Observable.merge(
Observable.fromPromise(services.applications.get(name).then(app => ({app, watchEvent: false}))),
services.applications
.watch({name})
.map(watchEvent => {
if (watchEvent.type === 'DELETED') {
this.onAppDeleted();
}
return {app: watchEvent.application, watchEvent: true};
})
.repeat()
.retryWhen(errors => errors.delay(500)),
this.refreshRequested.filter(e => e !== null).flatMap(() => services.applications.get(name).then(app => ({app, watchEvent: true})))
).flatMap(appInfo => {
const app = appInfo.app;
const fallbackTree: appModels.ApplicationTree = {
nodes: app.status.resources.map(res => ({...res, parentRefs: [], info: [], resourceVersion: '', uid: ''})),
orphanedNodes: []
};
const treeSource = new Observable<{application: appModels.Application; tree: appModels.ApplicationTree}>(observer => {
services.applications
.resourceTree(app.metadata.name)
.then(tree => observer.next({application: app, tree}))
.catch(e => {
observer.next({application: app, tree: fallbackTree});
observer.error(e);
});
return Observable.fromPromise(services.applications.get(name))
.flatMap(app => {
const fallbackTree = {
nodes: app.status.resources.map(res => ({...res, parentRefs: [], info: [], resourceVersion: '', uid: ''})),
orphanedNodes: []
} as appModels.ApplicationTree;
return Observable.combineLatest(
Observable.merge(
Observable.from([app]),
this.appChanged.filter(item => !!item),
AppUtils.handlePageVisibility(() =>
services.applications
.watch({name})
.map(watchEvent => {
if (watchEvent.type === 'DELETED') {
this.onAppDeleted();
}
return watchEvent.application;
})
.repeat()
.retryWhen(errors => errors.delay(500))
)
),
Observable.merge(
Observable.from([fallbackTree]),
services.applications.resourceTree(name).catch(() => fallbackTree),
AppUtils.handlePageVisibility(() =>
services.applications
.watchResourceTree(name)
.repeat()
.retryWhen(errors => errors.delay(500))
)
)
);
})
.repeat()
.retryWhen(errors => errors.delay(1000));
if (appInfo.watchEvent) {
return treeSource;
} else {
return Observable.merge(Observable.from([{application: app, tree: fallbackTree}]), treeSource);
}
});
.filter(([application, tree]) => !!application && !!tree)
.map(([application, tree]) => ({application, tree}));
}
private onAppDeleted() {
@@ -523,8 +530,8 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{nam
latestApp.metadata.labels = app.metadata.labels;
latestApp.metadata.annotations = app.metadata.annotations;
latestApp.spec = app.spec;
await services.applications.update(latestApp);
this.refreshRequested.next({});
const updatedApp = await services.applications.update(latestApp);
this.appChanged.next(updatedApp);
}
private groupAppNodesByKey(application: appModels.Application, tree: appModels.ApplicationTree) {
@@ -593,7 +600,7 @@ Are you sure you want to disable auto-sync and rollback application '${this.prop
await services.applications.update(update);
}
await services.applications.rollback(this.props.match.params.name, revisionHistory.id);
this.refreshRequested.next({});
this.appChanged.next(await services.applications.get(this.props.match.params.name));
this.setRollbackPanelVisible(-1);
}
} catch (e) {
@@ -641,7 +648,7 @@ Are you sure you want to disable auto-sync and rollback application '${this.prop
submit: async (vals, _, close) => {
try {
await services.applications.deleteResource(this.props.match.params.name, resource, !!vals.force);
this.refreshRequested.next({});
this.appChanged.next(await services.applications.get(this.props.match.params.name));
close();
} catch (e) {
this.appContext.apis.notifications.show({

View File

@@ -35,7 +35,7 @@ export const ApplicationSummary = (props: {app: models.Application; updateApp: (
title: 'PROJECT',
view: <a href={'/settings/projects/' + app.spec.project}>{app.spec.project}</a>,
edit: (formApi: FormApi) => (
<DataLoader load={() => services.projects.list().then(projs => projs.map(item => item.metadata.name))}>
<DataLoader load={() => services.projects.list('items.metadata.name').then(projs => projs.map(item => item.metadata.name))}>
{projects => <FormField formApi={formApi} field='spec.project' component={FormSelect} componentProps={{options: projects}} />}
</DataLoader>
)

View File

@@ -146,7 +146,7 @@ export class ApplicationsFilter extends React.Component<ApplicationsFilterProps,
<p>Projects</p>
<ul>
<li>
<DataLoader load={() => services.projects.list()}>
<DataLoader load={() => services.projects.list('items.metadata.name')}>
{projects => {
const projAppCount = new Map<string, number>();
projects.forEach(proj => projAppCount.set(proj.metadata.name, 0));
@@ -168,9 +168,9 @@ export class ApplicationsFilter extends React.Component<ApplicationsFilterProps,
<li>
<TagsInput
placeholder='https://kubernetes.default.svc'
autocomplete={Array.from(new Set(applications.map(app => app.spec.destination.server).filter(item => !!item))).filter(
ns => pref.clustersFilter.indexOf(ns) === -1
)}
autocomplete={Array.from(
new Set(applications.map(app => app.spec.destination.server || app.spec.destination.name).filter(item => !!item))
).filter(ns => pref.clustersFilter.indexOf(ns) === -1)}
tags={pref.clustersFilter}
onChange={selected => onChange({...pref, clustersFilter: selected})}
/>

View File

@@ -21,6 +21,8 @@ import {ApplicationTiles} from './applications-tiles';
require('./applications-list.scss');
const EVENTS_BUFFER_TIMEOUT = 500;
const WATCH_RETRY_TIMEOUT = 500;
const APP_FIELDS = [
'metadata.name',
'metadata.annotations',
@@ -35,37 +37,44 @@ const APP_FIELDS = [
'status.operationState.operation.sync',
'status.summary'
];
const APP_LIST_FIELDS = APP_FIELDS.map(field => `items.${field}`);
const APP_LIST_FIELDS = ['metadata.resourceVersion', ...APP_FIELDS.map(field => `items.${field}`)];
const APP_WATCH_FIELDS = ['result.type', ...APP_FIELDS.map(field => `result.application.${field}`)];
function loadApplications(): Observable<models.Application[]> {
return Observable.fromPromise(services.applications.list([], {fields: APP_LIST_FIELDS})).flatMap(applications =>
Observable.merge(
return Observable.fromPromise(services.applications.list([], {fields: APP_LIST_FIELDS})).flatMap(applicationsList => {
const applications = applicationsList.items;
return Observable.merge(
Observable.from([applications]),
services.applications
.watch(null, {fields: APP_WATCH_FIELDS})
.map(appChange => {
const index = applications.findIndex(item => item.metadata.name === appChange.application.metadata.name);
switch (appChange.type) {
case 'DELETED':
if (index > -1) {
applications.splice(index, 1);
}
break;
default:
if (index > -1) {
applications[index] = appChange.application;
} else {
applications.unshift(appChange.application);
}
break;
}
return {applications, updated: true};
.watch({resourceVersion: applicationsList.metadata.resourceVersion}, {fields: APP_WATCH_FIELDS})
.repeat()
.retryWhen(errors => errors.delay(WATCH_RETRY_TIMEOUT))
// batch events to avoid constant re-rendering and improve UI performance
.bufferTime(EVENTS_BUFFER_TIMEOUT)
.map(appChanges => {
appChanges.forEach(appChange => {
const index = applications.findIndex(item => item.metadata.name === appChange.application.metadata.name);
switch (appChange.type) {
case 'DELETED':
if (index > -1) {
applications.splice(index, 1);
}
break;
default:
if (index > -1) {
applications[index] = appChange.application;
} else {
applications.unshift(appChange.application);
}
break;
}
});
return {applications, updated: appChanges.length > 0};
})
.filter(item => item.updated)
.map(item => item.applications)
)
);
);
});
}
const ViewPref = ({children}: {children: (pref: AppsListPreferences & {page: number; search: string}) => React.ReactNode}) => (
@@ -134,7 +143,7 @@ function filterApps(applications: models.Application[], pref: AppsListPreference
(pref.syncFilter.length === 0 || pref.syncFilter.includes(app.status.sync.status)) &&
(pref.healthFilter.length === 0 || pref.healthFilter.includes(app.status.health.status)) &&
(pref.namespacesFilter.length === 0 || pref.namespacesFilter.some(ns => minimatch(app.spec.destination.namespace, ns))) &&
(pref.clustersFilter.length === 0 || pref.clustersFilter.some(server => minimatch(app.spec.destination.server, server))) &&
(pref.clustersFilter.length === 0 || pref.clustersFilter.some(server => minimatch(app.spec.destination.server || app.spec.destination.name, server))) &&
(pref.labelsFilter.length === 0 || pref.labelsFilter.every(selector => LabelSelector.match(selector, app.metadata.labels)))
);
}
@@ -155,6 +164,21 @@ export const ApplicationsList = (props: RouteComponentProps<{}>) => {
const clusters = React.useMemo(() => services.clusters.list(), []);
const [isAppCreatePending, setAppCreatePending] = React.useState(false);
const loaderRef = React.useRef<DataLoader>();
function refreshApp(appName: string) {
// app refreshing might be done too quickly so that UI might miss it due to event batching
// add refreshing annotation in the UI to improve user experience
if (loaderRef.current) {
const applications = loaderRef.current.getData() as models.Application[];
const app = applications.find(item => item.metadata.name === appName);
if (app) {
AppUtils.setAppRefreshing(app);
loaderRef.current.setData(applications);
}
}
services.applications.get(appName, 'normal');
}
return (
<ClusterCtx.Provider value={clusters}>
<Consumer>
@@ -209,7 +233,8 @@ export const ApplicationsList = (props: RouteComponentProps<{}>) => {
<ViewPref>
{pref => (
<DataLoader
load={() => loadApplications()}
ref={loaderRef}
load={() => AppUtils.handlePageVisibility(() => loadApplications())}
loadingRenderer={() => (
<div className='argo-container'>
<MockupList height={100} marginTop={30} />
@@ -305,14 +330,14 @@ export const ApplicationsList = (props: RouteComponentProps<{}>) => {
<ApplicationTiles
applications={data}
syncApplication={appName => ctx.navigation.goto('.', {syncApp: appName})}
refreshApplication={appName => services.applications.get(appName, 'normal')}
refreshApplication={refreshApp}
deleteApplication={appName => AppUtils.deleteApplication(appName, ctx)}
/>
)) || (
<ApplicationsTable
applications={data}
syncApplication={appName => ctx.navigation.goto('.', {syncApp: appName})}
refreshApplication={appName => services.applications.get(appName, 'normal')}
refreshApplication={refreshApp}
deleteApplication={appName => AppUtils.deleteApplication(appName, ctx)}
/>
)

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import {Checkbox, NotificationType} from 'argo-ui';
import * as React from 'react';
import {Observable, Observer, Subscription} from 'rxjs';
import {COLORS, ErrorNotification, Revision} from '../../shared/components';
import {ContextApis} from '../../shared/context';
import * as appModels from '../../shared/models';
@@ -413,6 +414,15 @@ export function isAppRefreshing(app: appModels.Application) {
return !!(app.metadata.annotations && app.metadata.annotations[appModels.AnnotationRefreshKey]);
}
export function setAppRefreshing(app: appModels.Application) {
if (!app.metadata.annotations) {
app.metadata.annotations = {};
}
if (!app.metadata.annotations[appModels.AnnotationRefreshKey]) {
app.metadata.annotations[appModels.AnnotationRefreshKey] = 'refreshing';
}
}
export function refreshLinkAttrs(app: appModels.Application) {
return {disabled: isAppRefreshing(app)};
}
@@ -507,3 +517,42 @@ export const ApplicationSyncWindowStatusIcon = ({project, state}: {project: stri
</a>
);
};
/**
* Automatically stops and restarts the given observable when page visibility changes.
*/
export function handlePageVisibility<T>(src: () => Observable<T>): Observable<T> {
return new Observable<T>((observer: Observer<T>) => {
let subscription: Subscription;
const ensureUnsubscribed = () => {
if (subscription) {
subscription.unsubscribe();
subscription = null;
}
};
const start = () => {
ensureUnsubscribed();
subscription = src().subscribe((item: T) => observer.next(item), err => observer.error(err), () => observer.complete());
};
if (!document.hidden) {
start();
}
const visibilityChangeSubscription = Observable.fromEvent(document, 'visibilitychange')
// wait until user stop clicking back and forth to avoid restarting observable too often
.debounceTime(500)
.subscribe(() => {
if (document.hidden && subscription) {
ensureUnsubscribed();
} else if (!document.hidden && !subscription) {
start();
}
});
return () => {
visibilityChangeSubscription.unsubscribe();
ensureUnsubscribed();
};
});
}

View File

@@ -20,7 +20,7 @@ function generatePolicy(project: string, role: string, action?: string, object?:
const actions = ['get', 'create', 'update', 'delete', 'sync', 'override'];
export const ProjectRolePoliciesEdit = (props: ProjectRolePoliciesProps) => (
<DataLoader load={() => services.applications.list([props.projName], {fields: ['items.metadata.name']})}>
<DataLoader load={() => services.applications.list([props.projName], {fields: ['items.metadata.name']}).then(list => list.items)}>
{applications => (
<React.Fragment>
<h4>Policy Rules</h4>

View File

@@ -18,13 +18,14 @@ function optionsToSearch(options?: QueryOptions) {
}
export class ApplicationsService {
public list(projects: string[], options?: QueryOptions): Promise<models.Application[]> {
public list(projects: string[], options?: QueryOptions): Promise<models.ApplicationList> {
return requests
.get('/applications')
.query({project: projects, ...optionsToSearch(options)})
.then(res => res.body as models.ApplicationList)
.then(list => {
return (list.items || []).map(app => this.parseAppFields(app));
list.items = (list.items || []).map(app => this.parseAppFields(app));
return list;
});
}
@@ -54,6 +55,10 @@ export class ApplicationsService {
return requests.get(`/applications/${name}/resource-tree`).then(res => res.body as models.ApplicationTree);
}
public watchResourceTree(name: string): Observable<models.ApplicationTree> {
return requests.loadEventSource(`/stream/applications/${name}/resource-tree`).map(data => JSON.parse(data).result as models.ApplicationTree);
}
public managedResources(name: string, options: {id?: models.ResourceID; fields?: string[]} = {}): Promise<models.ResourceDiff[]> {
return requests
.get(`/applications/${name}/managed-resources`)
@@ -96,7 +101,7 @@ export class ApplicationsService {
return requests
.put(`/applications/${app.metadata.name}`)
.send(app)
.then(res => res.body as models.Application);
.then(res => this.parseAppFields(res.body));
}
public create(app: models.Application): Promise<models.Application> {
@@ -114,10 +119,15 @@ export class ApplicationsService {
.then(() => true);
}
public watch(query?: {name: string}, options?: QueryOptions): Observable<models.ApplicationWatchEvent> {
public watch(query?: {name?: string; resourceVersion?: string}, options?: QueryOptions): Observable<models.ApplicationWatchEvent> {
const search = new URLSearchParams();
if (query) {
search.set('name', query.name);
if (query.name) {
search.set('name', query.name);
}
if (query.resourceVersion) {
search.set('resourceVersion', query.resourceVersion);
}
}
if (options) {
const searchOptions = optionsToSearch(options);

View File

@@ -90,9 +90,10 @@ function paramsToProj(params: ProjectParams) {
}
export class ProjectsService {
public list(): Promise<models.Project[]> {
public list(...fields: string[]): Promise<models.Project[]> {
return requests
.get('/projects')
.query({fields: fields.join(',')})
.then(res => res.body as models.ProjectList)
.then(list => list.items || []);
}

View File

@@ -1,6 +1,7 @@
package appstate
import (
"context"
"fmt"
"time"
@@ -76,8 +77,16 @@ func (c *Cache) GetAppResourcesTree(appName string, res *appv1.ApplicationTree)
return err
}
func (c *Cache) OnAppResourcesTreeChanged(ctx context.Context, appName string, callback func() error) error {
return c.Cache.OnUpdated(ctx, appManagedResourcesKey(appName), callback)
}
func (c *Cache) SetAppResourcesTree(appName string, resourcesTree *appv1.ApplicationTree) error {
return c.SetItem(appResourcesTreeKey(appName), resourcesTree, c.appStateCacheExpiration, resourcesTree == nil)
err := c.SetItem(appResourcesTreeKey(appName), resourcesTree, c.appStateCacheExpiration, resourcesTree == nil)
if err != nil {
return err
}
return c.Cache.NotifyUpdated(appManagedResourcesKey(appName))
}
func (c *Cache) SetClusterInfo(server string, info *appv1.ClusterInfo) error {

9
util/cache/cache.go vendored
View File

@@ -1,6 +1,7 @@
package cache
import (
"context"
"fmt"
"math"
"os"
@@ -96,3 +97,11 @@ func (c *Cache) GetItem(key string, item interface{}) error {
key = fmt.Sprintf("%s|%s", key, common.CacheVersion)
return c.client.Get(key, item)
}
func (c *Cache) OnUpdated(ctx context.Context, key string, callback func() error) error {
return c.client.OnUpdated(ctx, fmt.Sprintf("%s|%s", key, common.CacheVersion), callback)
}
func (c *Cache) NotifyUpdated(key string) error {
return c.client.NotifyUpdated(fmt.Sprintf("%s|%s", key, common.CacheVersion))
}

View File

@@ -1,6 +1,7 @@
package cache
import (
"context"
"errors"
"time"
)
@@ -18,4 +19,6 @@ type CacheClient interface {
Set(item *Item) error
Get(key string, obj interface{}) error
Delete(key string) error
OnUpdated(ctx context.Context, key string, callback func() error) error
NotifyUpdated(key string) error
}

View File

@@ -2,6 +2,7 @@ package cache
import (
"bytes"
"context"
"encoding/gob"
"time"
@@ -45,3 +46,11 @@ func (i *InMemoryCache) Delete(key string) error {
func (i *InMemoryCache) Flush() {
i.memCache.Flush()
}
func (i *InMemoryCache) OnUpdated(ctx context.Context, key string, callback func() error) error {
return nil
}
func (i *InMemoryCache) NotifyUpdated(key string) error {
return nil
}

25
util/cache/redis.go vendored
View File

@@ -1,8 +1,10 @@
package cache
import (
"context"
"time"
ioutil "github.com/argoproj/gitops-engine/pkg/utils/io"
rediscache "github.com/go-redis/cache"
"github.com/go-redis/redis"
"github.com/vmihailenco/msgpack"
@@ -10,6 +12,7 @@ import (
func NewRedisCache(client *redis.Client, expiration time.Duration) CacheClient {
return &redisCache{
client: client,
expiration: expiration,
codec: &rediscache.Codec{
Redis: client,
@@ -25,6 +28,7 @@ func NewRedisCache(client *redis.Client, expiration time.Duration) CacheClient {
type redisCache struct {
expiration time.Duration
client *redis.Client
codec *rediscache.Codec
}
@@ -52,6 +56,27 @@ func (r *redisCache) Delete(key string) error {
return r.codec.Delete(key)
}
func (r *redisCache) OnUpdated(ctx context.Context, key string, callback func() error) error {
pubsub := r.client.Subscribe(key)
defer ioutil.Close(pubsub)
ch := pubsub.Channel()
for {
select {
case <-ctx.Done():
return nil
case <-ch:
if err := callback(); err != nil {
return err
}
}
}
}
func (r *redisCache) NotifyUpdated(key string) error {
return r.client.Publish(key, "").Err()
}
type MetricsRegistry interface {
IncRedisRequest(failed bool)
ObserveRedisRequestDuration(duration time.Duration)

View File

@@ -134,7 +134,7 @@ func replaceListSecrets(obj []interface{}, secretValues map[string]string) []int
// https://github.com/dexidp/dex/tree/master/Documentation/connectors
func needsRedirectURI(connectorType string) bool {
switch connectorType {
case "oidc", "saml", "microsoft", "linkedin", "gitlab", "github", "bitbucket-cloud":
case "oidc", "saml", "microsoft", "linkedin", "gitlab", "github", "bitbucket-cloud", "openshift":
return true
}
return false

View File

@@ -54,6 +54,9 @@ Expire-Date: 6m
%commit
`
// Canary marker for GNUPGHOME created by Argo CD
const canaryMarkerFilename = ".argocd-generated"
type PGPKeyID string
func isHexString(s string) bool {
@@ -162,6 +165,39 @@ func writeKeyToFile(keyData string) (string, error) {
return f.Name(), nil
}
// removeKeyRing removes an already initialized keyring from the file system
// This must only be called on container startup, when no gpg-agent is running
// yet, otherwise key generation will fail.
func removeKeyRing(path string) error {
_, err := os.Stat(filepath.Join(path, canaryMarkerFilename))
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("refusing to remove directory %s: it's not initialized by Argo CD", path)
} else {
return err
}
}
rd, err := os.Open(path)
if err != nil {
return err
}
defer rd.Close()
dns, err := rd.Readdirnames(-1)
if err != nil {
return err
}
for _, p := range dns {
if p == "." || p == ".." {
continue
}
err := os.RemoveAll(filepath.Join(path, p))
if err != nil {
return err
}
}
return nil
}
// IsGPGEnabled returns true if GPG feature is enabled
func IsGPGEnabled() bool {
if en := os.Getenv("ARGOCD_GPG_ENABLED"); strings.ToLower(en) == "false" || strings.ToLower(en) == "no" {
@@ -197,8 +233,17 @@ func InitializeGnuPG() error {
return err
}
} else {
// We can't initialize a second time
return fmt.Errorf("%s at %s already initialized, can't initialize again.", common.EnvGnuPGHome, gnuPgHome)
// This usually happens with emptyDir mount on container crash - we need to
// re-initialize key ring.
err = removeKeyRing(gnuPgHome)
if err != nil {
return fmt.Errorf("re-initializing keyring at %s failed: %v", gnuPgHome, err)
}
}
err = ioutil.WriteFile(filepath.Join(gnuPgHome, canaryMarkerFilename), []byte("canary"), 0644)
if err != nil {
return fmt.Errorf("could not create canary: %v", err)
}
f, err := ioutil.TempFile("", "gpg-key-recipe")

View File

@@ -5,10 +5,12 @@ import (
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/test"
@@ -60,10 +62,20 @@ func Test_GPG_InitializeGnuPG(t *testing.T) {
assert.Len(t, keys, 1)
assert.Equal(t, keys[0].Trust, "ultimate")
// Second run should return error
// During unit-tests, we need to also kill gpg-agent so we can create a new key.
// In real world scenario -- i.e. container crash -- gpg-agent is not running yet.
cmd := exec.Command("gpgconf", "--kill", "gpg-agent")
cmd.Env = []string{fmt.Sprintf("GNUPGHOME=%s", p)}
err = cmd.Run()
require.NoError(t, err)
// Second run should not return error
err = InitializeGnuPG()
assert.Error(t, err)
assert.Contains(t, err.Error(), "already initialized")
require.NoError(t, err)
keys, err = GetInstalledPGPKeys(nil)
assert.NoError(t, err)
assert.Len(t, keys, 1)
assert.Equal(t, keys[0].Trust, "ultimate")
// GNUPGHOME is a file - we need to error out
f, err := ioutil.TempFile("", "gpg-test")