mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-02-20 01:28:45 +01:00
Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc43124058 | ||
|
|
b6af657295 | ||
|
|
a3624a3f20 | ||
|
|
01ae20d1b3 | ||
|
|
89ef3563db | ||
|
|
831e4525c3 | ||
|
|
f8d6665c67 | ||
|
|
0680ddbdf9 | ||
|
|
ad36916ec4 | ||
|
|
af54ef8db5 | ||
|
|
68606c6caf | ||
|
|
6a8cb6eff0 | ||
|
|
d03ccf305c | ||
|
|
7f45c9e093 | ||
|
|
449e6939b2 | ||
|
|
99aab9a5f3 | ||
|
|
347f221adb | ||
|
|
1fcbe3f511 | ||
|
|
d417417c21 | ||
|
|
e7f98814a9 | ||
|
|
3f708b8b14 | ||
|
|
7bc333d193 | ||
|
|
e3b1d9327d | ||
|
|
deb07ee698 | ||
|
|
435989c07e | ||
|
|
2503eb32af | ||
|
|
be57dfe1fa | ||
|
|
e99c8b754b | ||
|
|
2076b4f73c | ||
|
|
5c595d8410 | ||
|
|
8340e1e43f | ||
|
|
1cddb8e607 | ||
|
|
262c8fa529 | ||
|
|
97a49a24cc | ||
|
|
a9a8d0e45f | ||
|
|
92de225ce5 | ||
|
|
a713e5023a | ||
|
|
ec60abd4d8 | ||
|
|
c6d9d50ee9 | ||
|
|
7244b8b40f | ||
|
|
8e81bb6c80 | ||
|
|
3bc2e1ae4c | ||
|
|
61f63f35ae | ||
|
|
5eb1f9bd16 | ||
|
|
4149f484bf | ||
|
|
0b2895977e | ||
|
|
99b30a87a6 | ||
|
|
9fc6ec116d | ||
|
|
f7f553f675 | ||
|
|
a9d9d07edd | ||
|
|
0f083c9e58 | ||
|
|
5392ca7e79 | ||
|
|
243ecc2f25 | ||
|
|
425b4087f3 | ||
|
|
74a367d10e |
1
USERS.md
1
USERS.md
@@ -41,6 +41,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Beez Innovation Labs](https://www.beezlabs.com/)
|
||||
1. [Bedag Informatik AG](https://www.bedag.ch/)
|
||||
1. [Beleza Na Web](https://www.belezanaweb.com.br/)
|
||||
1. [Believable Bots](https://believablebots.io)
|
||||
1. [BigPanda](https://bigpanda.io)
|
||||
1. [BioBox Analytics](https://biobox.io)
|
||||
1. [BMW Group](https://www.bmwgroup.com/)
|
||||
|
||||
@@ -994,7 +994,7 @@ func appSyncEnabledForNextStep(appset *argov1alpha1.ApplicationSet, app argov1al
|
||||
}
|
||||
|
||||
func progressiveSyncsRollingSyncStrategyEnabled(appset *argov1alpha1.ApplicationSet) bool {
|
||||
return appset.Spec.Strategy != nil && appset.Spec.Strategy.RollingSync != nil && appset.Spec.Strategy.Type == "RollingSync"
|
||||
return appset.Spec.Strategy != nil && appset.Spec.Strategy.RollingSync != nil && appset.Spec.Strategy.Type == "RollingSync" && len(appset.Spec.Strategy.RollingSync.Steps) > 0
|
||||
}
|
||||
|
||||
func isApplicationHealthy(app argov1alpha1.Application) bool {
|
||||
@@ -1017,6 +1017,16 @@ func statusStrings(app argov1alpha1.Application) (string, string, string) {
|
||||
return healthStatusString, syncStatusString, operationPhaseString
|
||||
}
|
||||
|
||||
func getAppStep(appName string, appStepMap map[string]int) int {
|
||||
// if an application is not selected by any match expression, it defaults to step -1
|
||||
step := -1
|
||||
if appStep, ok := appStepMap[appName]; ok {
|
||||
// 1-based indexing
|
||||
step = appStep + 1
|
||||
}
|
||||
return step
|
||||
}
|
||||
|
||||
// check the status of each Application's status and promote Applications to the next status if needed
|
||||
func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx context.Context, logCtx *log.Entry, applicationSet *argov1alpha1.ApplicationSet, applications []argov1alpha1.Application, appStepMap map[string]int) ([]argov1alpha1.ApplicationSetApplicationStatus, error) {
|
||||
now := metav1.Now()
|
||||
@@ -1036,7 +1046,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
|
||||
LastTransitionTime: &now,
|
||||
Message: "No Application status found, defaulting status to Waiting.",
|
||||
Status: "Waiting",
|
||||
Step: fmt.Sprint(appStepMap[app.Name] + 1),
|
||||
Step: fmt.Sprint(getAppStep(app.Name, appStepMap)),
|
||||
TargetRevisions: app.Status.GetRevisions(),
|
||||
}
|
||||
} else {
|
||||
@@ -1061,7 +1071,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
|
||||
currentAppStatus.LastTransitionTime = &now
|
||||
currentAppStatus.Status = "Waiting"
|
||||
currentAppStatus.Message = "Application has pending changes, setting status to Waiting."
|
||||
currentAppStatus.Step = fmt.Sprint(appStepMap[currentAppStatus.Application] + 1)
|
||||
currentAppStatus.Step = fmt.Sprint(getAppStep(currentAppStatus.Application, appStepMap))
|
||||
currentAppStatus.TargetRevisions = app.Status.GetRevisions()
|
||||
}
|
||||
|
||||
@@ -1079,14 +1089,14 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
|
||||
currentAppStatus.LastTransitionTime = &now
|
||||
currentAppStatus.Status = "Progressing"
|
||||
currentAppStatus.Message = "Application resource completed a sync successfully, updating status from Pending to Progressing."
|
||||
currentAppStatus.Step = fmt.Sprint(appStepMap[currentAppStatus.Application] + 1)
|
||||
currentAppStatus.Step = fmt.Sprint(getAppStep(currentAppStatus.Application, appStepMap))
|
||||
}
|
||||
} else if operationPhaseString == "Running" || healthStatusString == "Progressing" {
|
||||
logCtx.Infof("Application %v has entered Progressing status, updating its ApplicationSet status to Progressing", app.Name)
|
||||
currentAppStatus.LastTransitionTime = &now
|
||||
currentAppStatus.Status = "Progressing"
|
||||
currentAppStatus.Message = "Application resource became Progressing, updating status from Pending to Progressing."
|
||||
currentAppStatus.Step = fmt.Sprint(appStepMap[currentAppStatus.Application] + 1)
|
||||
currentAppStatus.Step = fmt.Sprint(getAppStep(currentAppStatus.Application, appStepMap))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1095,7 +1105,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
|
||||
currentAppStatus.LastTransitionTime = &now
|
||||
currentAppStatus.Status = healthStatusString
|
||||
currentAppStatus.Message = "Application resource is already Healthy, updating status from Waiting to Healthy."
|
||||
currentAppStatus.Step = fmt.Sprint(appStepMap[currentAppStatus.Application] + 1)
|
||||
currentAppStatus.Step = fmt.Sprint(getAppStep(currentAppStatus.Application, appStepMap))
|
||||
}
|
||||
|
||||
if currentAppStatus.Status == "Progressing" && isApplicationHealthy(app) {
|
||||
@@ -1103,7 +1113,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx con
|
||||
currentAppStatus.LastTransitionTime = &now
|
||||
currentAppStatus.Status = healthStatusString
|
||||
currentAppStatus.Message = "Application resource became Healthy, updating status from Progressing to Healthy."
|
||||
currentAppStatus.Step = fmt.Sprint(appStepMap[currentAppStatus.Application] + 1)
|
||||
currentAppStatus.Step = fmt.Sprint(getAppStep(currentAppStatus.Application, appStepMap))
|
||||
}
|
||||
|
||||
appStatuses = append(appStatuses, currentAppStatus)
|
||||
@@ -1124,14 +1134,12 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
|
||||
appStatuses := make([]argov1alpha1.ApplicationSetApplicationStatus, 0, len(applicationSet.Status.ApplicationStatus))
|
||||
|
||||
// if we have no RollingUpdate steps, clear out the existing ApplicationStatus entries
|
||||
if applicationSet.Spec.Strategy != nil && applicationSet.Spec.Strategy.Type != "" && applicationSet.Spec.Strategy.Type != "AllAtOnce" {
|
||||
if progressiveSyncsRollingSyncStrategyEnabled(applicationSet) {
|
||||
updateCountMap := []int{}
|
||||
totalCountMap := []int{}
|
||||
|
||||
length := 0
|
||||
if progressiveSyncsRollingSyncStrategyEnabled(applicationSet) {
|
||||
length = len(applicationSet.Spec.Strategy.RollingSync.Steps)
|
||||
}
|
||||
length := len(applicationSet.Spec.Strategy.RollingSync.Steps)
|
||||
|
||||
for s := 0; s < length; s++ {
|
||||
updateCountMap = append(updateCountMap, 0)
|
||||
totalCountMap = append(totalCountMap, 0)
|
||||
@@ -1141,10 +1149,8 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
|
||||
for _, appStatus := range applicationSet.Status.ApplicationStatus {
|
||||
totalCountMap[appStepMap[appStatus.Application]] += 1
|
||||
|
||||
if progressiveSyncsRollingSyncStrategyEnabled(applicationSet) {
|
||||
if appStatus.Status == "Pending" || appStatus.Status == "Progressing" {
|
||||
updateCountMap[appStepMap[appStatus.Application]] += 1
|
||||
}
|
||||
if appStatus.Status == "Pending" || appStatus.Status == "Progressing" {
|
||||
updateCountMap[appStepMap[appStatus.Application]] += 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1169,7 +1175,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
|
||||
|
||||
if updateCountMap[appStepMap[appStatus.Application]] >= maxUpdateVal {
|
||||
maxUpdateAllowed = false
|
||||
logCtx.Infof("Application %v is not allowed to update yet, %v/%v Applications already updating in step %v in AppSet %v", appStatus.Application, updateCountMap[appStepMap[appStatus.Application]], maxUpdateVal, appStepMap[appStatus.Application]+1, applicationSet.Name)
|
||||
logCtx.Infof("Application %v is not allowed to update yet, %v/%v Applications already updating in step %v in AppSet %v", appStatus.Application, updateCountMap[appStepMap[appStatus.Application]], maxUpdateVal, getAppStep(appStatus.Application, appStepMap), applicationSet.Name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1178,7 +1184,7 @@ func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress
|
||||
appStatus.LastTransitionTime = &now
|
||||
appStatus.Status = "Pending"
|
||||
appStatus.Message = "Application moved to Pending status, watching for the Application resource to start Progressing."
|
||||
appStatus.Step = fmt.Sprint(appStepMap[appStatus.Application] + 1)
|
||||
appStatus.Step = fmt.Sprint(getAppStep(appStatus.Application, appStepMap))
|
||||
|
||||
updateCountMap[appStepMap[appStatus.Application]] += 1
|
||||
}
|
||||
|
||||
@@ -3813,8 +3813,17 @@ func TestBuildAppSyncMap(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -3830,8 +3839,17 @@ func TestBuildAppSyncMap(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -3853,8 +3871,17 @@ func TestBuildAppSyncMap(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -3876,8 +3903,17 @@ func TestBuildAppSyncMap(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
@@ -3945,8 +3981,17 @@ func TestBuildAppSyncMap(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
@@ -4014,8 +4059,17 @@ func TestBuildAppSyncMap(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
@@ -4083,8 +4137,17 @@ func TestBuildAppSyncMap(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
@@ -4152,8 +4215,17 @@ func TestBuildAppSyncMap(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
@@ -4221,8 +4293,17 @@ func TestBuildAppSyncMap(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
@@ -4400,8 +4481,17 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -4417,8 +4507,17 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -4440,6 +4539,9 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
appStepMap: map[string]int{
|
||||
"app1": 0,
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
@@ -4459,8 +4561,17 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{},
|
||||
@@ -4483,6 +4594,9 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
appStepMap: map[string]int{
|
||||
"app1": 0,
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
@@ -4502,8 +4616,17 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
@@ -4535,6 +4658,9 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
appStepMap: map[string]int{
|
||||
"app1": 0,
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
@@ -4554,8 +4680,17 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
@@ -4601,6 +4736,10 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
appStepMap: map[string]int{
|
||||
"app1": 0,
|
||||
"app2-multisource": 0,
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
@@ -4627,8 +4766,17 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
@@ -4655,6 +4803,9 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
appStepMap: map[string]int{
|
||||
"app1": 0,
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
@@ -4674,8 +4825,17 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
@@ -4708,6 +4868,9 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
appStepMap: map[string]int{
|
||||
"app1": 0,
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
@@ -4727,8 +4890,17 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
@@ -4761,6 +4933,9 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
appStepMap: map[string]int{
|
||||
"app1": 0,
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
@@ -4780,8 +4955,17 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
@@ -4814,6 +4998,9 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
appStepMap: map[string]int{
|
||||
"app1": 0,
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
@@ -4833,8 +5020,17 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -4883,8 +5079,17 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
@@ -4933,6 +5138,9 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
appStepMap: map[string]int{
|
||||
"app1": 0,
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
@@ -4952,8 +5160,17 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
@@ -5002,6 +5219,9 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
appStepMap: map[string]int{
|
||||
"app1": 0,
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
@@ -5021,8 +5241,17 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
@@ -5070,6 +5299,9 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
appStepMap: map[string]int{
|
||||
"app1": 0,
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
@@ -5089,8 +5321,17 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSetSpec{
|
||||
Strategy: &v1alpha1.ApplicationSetStrategy{
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{},
|
||||
Type: "RollingSync",
|
||||
RollingSync: &v1alpha1.ApplicationSetRolloutStrategy{
|
||||
Steps: []v1alpha1.ApplicationSetRolloutStep{
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
{
|
||||
MatchExpressions: []v1alpha1.ApplicationMatchExpression{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1alpha1.ApplicationSetStatus{
|
||||
@@ -5130,6 +5371,9 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
appStepMap: map[string]int{
|
||||
"app1": 0,
|
||||
},
|
||||
expectedAppStatus: []v1alpha1.ApplicationSetApplicationStatus{
|
||||
{
|
||||
Application: "app1",
|
||||
|
||||
@@ -168,7 +168,7 @@ func (g *PullRequestGenerator) selectServiceProvider(ctx context.Context, genera
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching Secret Bearer token: %w", err)
|
||||
}
|
||||
return pullrequest.NewBitbucketServiceBearerToken(ctx, providerConfig.API, appToken, providerConfig.Project, providerConfig.Repo, g.scmRootCAPath, providerConfig.Insecure, caCerts)
|
||||
return pullrequest.NewBitbucketServiceBearerToken(ctx, appToken, providerConfig.API, providerConfig.Project, providerConfig.Repo, g.scmRootCAPath, providerConfig.Insecure, caCerts)
|
||||
} else if providerConfig.BasicAuth != nil {
|
||||
password, err := utils.GetSecretRef(ctx, g.client, providerConfig.BasicAuth.PasswordRef, applicationSetInfo.Namespace)
|
||||
if err != nil {
|
||||
|
||||
@@ -19,7 +19,7 @@ type BitbucketCloudPullRequest struct {
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Source BitbucketCloudPullRequestSource `json:"source"`
|
||||
Author string `json:"author"`
|
||||
Author BitbucketCloudPullRequestAuthor `json:"author"`
|
||||
}
|
||||
|
||||
type BitbucketCloudPullRequestSource struct {
|
||||
@@ -35,6 +35,11 @@ type BitbucketCloudPullRequestSourceCommit struct {
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
|
||||
// Also have display_name and uuid, but don't plan to use them.
|
||||
type BitbucketCloudPullRequestAuthor struct {
|
||||
Nickname string `json:"nickname"`
|
||||
}
|
||||
|
||||
type PullRequestResponse struct {
|
||||
Page int32 `json:"page"`
|
||||
Size int32 `json:"size"`
|
||||
@@ -134,7 +139,7 @@ func (b *BitbucketCloudService) List(_ context.Context) ([]*PullRequest, error)
|
||||
Title: pull.Title,
|
||||
Branch: pull.Source.Branch.Name,
|
||||
HeadSHA: pull.Source.Commit.Hash,
|
||||
Author: pull.Author,
|
||||
Author: pull.Author.Nickname,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,9 @@ func defaultHandlerCloud(t *testing.T) func(http.ResponseWriter, *http.Request)
|
||||
"hash": "1a8dd249c04a"
|
||||
}
|
||||
},
|
||||
"author": "testName"
|
||||
"author": {
|
||||
"nickname": "testName"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`)
|
||||
@@ -154,7 +156,9 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
|
||||
"hash": "1a8dd249c04a"
|
||||
}
|
||||
},
|
||||
"author": "testName"
|
||||
"author": {
|
||||
"nickname": "testName"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 102,
|
||||
@@ -168,7 +172,9 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
|
||||
"hash": "4cf807e67a6d"
|
||||
}
|
||||
},
|
||||
"author": "testName"
|
||||
"author": {
|
||||
"nickname": "testName"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`, r.Host))
|
||||
@@ -191,7 +197,9 @@ func TestListPullRequestPaginationCloud(t *testing.T) {
|
||||
"hash": "6344d9623e3b"
|
||||
}
|
||||
},
|
||||
"author": "testName"
|
||||
"author": {
|
||||
"nickname": "testName"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`, r.Host))
|
||||
@@ -339,7 +347,9 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
|
||||
"hash": "1a8dd249c04a"
|
||||
}
|
||||
},
|
||||
"author": "testName"
|
||||
"author": {
|
||||
"nickname": "testName"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 200,
|
||||
@@ -353,7 +363,9 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
|
||||
"hash": "4cf807e67a6d"
|
||||
}
|
||||
},
|
||||
"author": "testName"
|
||||
"author": {
|
||||
"nickname": "testName"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`, r.Host))
|
||||
@@ -376,7 +388,9 @@ func TestListPullRequestBranchMatchCloud(t *testing.T) {
|
||||
"hash": "6344d9623e3b"
|
||||
}
|
||||
},
|
||||
"author": "testName"
|
||||
"author": {
|
||||
"nickname": "testName"
|
||||
}
|
||||
}
|
||||
]
|
||||
}`, r.Host))
|
||||
|
||||
10
assets/swagger.json
generated
10
assets/swagger.json
generated
@@ -4719,6 +4719,9 @@
|
||||
"impersonationEnabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"installationID": {
|
||||
"type": "string"
|
||||
},
|
||||
"kustomizeOptions": {
|
||||
"$ref": "#/definitions/v1alpha1KustomizeOptions"
|
||||
},
|
||||
@@ -6084,7 +6087,7 @@
|
||||
"properties": {
|
||||
"defaultServiceAccount": {
|
||||
"type": "string",
|
||||
"title": "ServiceAccountName to be used for impersonation during the sync operation"
|
||||
"title": "DefaultServiceAccount to be used for impersonation during the sync operation"
|
||||
},
|
||||
"namespace": {
|
||||
"description": "Namespace specifies the target namespace for the application's resources.",
|
||||
@@ -9167,6 +9170,11 @@
|
||||
"description": "SyncOperation contains details about a sync operation.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"autoHealAttemptsCount": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"title": "SelfHealAttemptsCount contains the number of auto-heal attempts"
|
||||
},
|
||||
"dryRun": {
|
||||
"type": "boolean",
|
||||
"title": "DryRun specifies to perform a `kubectl apply --dry-run` without actually performing the sync"
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/redis/go-redis/v9"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
|
||||
@@ -24,6 +25,7 @@ import (
|
||||
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
|
||||
"github.com/argoproj/argo-cd/v2/pkg/ratelimiter"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
|
||||
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
|
||||
@@ -56,6 +58,9 @@ func NewCommand() *cobra.Command {
|
||||
repoServerAddress string
|
||||
repoServerTimeoutSeconds int
|
||||
selfHealTimeoutSeconds int
|
||||
selfHealBackoffTimeoutSeconds int
|
||||
selfHealBackoffFactor int
|
||||
selfHealBackoffCapSeconds int
|
||||
statusProcessors int
|
||||
operationProcessors int
|
||||
glogLevel int
|
||||
@@ -78,6 +83,9 @@ func NewCommand() *cobra.Command {
|
||||
enableDynamicClusterDistribution bool
|
||||
serverSideDiff bool
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
|
||||
|
||||
// argocd k8s event logging flag
|
||||
enableK8sEvent []string
|
||||
)
|
||||
command := cobra.Command{
|
||||
Use: cliName,
|
||||
@@ -152,6 +160,14 @@ func NewCommand() *cobra.Command {
|
||||
kubectl := kubeutil.NewKubectl()
|
||||
clusterSharding, err := sharding.GetClusterSharding(kubeClient, settingsMgr, shardingAlgorithm, enableDynamicClusterDistribution)
|
||||
errors.CheckError(err)
|
||||
var selfHealBackoff *wait.Backoff
|
||||
if selfHealBackoffTimeoutSeconds != 0 {
|
||||
selfHealBackoff = &wait.Backoff{
|
||||
Duration: time.Duration(selfHealBackoffTimeoutSeconds) * time.Second,
|
||||
Factor: float64(selfHealBackoffFactor),
|
||||
Cap: time.Duration(selfHealBackoffCapSeconds) * time.Second,
|
||||
}
|
||||
}
|
||||
appController, err = controller.NewApplicationController(
|
||||
namespace,
|
||||
settingsMgr,
|
||||
@@ -164,6 +180,7 @@ func NewCommand() *cobra.Command {
|
||||
hardResyncDuration,
|
||||
time.Duration(appResyncJitter)*time.Second,
|
||||
time.Duration(selfHealTimeoutSeconds)*time.Second,
|
||||
selfHealBackoff,
|
||||
time.Duration(repoErrorGracePeriod)*time.Second,
|
||||
metricsPort,
|
||||
metricsCacheExpiration,
|
||||
@@ -177,6 +194,7 @@ func NewCommand() *cobra.Command {
|
||||
serverSideDiff,
|
||||
enableDynamicClusterDistribution,
|
||||
ignoreNormalizerOpts,
|
||||
enableK8sEvent,
|
||||
)
|
||||
errors.CheckError(err)
|
||||
cacheutil.CollectMetrics(redisClient, appController.GetMetricsServer())
|
||||
@@ -226,7 +244,10 @@ func NewCommand() *cobra.Command {
|
||||
command.Flags().IntVar(&glogLevel, "gloglevel", 0, "Set the glog logging level")
|
||||
command.Flags().IntVar(&metricsPort, "metrics-port", common.DefaultPortArgoCDMetrics, "Start metrics server on given port")
|
||||
command.Flags().DurationVar(&metricsCacheExpiration, "metrics-cache-expiration", env.ParseDurationFromEnv("ARGOCD_APPLICATION_CONTROLLER_METRICS_CACHE_EXPIRATION", 0*time.Second, 0, math.MaxInt64), "Prometheus metrics cache expiration (disabled by default. e.g. 24h0m0s)")
|
||||
command.Flags().IntVar(&selfHealTimeoutSeconds, "self-heal-timeout-seconds", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_TIMEOUT_SECONDS", 5, 0, math.MaxInt32), "Specifies timeout between application self heal attempts")
|
||||
command.Flags().IntVar(&selfHealTimeoutSeconds, "self-heal-timeout-seconds", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_TIMEOUT_SECONDS", 0, 0, math.MaxInt32), "Specifies timeout between application self heal attempts")
|
||||
command.Flags().IntVar(&selfHealBackoffTimeoutSeconds, "self-heal-backoff-timeout-seconds", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_TIMEOUT_SECONDS", 2, 0, math.MaxInt32), "Specifies initial timeout of exponential backoff between self heal attempts")
|
||||
command.Flags().IntVar(&selfHealBackoffFactor, "self-heal-backoff-factor", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_FACTOR", 3, 0, math.MaxInt32), "Specifies factor of exponential timeout between application self heal attempts")
|
||||
command.Flags().IntVar(&selfHealBackoffCapSeconds, "self-heal-backoff-cap-seconds", env.ParseNumFromEnv("ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_CAP_SECONDS", 300, 0, math.MaxInt32), "Specifies max timeout of exponential backoff between application self heal attempts")
|
||||
command.Flags().Int64Var(&kubectlParallelismLimit, "kubectl-parallelism-limit", env.ParseInt64FromEnv("ARGOCD_APPLICATION_CONTROLLER_KUBECTL_PARALLELISM_LIMIT", 20, 0, math.MaxInt64), "Number of allowed concurrent kubectl fork/execs. Any value less than 1 means no limit.")
|
||||
command.Flags().BoolVar(&repoServerPlaintext, "repo-server-plaintext", env.ParseBoolFromEnv("ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT", false), "Disable TLS on connections to repo server")
|
||||
command.Flags().BoolVar(&repoServerStrictTLS, "repo-server-strict-tls", env.ParseBoolFromEnv("ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_STRICT_TLS", false), "Whether to use strict validation of the TLS cert presented by the repo server")
|
||||
@@ -251,6 +272,9 @@ func NewCommand() *cobra.Command {
|
||||
command.Flags().BoolVar(&enableDynamicClusterDistribution, "dynamic-cluster-distribution-enabled", env.ParseBoolFromEnv(common.EnvEnableDynamicClusterDistribution, false), "Enables dynamic cluster distribution.")
|
||||
command.Flags().BoolVar(&serverSideDiff, "server-side-diff-enabled", env.ParseBoolFromEnv(common.EnvServerSideDiff, false), "Feature flag to enable ServerSide diff. Default (\"false\")")
|
||||
command.Flags().DurationVar(&ignoreNormalizerOpts.JQExecutionTimeout, "ignore-normalizer-jq-execution-timeout-seconds", env.ParseDurationFromEnv("ARGOCD_IGNORE_NORMALIZER_JQ_TIMEOUT", 0*time.Second, 0, math.MaxInt64), "Set ignore normalizer JQ execution timeout")
|
||||
// argocd k8s event logging flag
|
||||
command.Flags().StringSliceVar(&enableK8sEvent, "enable-k8s-event", env.StringsFromEnv("ARGOCD_ENABLE_K8S_EVENT", argo.DefaultEnableEventList(), ","), "Enable ArgoCD to use k8s event. For disabling all events, set the value as `none`. (e.g --enable-k8s-event=none), For enabling specific events, set the value as `event reason`. (e.g --enable-k8s-event=StatusRefreshed,ResourceCreated)")
|
||||
|
||||
cacheSource = appstatecache.AddCacheFlagsToCmd(&command, cacheutil.Options{
|
||||
OnClientCreated: func(client *redis.Client) {
|
||||
redisClient = client
|
||||
|
||||
@@ -163,6 +163,7 @@ func NewCommand() *cobra.Command {
|
||||
}()
|
||||
|
||||
go ctrl.Run(ctx, processorsCount)
|
||||
<-ctx.Done()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
reposervercache "github.com/argoproj/argo-cd/v2/reposerver/cache"
|
||||
"github.com/argoproj/argo-cd/v2/server"
|
||||
servercache "github.com/argoproj/argo-cd/v2/server/cache"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
|
||||
"github.com/argoproj/argo-cd/v2/util/cli"
|
||||
"github.com/argoproj/argo-cd/v2/util/dex"
|
||||
@@ -91,6 +92,9 @@ func NewCommand() *cobra.Command {
|
||||
scmRootCAPath string
|
||||
allowedScmProviders []string
|
||||
enableScmProviders bool
|
||||
|
||||
// argocd k8s event logging flag
|
||||
enableK8sEvent []string
|
||||
)
|
||||
command := &cobra.Command{
|
||||
Use: cliName,
|
||||
@@ -151,6 +155,7 @@ func NewCommand() *cobra.Command {
|
||||
controllerClient, err := client.New(config, client.Options{Scheme: scheme})
|
||||
errors.CheckError(err)
|
||||
controllerClient = client.NewDryRunClient(controllerClient)
|
||||
controllerClient = client.NewNamespacedClient(controllerClient, namespace)
|
||||
|
||||
// Load CA information to use for validating connections to the
|
||||
// repository server, if strict TLS validation was requested.
|
||||
@@ -229,6 +234,7 @@ func NewCommand() *cobra.Command {
|
||||
ApplicationNamespaces: applicationNamespaces,
|
||||
EnableProxyExtension: enableProxyExtension,
|
||||
WebhookParallelism: webhookParallelism,
|
||||
EnableK8sEvent: enableK8sEvent,
|
||||
}
|
||||
|
||||
appsetOpts := server.ApplicationSetOpts{
|
||||
@@ -303,6 +309,7 @@ func NewCommand() *cobra.Command {
|
||||
command.Flags().StringSliceVar(&applicationNamespaces, "application-namespaces", env.StringsFromEnv("ARGOCD_APPLICATION_NAMESPACES", []string{}, ","), "List of additional namespaces where application resources can be managed in")
|
||||
command.Flags().BoolVar(&enableProxyExtension, "enable-proxy-extension", env.ParseBoolFromEnv("ARGOCD_SERVER_ENABLE_PROXY_EXTENSION", false), "Enable Proxy Extension feature")
|
||||
command.Flags().IntVar(&webhookParallelism, "webhook-parallelism-limit", env.ParseNumFromEnv("ARGOCD_SERVER_WEBHOOK_PARALLELISM_LIMIT", 50, 1, 1000), "Number of webhook requests processed concurrently")
|
||||
command.Flags().StringSliceVar(&enableK8sEvent, "enable-k8s-event", env.StringsFromEnv("ARGOCD_ENABLE_K8S_EVENT", argo.DefaultEnableEventList(), ","), "Enable ArgoCD to use k8s event. For disabling all events, set the value as `none`. (e.g --enable-k8s-event=none), For enabling specific events, set the value as `event reason`. (e.g --enable-k8s-event=StatusRefreshed,ResourceCreated)")
|
||||
|
||||
// Flags related to the applicationSet component.
|
||||
command.Flags().StringVar(&scmRootCAPath, "appset-scm-root-ca-path", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_SCM_ROOT_CA_PATH", ""), "Provide Root CA Path for self-signed TLS Certificates")
|
||||
|
||||
@@ -379,7 +379,8 @@ func NewApplicationGetCommand(clientOpts *argocdclient.ClientOptions) *cobra.Com
|
||||
})
|
||||
errors.CheckError(err)
|
||||
|
||||
if app.Spec.HasMultipleSources() {
|
||||
// check for source position if --show-params is set
|
||||
if app.Spec.HasMultipleSources() && showParams {
|
||||
if sourcePosition <= 0 {
|
||||
errors.CheckError(fmt.Errorf("Source position should be specified and must be greater than 0 for applications with multiple sources"))
|
||||
}
|
||||
@@ -585,8 +586,8 @@ func printAppSummaryTable(app *argoappv1.Application, appURL string, windows *ar
|
||||
var status string
|
||||
var allow, deny, inactiveAllows bool
|
||||
if windows.HasWindows() {
|
||||
active := windows.Active()
|
||||
if active.HasWindows() {
|
||||
active, err := windows.Active()
|
||||
if err == nil && active.HasWindows() {
|
||||
for _, w := range *active {
|
||||
if w.Kind == "deny" {
|
||||
deny = true
|
||||
@@ -595,13 +596,14 @@ func printAppSummaryTable(app *argoappv1.Application, appURL string, windows *ar
|
||||
}
|
||||
}
|
||||
}
|
||||
if windows.InactiveAllows().HasWindows() {
|
||||
inactiveAllowWindows, err := windows.InactiveAllows()
|
||||
if err == nil && inactiveAllowWindows.HasWindows() {
|
||||
inactiveAllows = true
|
||||
}
|
||||
|
||||
s := windows.CanSync(true)
|
||||
if deny || !deny && !allow && inactiveAllows {
|
||||
if s {
|
||||
s, err := windows.CanSync(true)
|
||||
if err == nil && s {
|
||||
status = "Manual Allowed"
|
||||
} else {
|
||||
status = "Sync Denied"
|
||||
@@ -1374,7 +1376,7 @@ func groupObjsForDiff(resources *application.ManagedResourcesResponse, objs map[
|
||||
}
|
||||
if local, ok := objs[key]; ok || live != nil {
|
||||
if local != nil && !kube.IsCRD(local) {
|
||||
err = resourceTracking.SetAppInstance(local, argoSettings.AppLabelKey, appName, namespace, argoappv1.TrackingMethod(argoSettings.GetTrackingMethod()))
|
||||
err = resourceTracking.SetAppInstance(local, argoSettings.AppLabelKey, appName, namespace, argoappv1.TrackingMethod(argoSettings.GetTrackingMethod()), argoSettings.GetInstallationID())
|
||||
errors.CheckError(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
runtimeUtil "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
cache2 "k8s.io/client-go/tools/cache"
|
||||
@@ -48,6 +49,7 @@ type forwardCacheClient struct {
|
||||
err error
|
||||
redisHaProxyName string
|
||||
redisName string
|
||||
redisPassword string
|
||||
}
|
||||
|
||||
func (c *forwardCacheClient) doLazy(action func(client cache.CacheClient) error) error {
|
||||
@@ -64,7 +66,7 @@ func (c *forwardCacheClient) doLazy(action func(client cache.CacheClient) error)
|
||||
return
|
||||
}
|
||||
|
||||
redisClient := redis.NewClient(&redis.Options{Addr: fmt.Sprintf("localhost:%d", redisPort)})
|
||||
redisClient := redis.NewClient(&redis.Options{Addr: fmt.Sprintf("localhost:%d", redisPort), Password: c.redisPassword})
|
||||
c.client = cache.NewRedisCache(redisClient, time.Hour, c.compression)
|
||||
})
|
||||
if c.err != nil {
|
||||
@@ -201,7 +203,7 @@ func MaybeStartLocalServer(ctx context.Context, clientOpts *apiclient.ClientOpti
|
||||
}
|
||||
|
||||
// get rid of logging error handler
|
||||
runtime.ErrorHandlers = runtime.ErrorHandlers[1:]
|
||||
runtimeUtil.ErrorHandlers = runtimeUtil.ErrorHandlers[1:]
|
||||
cli.SetLogLevel(log.ErrorLevel.String())
|
||||
log.SetLevel(log.ErrorLevel)
|
||||
os.Setenv(v1alpha1.EnvVarFakeInClusterConfig, "true")
|
||||
@@ -236,7 +238,14 @@ func MaybeStartLocalServer(ctx context.Context, clientOpts *apiclient.ClientOpti
|
||||
return fmt.Errorf("error creating kubernetes dynamic clientset: %w", err)
|
||||
}
|
||||
|
||||
controllerClientset, err := client.New(restConfig, client.Options{})
|
||||
scheme := runtime.NewScheme()
|
||||
err = v1alpha1.AddToScheme(scheme)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error adding argo resources to scheme: %w", err)
|
||||
}
|
||||
controllerClientset, err := client.New(restConfig, client.Options{
|
||||
Scheme: scheme,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating kubernetes controller clientset: %w", err)
|
||||
}
|
||||
@@ -251,12 +260,12 @@ func MaybeStartLocalServer(ctx context.Context, clientOpts *apiclient.ClientOpti
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running miniredis: %w", err)
|
||||
}
|
||||
appstateCache := appstatecache.NewCache(cache.NewCache(&forwardCacheClient{namespace: namespace, context: ctxStr, compression: compression, redisHaProxyName: clientOpts.RedisHaProxyName, redisName: clientOpts.RedisName}), time.Hour)
|
||||
|
||||
redisOptions := &redis.Options{Addr: mr.Addr()}
|
||||
if err = common.SetOptionalRedisPasswordFromKubeConfig(ctx, kubeClientset, namespace, redisOptions); err != nil {
|
||||
log.Warnf("Failed to fetch & set redis password for namespace %s: %v", namespace, err)
|
||||
}
|
||||
|
||||
appstateCache := appstatecache.NewCache(cache.NewCache(&forwardCacheClient{namespace: namespace, context: ctxStr, compression: compression, redisHaProxyName: clientOpts.RedisHaProxyName, redisName: clientOpts.RedisName, redisPassword: redisOptions.Password}), time.Hour)
|
||||
srv := server.NewServer(ctx, server.ArgoCDServerOpts{
|
||||
EnableGZip: false,
|
||||
Namespace: namespace,
|
||||
|
||||
@@ -352,9 +352,10 @@ func printSyncWindows(proj *v1alpha1.AppProject) {
|
||||
fmt.Fprintf(w, fmtStr, headers...)
|
||||
if proj.Spec.SyncWindows.HasWindows() {
|
||||
for i, window := range proj.Spec.SyncWindows {
|
||||
isActive, _ := window.Active()
|
||||
vals := []interface{}{
|
||||
strconv.Itoa(i),
|
||||
formatBoolOutput(window.Active()),
|
||||
formatBoolOutput(isActive),
|
||||
window.Kind,
|
||||
window.Schedule,
|
||||
window.Duration,
|
||||
|
||||
12
cmd/main.go
12
cmd/main.go
@@ -4,9 +4,9 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/argoproj/argo-cd/v2/cmd/util"
|
||||
|
||||
_ "go.uber.org/automaxprocs"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
appcontroller "github.com/argoproj/argo-cd/v2/cmd/argocd-application-controller/commands"
|
||||
applicationset "github.com/argoproj/argo-cd/v2/cmd/argocd-applicationset-controller/commands"
|
||||
@@ -31,9 +31,12 @@ func main() {
|
||||
if val := os.Getenv(binaryNameEnv); val != "" {
|
||||
binaryName = val
|
||||
}
|
||||
|
||||
isCLI := false
|
||||
switch binaryName {
|
||||
case "argocd", "argocd-linux-amd64", "argocd-darwin-amd64", "argocd-windows-amd64.exe":
|
||||
command = cli.NewCommand()
|
||||
isCLI = true
|
||||
case "argocd-server":
|
||||
command = apiserver.NewCommand()
|
||||
case "argocd-application-controller":
|
||||
@@ -42,19 +45,24 @@ func main() {
|
||||
command = reposerver.NewCommand()
|
||||
case "argocd-cmp-server":
|
||||
command = cmpserver.NewCommand()
|
||||
isCLI = true
|
||||
case "argocd-dex":
|
||||
command = dex.NewCommand()
|
||||
case "argocd-notifications":
|
||||
command = notification.NewCommand()
|
||||
case "argocd-git-ask-pass":
|
||||
command = gitaskpass.NewCommand()
|
||||
isCLI = true
|
||||
case "argocd-applicationset-controller":
|
||||
command = applicationset.NewCommand()
|
||||
case "argocd-k8s-auth":
|
||||
command = k8sauth.NewCommand()
|
||||
isCLI = true
|
||||
default:
|
||||
command = cli.NewCommand()
|
||||
isCLI = true
|
||||
}
|
||||
util.SetAutoMaxProcs(isCLI)
|
||||
|
||||
if err := command.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/automaxprocs/maxprocs"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
@@ -88,6 +90,19 @@ type AppOptions struct {
|
||||
ref string
|
||||
}
|
||||
|
||||
// SetAutoMaxProcs sets the GOMAXPROCS value based on the binary name.
|
||||
// It suppresses logs for CLI binaries and logs the setting for services.
|
||||
func SetAutoMaxProcs(isCLI bool) {
|
||||
if isCLI {
|
||||
_, _ = maxprocs.Set() // Intentionally ignore errors for CLI binaries
|
||||
} else {
|
||||
_, err := maxprocs.Set(maxprocs.Logger(log.Infof))
|
||||
if err != nil {
|
||||
log.Errorf("Error setting GOMAXPROCS: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func AddAppFlags(command *cobra.Command, opts *AppOptions) {
|
||||
command.Flags().StringVar(&opts.repoURL, "repo", "", "Repository URL, ignored if a file is set")
|
||||
command.Flags().StringVar(&opts.appPath, "path", "", "Path in repository to the app directory, ignored if a file is set")
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -529,3 +530,27 @@ func TestFilterResources(t *testing.T) {
|
||||
assert.Nil(t, filteredResources)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetAutoMaxProcs(t *testing.T) {
|
||||
t.Run("CLI mode ignores errors", func(t *testing.T) {
|
||||
logBuffer := &bytes.Buffer{}
|
||||
oldLogger := log.Default()
|
||||
log.SetOutput(logBuffer)
|
||||
defer log.SetOutput(oldLogger.Writer())
|
||||
|
||||
SetAutoMaxProcs(true)
|
||||
|
||||
assert.Empty(t, logBuffer.String(), "Expected no log output when isCLI is true")
|
||||
})
|
||||
|
||||
t.Run("Non-CLI mode logs error on failure", func(t *testing.T) {
|
||||
logBuffer := &bytes.Buffer{}
|
||||
oldLogger := log.Default()
|
||||
log.SetOutput(logBuffer)
|
||||
defer log.SetOutput(oldLogger.Writer())
|
||||
|
||||
SetAutoMaxProcs(false)
|
||||
|
||||
assert.NotContains(t, logBuffer.String(), "Error setting GOMAXPROCS", "Unexpected log output detected")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ func AddProjFlags(command *cobra.Command, opts *ProjectOpts) {
|
||||
command.Flags().StringArrayVar(&opts.allowedNamespacedResources, "allow-namespaced-resource", []string{}, "List of allowed namespaced resources")
|
||||
command.Flags().StringArrayVar(&opts.deniedNamespacedResources, "deny-namespaced-resource", []string{}, "List of denied namespaced resources")
|
||||
command.Flags().StringSliceVar(&opts.SourceNamespaces, "source-namespaces", []string{}, "List of source namespaces for applications")
|
||||
command.Flags().StringArrayVar(&opts.destinationServiceAccounts, "dest-service-accounts", []string{},
|
||||
"Destination server, namespace and target service account (e.g. https://192.168.99.100:8443,default,default-sa)")
|
||||
}
|
||||
|
||||
func getGroupKindList(values []string) []v1.GroupKind {
|
||||
@@ -98,8 +100,8 @@ func (opts *ProjectOpts) GetDestinationServiceAccounts() []v1alpha1.ApplicationD
|
||||
destinationServiceAccounts := make([]v1alpha1.ApplicationDestinationServiceAccount, 0)
|
||||
for _, destStr := range opts.destinationServiceAccounts {
|
||||
parts := strings.Split(destStr, ",")
|
||||
if len(parts) != 2 {
|
||||
log.Fatalf("Expected destination of the form: server,namespace. Received: %s", destStr)
|
||||
if len(parts) != 3 {
|
||||
log.Fatalf("Expected destination service account of the form: server,namespace, defaultServiceAccount. Received: %s", destStr)
|
||||
} else {
|
||||
destinationServiceAccounts = append(destinationServiceAccounts, v1alpha1.ApplicationDestinationServiceAccount{
|
||||
Server: parts[0],
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
)
|
||||
|
||||
func TestProjectOpts_ResourceLists(t *testing.T) {
|
||||
@@ -22,3 +24,27 @@ func TestProjectOpts_ResourceLists(t *testing.T) {
|
||||
[]v1.GroupKind{{Group: "rbac.authorization.k8s.io", Kind: "ClusterRole"}}, opts.GetDeniedClusterResources(),
|
||||
)
|
||||
}
|
||||
|
||||
func TestProjectOpts_GetDestinationServiceAccounts(t *testing.T) {
|
||||
opts := ProjectOpts{
|
||||
destinationServiceAccounts: []string{
|
||||
"https://192.168.99.100:8443,test-ns,test-sa",
|
||||
"https://kubernetes.default.svc.local:6443,guestbook,guestbook-sa",
|
||||
},
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t,
|
||||
[]v1alpha1.ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "https://192.168.99.100:8443",
|
||||
Namespace: "test-ns",
|
||||
DefaultServiceAccount: "test-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.default.svc.local:6443",
|
||||
Namespace: "guestbook",
|
||||
DefaultServiceAccount: "guestbook-sa",
|
||||
},
|
||||
}, opts.GetDestinationServiceAccounts(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -178,6 +178,7 @@ const (
|
||||
|
||||
// AnnotationKeyAppInstance is the Argo CD application name is used as the instance name
|
||||
AnnotationKeyAppInstance = "argocd.argoproj.io/tracking-id"
|
||||
AnnotationInstallationID = "argocd.argoproj.io/installation-id"
|
||||
|
||||
// AnnotationCompareOptions is a comma-separated list of options for comparison
|
||||
AnnotationCompareOptions = "argocd.argoproj.io/compare-options"
|
||||
|
||||
@@ -130,6 +130,7 @@ type ApplicationController struct {
|
||||
statusHardRefreshTimeout time.Duration
|
||||
statusRefreshJitter time.Duration
|
||||
selfHealTimeout time.Duration
|
||||
selfHealBackOff *wait.Backoff
|
||||
repoClientset apiclient.Clientset
|
||||
db db.ArgoDB
|
||||
settingsMgr *settings_util.SettingsManager
|
||||
@@ -160,6 +161,7 @@ func NewApplicationController(
|
||||
appHardResyncPeriod time.Duration,
|
||||
appResyncJitter time.Duration,
|
||||
selfHealTimeout time.Duration,
|
||||
selfHealBackoff *wait.Backoff,
|
||||
repoErrorGracePeriod time.Duration,
|
||||
metricsPort int,
|
||||
metricsCacheExpiration time.Duration,
|
||||
@@ -173,6 +175,7 @@ func NewApplicationController(
|
||||
serverSideDiff bool,
|
||||
dynamicClusterDistributionEnabled bool,
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts,
|
||||
enableK8sEvent []string,
|
||||
) (*ApplicationController, error) {
|
||||
log.Infof("appResyncPeriod=%v, appHardResyncPeriod=%v, appResyncJitter=%v", appResyncPeriod, appHardResyncPeriod, appResyncJitter)
|
||||
db := db.NewDB(namespace, settingsMgr, kubeClientset)
|
||||
@@ -197,9 +200,10 @@ func NewApplicationController(
|
||||
statusRefreshJitter: appResyncJitter,
|
||||
refreshRequestedApps: make(map[string]CompareWith),
|
||||
refreshRequestedAppsMutex: &sync.Mutex{},
|
||||
auditLogger: argo.NewAuditLogger(namespace, kubeClientset, common.ApplicationController),
|
||||
auditLogger: argo.NewAuditLogger(namespace, kubeClientset, common.ApplicationController, enableK8sEvent),
|
||||
settingsMgr: settingsMgr,
|
||||
selfHealTimeout: selfHealTimeout,
|
||||
selfHealBackOff: selfHealBackoff,
|
||||
clusterSharding: clusterSharding,
|
||||
projByNameCache: sync.Map{},
|
||||
applicationNamespaces: applicationNamespaces,
|
||||
@@ -1690,7 +1694,8 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
|
||||
app.Status.Summary = tree.GetSummary(app)
|
||||
}
|
||||
|
||||
if project.Spec.SyncWindows.Matches(app).CanSync(false) {
|
||||
canSync, _ := project.Spec.SyncWindows.Matches(app).CanSync(false)
|
||||
if canSync {
|
||||
syncErrCond, opMS := ctrl.autoSync(app, compareResult.syncStatus, compareResult.resources, compareResult.revisionUpdated)
|
||||
setOpMs = opMS
|
||||
if syncErrCond != nil {
|
||||
@@ -1980,6 +1985,9 @@ func (ctrl *ApplicationController) autoSync(app *appv1.Application, syncStatus *
|
||||
InitiatedBy: appv1.OperationInitiator{Automated: true},
|
||||
Retry: appv1.RetryStrategy{Limit: 5},
|
||||
}
|
||||
if app.Status.OperationState != nil && app.Status.OperationState.Operation.Sync != nil {
|
||||
op.Sync.SelfHealAttemptsCount = app.Status.OperationState.Operation.Sync.SelfHealAttemptsCount
|
||||
}
|
||||
if app.Spec.SyncPolicy.Retry != nil {
|
||||
op.Retry = *app.Spec.SyncPolicy.Retry
|
||||
}
|
||||
@@ -1997,6 +2005,7 @@ func (ctrl *ApplicationController) autoSync(app *appv1.Application, syncStatus *
|
||||
return nil, 0
|
||||
} else if alreadyAttempted && selfHeal {
|
||||
if shouldSelfHeal, retryAfter := ctrl.shouldSelfHeal(app); shouldSelfHeal {
|
||||
op.Sync.SelfHealAttemptsCount++
|
||||
for _, resource := range resources {
|
||||
if resource.Status != appv1.SyncStatusCodeSynced {
|
||||
op.Sync.Resources = append(op.Sync.Resources, appv1.SyncOperationResource{
|
||||
@@ -2115,10 +2124,24 @@ func (ctrl *ApplicationController) shouldSelfHeal(app *appv1.Application) (bool,
|
||||
}
|
||||
|
||||
var retryAfter time.Duration
|
||||
if app.Status.OperationState.FinishedAt == nil {
|
||||
retryAfter = ctrl.selfHealTimeout
|
||||
if ctrl.selfHealBackOff == nil {
|
||||
if app.Status.OperationState.FinishedAt == nil {
|
||||
retryAfter = ctrl.selfHealTimeout
|
||||
} else {
|
||||
retryAfter = ctrl.selfHealTimeout - time.Since(app.Status.OperationState.FinishedAt.Time)
|
||||
}
|
||||
} else {
|
||||
retryAfter = ctrl.selfHealTimeout - time.Since(app.Status.OperationState.FinishedAt.Time)
|
||||
backOff := *ctrl.selfHealBackOff
|
||||
backOff.Steps = int(app.Status.OperationState.Operation.Sync.SelfHealAttemptsCount)
|
||||
var delay time.Duration
|
||||
for backOff.Steps > 0 {
|
||||
delay = backOff.Step()
|
||||
}
|
||||
if app.Status.OperationState.FinishedAt == nil {
|
||||
retryAfter = delay
|
||||
} else {
|
||||
retryAfter = delay - time.Since(app.Status.OperationState.FinishedAt.Time)
|
||||
}
|
||||
}
|
||||
return retryAfter <= 0, retryAfter
|
||||
}
|
||||
|
||||
@@ -4,16 +4,18 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
clustercache "github.com/argoproj/gitops-engine/pkg/cache"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube/kubetest"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
clustercache "github.com/argoproj/gitops-engine/pkg/cache"
|
||||
"k8s.io/utils/ptr"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
statecache "github.com/argoproj/argo-cd/v2/controller/cache"
|
||||
@@ -43,12 +45,15 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
mockrepoclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
|
||||
"github.com/argoproj/argo-cd/v2/test"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
|
||||
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
|
||||
"github.com/argoproj/argo-cd/v2/util/settings"
|
||||
)
|
||||
|
||||
var testEnableEventList []string = argo.DefaultEnableEventList()
|
||||
|
||||
type namespacedResource struct {
|
||||
v1alpha1.ResourceNode
|
||||
AppName string
|
||||
@@ -64,6 +69,7 @@ type fakeData struct {
|
||||
metricsCacheExpiration time.Duration
|
||||
applicationNamespaces []string
|
||||
updateRevisionForPathsResponse *apiclient.UpdateRevisionForPathsResponse
|
||||
additionalObjs []runtime.Object
|
||||
}
|
||||
|
||||
type MockKubectl struct {
|
||||
@@ -133,7 +139,9 @@ func newFakeController(data *fakeData, repoErr error) *ApplicationController {
|
||||
},
|
||||
Data: data.configMapData,
|
||||
}
|
||||
kubeClient := fake.NewSimpleClientset(&clust, &cm, &secret)
|
||||
runtimeObjs := []runtime.Object{&clust, &secret, &cm}
|
||||
runtimeObjs = append(runtimeObjs, data.additionalObjs...)
|
||||
kubeClient := fake.NewSimpleClientset(runtimeObjs...)
|
||||
settingsMgr := settings.NewSettingsManager(context.Background(), kubeClient, test.FakeArgoCDNamespace)
|
||||
kubectl := &MockKubectl{Kubectl: &kubetest.MockKubectlCmd{}}
|
||||
ctrl, err := NewApplicationController(
|
||||
@@ -151,6 +159,7 @@ func newFakeController(data *fakeData, repoErr error) *ApplicationController {
|
||||
time.Hour,
|
||||
time.Second,
|
||||
time.Minute,
|
||||
nil,
|
||||
time.Second*10,
|
||||
common.DefaultPortArgoCDMetrics,
|
||||
data.metricsCacheExpiration,
|
||||
@@ -164,6 +173,7 @@ func newFakeController(data *fakeData, repoErr error) *ApplicationController {
|
||||
false,
|
||||
false,
|
||||
normalizers.IgnoreNormalizerOpts{},
|
||||
testEnableEventList,
|
||||
)
|
||||
db := &dbmocks.ArgoDB{}
|
||||
db.On("GetApplicationControllerReplicas").Return(1)
|
||||
@@ -2184,3 +2194,66 @@ func TestAlreadyAttemptSync(t *testing.T) {
|
||||
assert.False(t, attempted)
|
||||
})
|
||||
}
|
||||
|
||||
func assertDurationAround(t *testing.T, expected time.Duration, actual time.Duration) {
|
||||
delta := time.Second / 2
|
||||
assert.GreaterOrEqual(t, expected, actual-delta)
|
||||
assert.LessOrEqual(t, expected, actual+delta)
|
||||
}
|
||||
|
||||
func TestSelfHealExponentialBackoff(t *testing.T) {
|
||||
ctrl := newFakeController(&fakeData{}, nil)
|
||||
ctrl.selfHealBackOff = &wait.Backoff{
|
||||
Factor: 3,
|
||||
Duration: 2 * time.Second,
|
||||
Cap: 5 * time.Minute,
|
||||
}
|
||||
|
||||
app := &v1alpha1.Application{
|
||||
Status: v1alpha1.ApplicationStatus{
|
||||
OperationState: &v1alpha1.OperationState{
|
||||
Operation: v1alpha1.Operation{
|
||||
Sync: &v1alpha1.SyncOperation{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
attempts int64
|
||||
finishedAt *metav1.Time
|
||||
expectedDuration time.Duration
|
||||
shouldSelfHeal bool
|
||||
}{{
|
||||
attempts: 0,
|
||||
finishedAt: ptr.To(metav1.Now()),
|
||||
expectedDuration: 0,
|
||||
shouldSelfHeal: true,
|
||||
}, {
|
||||
attempts: 1,
|
||||
finishedAt: ptr.To(metav1.Now()),
|
||||
expectedDuration: 2 * time.Second,
|
||||
shouldSelfHeal: false,
|
||||
}, {
|
||||
attempts: 2,
|
||||
finishedAt: ptr.To(metav1.Now()),
|
||||
expectedDuration: 6 * time.Second,
|
||||
shouldSelfHeal: false,
|
||||
}, {
|
||||
attempts: 3,
|
||||
finishedAt: nil,
|
||||
expectedDuration: 18 * time.Second,
|
||||
shouldSelfHeal: false,
|
||||
}}
|
||||
|
||||
for i := range testCases {
|
||||
tc := testCases[i]
|
||||
t.Run(fmt.Sprintf("test case %d", i), func(t *testing.T) {
|
||||
app.Status.OperationState.Operation.Sync.SelfHealAttemptsCount = tc.attempts
|
||||
app.Status.OperationState.FinishedAt = tc.finishedAt
|
||||
ok, duration := ctrl.shouldSelfHeal(app)
|
||||
require.Equal(t, ok, tc.shouldSelfHeal)
|
||||
assertDurationAround(t, tc.expectedDuration, duration)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
9
controller/cache/cache.go
vendored
9
controller/cache/cache.go
vendored
@@ -197,6 +197,7 @@ type cacheSettings struct {
|
||||
clusterSettings clustercache.Settings
|
||||
appInstanceLabelKey string
|
||||
trackingMethod appv1.TrackingMethod
|
||||
installationID string
|
||||
// resourceOverrides provides a list of ignored differences to ignore watched resource updates
|
||||
resourceOverrides map[string]appv1.ResourceOverride
|
||||
|
||||
@@ -225,6 +226,10 @@ func (c *liveStateCache) loadCacheSettings() (*cacheSettings, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
installationID, err := c.settingsMgr.GetInstallationID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resourceUpdatesOverrides, err := c.settingsMgr.GetIgnoreResourceUpdatesOverrides()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -246,7 +251,7 @@ func (c *liveStateCache) loadCacheSettings() (*cacheSettings, error) {
|
||||
ResourcesFilter: resourcesFilter,
|
||||
}
|
||||
|
||||
return &cacheSettings{clusterSettings, appInstanceLabelKey, argo.GetTrackingMethod(c.settingsMgr), resourceUpdatesOverrides, ignoreResourceUpdatesEnabled}, nil
|
||||
return &cacheSettings{clusterSettings, appInstanceLabelKey, argo.GetTrackingMethod(c.settingsMgr), installationID, resourceUpdatesOverrides, ignoreResourceUpdatesEnabled}, nil
|
||||
}
|
||||
|
||||
func asResourceNode(r *clustercache.Resource) appv1.ResourceNode {
|
||||
@@ -523,7 +528,7 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
|
||||
|
||||
res.Health, _ = health.GetResourceHealth(un, cacheSettings.clusterSettings.ResourceHealthOverride)
|
||||
|
||||
appName := c.resourceTracking.GetAppName(un, cacheSettings.appInstanceLabelKey, cacheSettings.trackingMethod)
|
||||
appName := c.resourceTracking.GetAppName(un, cacheSettings.appInstanceLabelKey, cacheSettings.trackingMethod, cacheSettings.installationID)
|
||||
if isRoot && appName != "" {
|
||||
res.AppName = appName
|
||||
}
|
||||
|
||||
57
controller/cache/info.go
vendored
57
controller/cache/info.go
vendored
@@ -278,6 +278,32 @@ func populateIstioVirtualServiceInfo(un *unstructured.Unstructured, res *Resourc
|
||||
res.NetworkingInfo = &v1alpha1.ResourceNetworkingInfo{TargetRefs: targets, ExternalURLs: urls}
|
||||
}
|
||||
|
||||
func isPodInitializedConditionTrue(status *v1.PodStatus) bool {
|
||||
for _, condition := range status.Conditions {
|
||||
if condition.Type != v1.PodInitialized {
|
||||
continue
|
||||
}
|
||||
|
||||
return condition.Status == v1.ConditionTrue
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isRestartableInitContainer(initContainer *v1.Container) bool {
|
||||
if initContainer == nil {
|
||||
return false
|
||||
}
|
||||
if initContainer.RestartPolicy == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return *initContainer.RestartPolicy == v1.ContainerRestartPolicyAlways
|
||||
}
|
||||
|
||||
func isPodPhaseTerminal(phase v1.PodPhase) bool {
|
||||
return phase == v1.PodFailed || phase == v1.PodSucceeded
|
||||
}
|
||||
|
||||
func populatePodInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
||||
pod := v1.Pod{}
|
||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, &pod)
|
||||
@@ -288,7 +314,8 @@ func populatePodInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
||||
totalContainers := len(pod.Spec.Containers)
|
||||
readyContainers := 0
|
||||
|
||||
reason := string(pod.Status.Phase)
|
||||
podPhase := pod.Status.Phase
|
||||
reason := string(podPhase)
|
||||
if pod.Status.Reason != "" {
|
||||
reason = pod.Status.Reason
|
||||
}
|
||||
@@ -306,6 +333,21 @@ func populatePodInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
||||
res.Images = append(res.Images, image)
|
||||
}
|
||||
|
||||
// If the Pod carries {type:PodScheduled, reason:SchedulingGated}, set reason to 'SchedulingGated'.
|
||||
for _, condition := range pod.Status.Conditions {
|
||||
if condition.Type == v1.PodScheduled && condition.Reason == v1.PodReasonSchedulingGated {
|
||||
reason = v1.PodReasonSchedulingGated
|
||||
}
|
||||
}
|
||||
|
||||
initContainers := make(map[string]*v1.Container)
|
||||
for i := range pod.Spec.InitContainers {
|
||||
initContainers[pod.Spec.InitContainers[i].Name] = &pod.Spec.InitContainers[i]
|
||||
if isRestartableInitContainer(&pod.Spec.InitContainers[i]) {
|
||||
totalContainers++
|
||||
}
|
||||
}
|
||||
|
||||
initializing := false
|
||||
for i := range pod.Status.InitContainerStatuses {
|
||||
container := pod.Status.InitContainerStatuses[i]
|
||||
@@ -313,6 +355,12 @@ func populatePodInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
||||
switch {
|
||||
case container.State.Terminated != nil && container.State.Terminated.ExitCode == 0:
|
||||
continue
|
||||
case isRestartableInitContainer(initContainers[container.Name]) &&
|
||||
container.Started != nil && *container.Started:
|
||||
if container.Ready {
|
||||
readyContainers++
|
||||
}
|
||||
continue
|
||||
case container.State.Terminated != nil:
|
||||
// initialization is failed
|
||||
if len(container.State.Terminated.Reason) == 0 {
|
||||
@@ -334,8 +382,7 @@ func populatePodInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
||||
}
|
||||
break
|
||||
}
|
||||
if !initializing {
|
||||
restarts = 0
|
||||
if !initializing || isPodInitializedConditionTrue(&pod.Status) {
|
||||
hasRunning := false
|
||||
for i := len(pod.Status.ContainerStatuses) - 1; i >= 0; i-- {
|
||||
container := pod.Status.ContainerStatuses[i]
|
||||
@@ -370,7 +417,9 @@ func populatePodInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
||||
// and https://github.com/kubernetes/kubernetes/issues/90358#issuecomment-617859364
|
||||
if pod.DeletionTimestamp != nil && pod.Status.Reason == "NodeLost" {
|
||||
reason = "Unknown"
|
||||
} else if pod.DeletionTimestamp != nil {
|
||||
// If the pod is being deleted and the pod phase is not succeeded or failed, set the reason to "Terminating".
|
||||
// See https://github.com/kubernetes/kubectl/issues/1595#issuecomment-2080001023
|
||||
} else if pod.DeletionTimestamp != nil && !isPodPhaseTerminal(podPhase) {
|
||||
reason = "Terminating"
|
||||
}
|
||||
|
||||
|
||||
546
controller/cache/info_test.go
vendored
546
controller/cache/info_test.go
vendored
@@ -285,6 +285,552 @@ func TestGetPodInfo(t *testing.T) {
|
||||
assert.Equal(t, &v1alpha1.ResourceNetworkingInfo{Labels: map[string]string{"app": "guestbook"}}, info.NetworkingInfo)
|
||||
}
|
||||
|
||||
func TestGetPodWithInitialContainerInfo(t *testing.T) {
|
||||
pod := strToUnstructured(`
|
||||
apiVersion: "v1"
|
||||
kind: "Pod"
|
||||
metadata:
|
||||
labels:
|
||||
app: "app-with-initial-container"
|
||||
name: "app-with-initial-container-5f46976fdb-vd6rv"
|
||||
namespace: "default"
|
||||
ownerReferences:
|
||||
- apiVersion: "apps/v1"
|
||||
kind: "ReplicaSet"
|
||||
name: "app-with-initial-container-5f46976fdb"
|
||||
spec:
|
||||
containers:
|
||||
- image: "alpine:latest"
|
||||
imagePullPolicy: "Always"
|
||||
name: "app-with-initial-container"
|
||||
initContainers:
|
||||
- image: "alpine:latest"
|
||||
imagePullPolicy: "Always"
|
||||
name: "app-with-initial-container-logshipper"
|
||||
nodeName: "minikube"
|
||||
status:
|
||||
containerStatuses:
|
||||
- image: "alpine:latest"
|
||||
name: "app-with-initial-container"
|
||||
ready: true
|
||||
restartCount: 0
|
||||
started: true
|
||||
state:
|
||||
running:
|
||||
startedAt: "2024-10-08T08:44:25Z"
|
||||
initContainerStatuses:
|
||||
- image: "alpine:latest"
|
||||
name: "app-with-initial-container-logshipper"
|
||||
ready: true
|
||||
restartCount: 0
|
||||
started: false
|
||||
state:
|
||||
terminated:
|
||||
exitCode: 0
|
||||
reason: "Completed"
|
||||
phase: "Running"
|
||||
`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(pod, info, []string{})
|
||||
assert.Equal(t, []v1alpha1.InfoItem{
|
||||
{Name: "Status Reason", Value: "Running"},
|
||||
{Name: "Node", Value: "minikube"},
|
||||
{Name: "Containers", Value: "1/1"},
|
||||
}, info.Info)
|
||||
}
|
||||
|
||||
func TestGetPodInfoWithSidecar(t *testing.T) {
|
||||
pod := strToUnstructured(`
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
labels:
|
||||
app: app-with-sidecar
|
||||
name: app-with-sidecar-6664cc788c-lqlrp
|
||||
namespace: default
|
||||
ownerReferences:
|
||||
- apiVersion: apps/v1
|
||||
kind: ReplicaSet
|
||||
name: app-with-sidecar-6664cc788c
|
||||
spec:
|
||||
containers:
|
||||
- image: 'docker.m.daocloud.io/library/alpine:latest'
|
||||
imagePullPolicy: Always
|
||||
name: app-with-sidecar
|
||||
initContainers:
|
||||
- image: 'docker.m.daocloud.io/library/alpine:latest'
|
||||
imagePullPolicy: Always
|
||||
name: logshipper
|
||||
restartPolicy: Always
|
||||
nodeName: minikube
|
||||
status:
|
||||
containerStatuses:
|
||||
- image: 'docker.m.daocloud.io/library/alpine:latest'
|
||||
name: app-with-sidecar
|
||||
ready: true
|
||||
restartCount: 0
|
||||
started: true
|
||||
state:
|
||||
running:
|
||||
startedAt: '2024-10-08T08:39:43Z'
|
||||
initContainerStatuses:
|
||||
- image: 'docker.m.daocloud.io/library/alpine:latest'
|
||||
name: logshipper
|
||||
ready: true
|
||||
restartCount: 0
|
||||
started: true
|
||||
state:
|
||||
running:
|
||||
startedAt: '2024-10-08T08:39:40Z'
|
||||
phase: Running
|
||||
`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(pod, info, []string{})
|
||||
assert.Equal(t, []v1alpha1.InfoItem{
|
||||
{Name: "Status Reason", Value: "Running"},
|
||||
{Name: "Node", Value: "minikube"},
|
||||
{Name: "Containers", Value: "2/2"},
|
||||
}, info.Info)
|
||||
}
|
||||
|
||||
func TestGetPodInfoWithInitialContainer(t *testing.T) {
|
||||
pod := strToUnstructured(`
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
generateName: myapp-long-exist-56b7d8794d-
|
||||
labels:
|
||||
app: myapp-long-exist
|
||||
name: myapp-long-exist-56b7d8794d-pbgrd
|
||||
namespace: linghao
|
||||
ownerReferences:
|
||||
- apiVersion: apps/v1
|
||||
kind: ReplicaSet
|
||||
name: myapp-long-exist-56b7d8794d
|
||||
spec:
|
||||
containers:
|
||||
- image: alpine:latest
|
||||
imagePullPolicy: Always
|
||||
name: myapp-long-exist
|
||||
initContainers:
|
||||
- image: alpine:latest
|
||||
imagePullPolicy: Always
|
||||
name: myapp-long-exist-logshipper
|
||||
nodeName: minikube
|
||||
status:
|
||||
containerStatuses:
|
||||
- image: alpine:latest
|
||||
name: myapp-long-exist
|
||||
ready: false
|
||||
restartCount: 0
|
||||
started: false
|
||||
state:
|
||||
waiting:
|
||||
reason: PodInitializing
|
||||
initContainerStatuses:
|
||||
- image: alpine:latest
|
||||
name: myapp-long-exist-logshipper
|
||||
ready: false
|
||||
restartCount: 0
|
||||
started: true
|
||||
state:
|
||||
running:
|
||||
startedAt: '2024-10-09T08:03:45Z'
|
||||
phase: Pending
|
||||
startTime: '2024-10-09T08:02:39Z'
|
||||
`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(pod, info, []string{})
|
||||
assert.Equal(t, []v1alpha1.InfoItem{
|
||||
{Name: "Status Reason", Value: "Init:0/1"},
|
||||
{Name: "Node", Value: "minikube"},
|
||||
{Name: "Containers", Value: "0/1"},
|
||||
}, info.Info)
|
||||
}
|
||||
|
||||
// Test pod has 2 restartable init containers, the first one running but not started.
|
||||
func TestGetPodInfoWithRestartableInitContainer(t *testing.T) {
|
||||
pod := strToUnstructured(`
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test1
|
||||
spec:
|
||||
initContainers:
|
||||
- name: restartable-init-1
|
||||
restartPolicy: Always
|
||||
- name: restartable-init-2
|
||||
restartPolicy: Always
|
||||
containers:
|
||||
- name: container
|
||||
nodeName: minikube
|
||||
status:
|
||||
phase: Pending
|
||||
initContainerStatuses:
|
||||
- name: restartable-init-1
|
||||
ready: false
|
||||
restartCount: 3
|
||||
state:
|
||||
running: {}
|
||||
started: false
|
||||
lastTerminationState:
|
||||
terminated:
|
||||
finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time
|
||||
- name: restartable-init-2
|
||||
ready: false
|
||||
state:
|
||||
waiting: {}
|
||||
started: false
|
||||
containerStatuses:
|
||||
- ready: false
|
||||
restartCount: 0
|
||||
state:
|
||||
waiting: {}
|
||||
conditions:
|
||||
- type: ContainersReady
|
||||
status: "False"
|
||||
- type: Initialized
|
||||
status: "False"
|
||||
`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(pod, info, []string{})
|
||||
assert.Equal(t, []v1alpha1.InfoItem{
|
||||
{Name: "Status Reason", Value: "Init:0/2"},
|
||||
{Name: "Node", Value: "minikube"},
|
||||
{Name: "Containers", Value: "0/3"},
|
||||
{Name: "Restart Count", Value: "3"},
|
||||
}, info.Info)
|
||||
}
|
||||
|
||||
// Test pod has 2 restartable init containers, the first one started and the second one running but not started.
|
||||
func TestGetPodInfoWithPartiallyStartedInitContainers(t *testing.T) {
|
||||
pod := strToUnstructured(`
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test1
|
||||
spec:
|
||||
initContainers:
|
||||
- name: restartable-init-1
|
||||
restartPolicy: Always
|
||||
- name: restartable-init-2
|
||||
restartPolicy: Always
|
||||
containers:
|
||||
- name: container
|
||||
nodeName: minikube
|
||||
status:
|
||||
phase: Pending
|
||||
initContainerStatuses:
|
||||
- name: restartable-init-1
|
||||
ready: false
|
||||
restartCount: 3
|
||||
state:
|
||||
running: {}
|
||||
started: true
|
||||
lastTerminationState:
|
||||
terminated:
|
||||
finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time
|
||||
- name: restartable-init-2
|
||||
ready: false
|
||||
state:
|
||||
running: {}
|
||||
started: false
|
||||
containerStatuses:
|
||||
- ready: false
|
||||
restartCount: 0
|
||||
state:
|
||||
waiting: {}
|
||||
conditions:
|
||||
- type: ContainersReady
|
||||
status: "False"
|
||||
- type: Initialized
|
||||
status: "False"
|
||||
`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(pod, info, []string{})
|
||||
assert.Equal(t, []v1alpha1.InfoItem{
|
||||
{Name: "Status Reason", Value: "Init:1/2"},
|
||||
{Name: "Node", Value: "minikube"},
|
||||
{Name: "Containers", Value: "0/3"},
|
||||
{Name: "Restart Count", Value: "3"},
|
||||
}, info.Info)
|
||||
}
|
||||
|
||||
// Test pod has 2 restartable init containers started and 1 container running
|
||||
func TestGetPodInfoWithStartedInitContainers(t *testing.T) {
|
||||
pod := strToUnstructured(`
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test2
|
||||
spec:
|
||||
initContainers:
|
||||
- name: restartable-init-1
|
||||
restartPolicy: Always
|
||||
- name: restartable-init-2
|
||||
restartPolicy: Always
|
||||
containers:
|
||||
- name: container
|
||||
nodeName: minikube
|
||||
status:
|
||||
phase: Running
|
||||
initContainerStatuses:
|
||||
- name: restartable-init-1
|
||||
ready: false
|
||||
restartCount: 3
|
||||
state:
|
||||
running: {}
|
||||
started: true
|
||||
lastTerminationState:
|
||||
terminated:
|
||||
finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time
|
||||
- name: restartable-init-2
|
||||
ready: false
|
||||
state:
|
||||
running: {}
|
||||
started: true
|
||||
containerStatuses:
|
||||
- ready: true
|
||||
restartCount: 4
|
||||
state:
|
||||
running: {}
|
||||
lastTerminationState:
|
||||
terminated:
|
||||
finishedAt: "2023-10-01T00:00:00Z" # Replace with actual time
|
||||
conditions:
|
||||
- type: ContainersReady
|
||||
status: "False"
|
||||
- type: Initialized
|
||||
status: "True"
|
||||
`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(pod, info, []string{})
|
||||
assert.Equal(t, []v1alpha1.InfoItem{
|
||||
{Name: "Status Reason", Value: "Running"},
|
||||
{Name: "Node", Value: "minikube"},
|
||||
{Name: "Containers", Value: "1/3"},
|
||||
{Name: "Restart Count", Value: "7"},
|
||||
}, info.Info)
|
||||
}
|
||||
|
||||
// Test pod has 1 init container restarting and 1 container not running
|
||||
func TestGetPodInfoWithNormalInitContainer(t *testing.T) {
|
||||
pod := strToUnstructured(`
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test7
|
||||
spec:
|
||||
initContainers:
|
||||
- name: init-container
|
||||
containers:
|
||||
- name: main-container
|
||||
nodeName: minikube
|
||||
status:
|
||||
phase: podPhase
|
||||
initContainerStatuses:
|
||||
- ready: false
|
||||
restartCount: 3
|
||||
state:
|
||||
running: {}
|
||||
lastTerminationState:
|
||||
terminated:
|
||||
finishedAt: "2023-10-01T00:00:00Z" # Replace with the actual time
|
||||
containerStatuses:
|
||||
- ready: false
|
||||
restartCount: 0
|
||||
state:
|
||||
waiting: {}
|
||||
`)
|
||||
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(pod, info, []string{})
|
||||
assert.Equal(t, []v1alpha1.InfoItem{
|
||||
{Name: "Status Reason", Value: "Init:0/1"},
|
||||
{Name: "Node", Value: "minikube"},
|
||||
{Name: "Containers", Value: "0/1"},
|
||||
{Name: "Restart Count", Value: "3"},
|
||||
}, info.Info)
|
||||
}
|
||||
|
||||
// Test pod condition succeed
|
||||
func TestPodConditionSucceeded(t *testing.T) {
|
||||
pod := strToUnstructured(`
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test8
|
||||
spec:
|
||||
nodeName: minikube
|
||||
containers:
|
||||
- name: container
|
||||
status:
|
||||
phase: Succeeded
|
||||
containerStatuses:
|
||||
- ready: false
|
||||
restartCount: 0
|
||||
state:
|
||||
terminated:
|
||||
reason: Completed
|
||||
exitCode: 0
|
||||
`)
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(pod, info, []string{})
|
||||
assert.Equal(t, []v1alpha1.InfoItem{
|
||||
{Name: "Status Reason", Value: "Completed"},
|
||||
{Name: "Node", Value: "minikube"},
|
||||
{Name: "Containers", Value: "0/1"},
|
||||
}, info.Info)
|
||||
}
|
||||
|
||||
// Test pod condition failed
|
||||
func TestPodConditionFailed(t *testing.T) {
|
||||
pod := strToUnstructured(`
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test9
|
||||
spec:
|
||||
nodeName: minikube
|
||||
containers:
|
||||
- name: container
|
||||
status:
|
||||
phase: Failed
|
||||
containerStatuses:
|
||||
- ready: false
|
||||
restartCount: 0
|
||||
state:
|
||||
terminated:
|
||||
reason: Error
|
||||
exitCode: 1
|
||||
`)
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(pod, info, []string{})
|
||||
assert.Equal(t, []v1alpha1.InfoItem{
|
||||
{Name: "Status Reason", Value: "Error"},
|
||||
{Name: "Node", Value: "minikube"},
|
||||
{Name: "Containers", Value: "0/1"},
|
||||
}, info.Info)
|
||||
}
|
||||
|
||||
// Test pod condition succeed with deletion
|
||||
func TestPodConditionSucceededWithDeletion(t *testing.T) {
|
||||
pod := strToUnstructured(`
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test10
|
||||
deletionTimestamp: "2023-10-01T00:00:00Z"
|
||||
spec:
|
||||
nodeName: minikube
|
||||
containers:
|
||||
- name: container
|
||||
status:
|
||||
phase: Succeeded
|
||||
containerStatuses:
|
||||
- ready: false
|
||||
restartCount: 0
|
||||
state:
|
||||
terminated:
|
||||
reason: Completed
|
||||
exitCode: 0
|
||||
`)
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(pod, info, []string{})
|
||||
assert.Equal(t, []v1alpha1.InfoItem{
|
||||
{Name: "Status Reason", Value: "Completed"},
|
||||
{Name: "Node", Value: "minikube"},
|
||||
{Name: "Containers", Value: "0/1"},
|
||||
}, info.Info)
|
||||
}
|
||||
|
||||
// Test pod condition running with deletion
|
||||
func TestPodConditionRunningWithDeletion(t *testing.T) {
|
||||
pod := strToUnstructured(`
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test11
|
||||
deletionTimestamp: "2023-10-01T00:00:00Z"
|
||||
spec:
|
||||
nodeName: minikube
|
||||
containers:
|
||||
- name: container
|
||||
status:
|
||||
phase: Running
|
||||
containerStatuses:
|
||||
- ready: false
|
||||
restartCount: 0
|
||||
state:
|
||||
running: {}
|
||||
`)
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(pod, info, []string{})
|
||||
assert.Equal(t, []v1alpha1.InfoItem{
|
||||
{Name: "Status Reason", Value: "Terminating"},
|
||||
{Name: "Node", Value: "minikube"},
|
||||
{Name: "Containers", Value: "0/1"},
|
||||
}, info.Info)
|
||||
}
|
||||
|
||||
// Test pod condition pending with deletion
|
||||
func TestPodConditionPendingWithDeletion(t *testing.T) {
|
||||
pod := strToUnstructured(`
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test12
|
||||
deletionTimestamp: "2023-10-01T00:00:00Z"
|
||||
spec:
|
||||
nodeName: minikube
|
||||
containers:
|
||||
- name: container
|
||||
status:
|
||||
phase: Pending
|
||||
`)
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(pod, info, []string{})
|
||||
assert.Equal(t, []v1alpha1.InfoItem{
|
||||
{Name: "Status Reason", Value: "Terminating"},
|
||||
{Name: "Node", Value: "minikube"},
|
||||
{Name: "Containers", Value: "0/1"},
|
||||
}, info.Info)
|
||||
}
|
||||
|
||||
// Test PodScheduled condition with reason SchedulingGated
|
||||
func TestPodScheduledWithSchedulingGated(t *testing.T) {
|
||||
pod := strToUnstructured(`
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: test13
|
||||
spec:
|
||||
nodeName: minikube
|
||||
containers:
|
||||
- name: container1
|
||||
- name: container2
|
||||
status:
|
||||
phase: podPhase
|
||||
conditions:
|
||||
- type: PodScheduled
|
||||
status: "False"
|
||||
reason: SchedulingGated
|
||||
`)
|
||||
info := &ResourceInfo{}
|
||||
populateNodeInfo(pod, info, []string{})
|
||||
assert.Equal(t, []v1alpha1.InfoItem{
|
||||
{Name: "Status Reason", Value: "SchedulingGated"},
|
||||
{Name: "Node", Value: "minikube"},
|
||||
{Name: "Containers", Value: "0/2"},
|
||||
}, info.Info)
|
||||
}
|
||||
|
||||
func TestGetNodeInfo(t *testing.T) {
|
||||
node := strToUnstructured(`
|
||||
apiVersion: v1
|
||||
|
||||
@@ -161,6 +161,11 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp
|
||||
return nil, nil, false, fmt.Errorf("failed to get Helm settings: %w", err)
|
||||
}
|
||||
|
||||
installationID, err := m.settingsMgr.GetInstallationID()
|
||||
if err != nil {
|
||||
return nil, nil, false, fmt.Errorf("failed to get installation ID: %w", err)
|
||||
}
|
||||
|
||||
ts.AddCheckpoint("build_options_ms")
|
||||
serverVersion, apiResources, err := m.liveStateCache.GetVersionsInfo(app.Spec.Destination.Server)
|
||||
if err != nil {
|
||||
@@ -230,6 +235,7 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp
|
||||
TrackingMethod: string(argo.GetTrackingMethod(m.settingsMgr)),
|
||||
RefSources: refSources,
|
||||
HasMultipleSources: app.Spec.HasMultipleSources(),
|
||||
InstallationID: installationID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, false, fmt.Errorf("failed to compare revisions for source %d of %d: %w", i+1, len(sources), err)
|
||||
@@ -270,6 +276,7 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp
|
||||
RefSources: refSources,
|
||||
ProjectName: proj.Name,
|
||||
ProjectSourceRepos: proj.Spec.SourceRepos,
|
||||
InstallationID: installationID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, false, fmt.Errorf("failed to generate manifest for source %d of %d: %w", i+1, len(sources), err)
|
||||
@@ -353,20 +360,24 @@ func DeduplicateTargetObjects(
|
||||
|
||||
// getComparisonSettings will return the system level settings related to the
|
||||
// diff/normalization process.
|
||||
func (m *appStateManager) getComparisonSettings() (string, map[string]v1alpha1.ResourceOverride, *settings.ResourcesFilter, error) {
|
||||
func (m *appStateManager) getComparisonSettings() (string, map[string]v1alpha1.ResourceOverride, *settings.ResourcesFilter, string, error) {
|
||||
resourceOverrides, err := m.settingsMgr.GetResourceOverrides()
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
return "", nil, nil, "", err
|
||||
}
|
||||
appLabelKey, err := m.settingsMgr.GetAppInstanceLabelKey()
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
return "", nil, nil, "", err
|
||||
}
|
||||
resFilter, err := m.settingsMgr.GetResourcesFilter()
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
return "", nil, nil, "", err
|
||||
}
|
||||
return appLabelKey, resourceOverrides, resFilter, nil
|
||||
installationID, err := m.settingsMgr.GetInstallationID()
|
||||
if err != nil {
|
||||
return "", nil, nil, "", err
|
||||
}
|
||||
return appLabelKey, resourceOverrides, resFilter, installationID, nil
|
||||
}
|
||||
|
||||
// verifyGnuPGSignature verifies the result of a GnuPG operation for a given git
|
||||
@@ -417,7 +428,7 @@ func isManagedNamespace(ns *unstructured.Unstructured, app *v1alpha1.Application
|
||||
// revision and overrides in the app spec.
|
||||
func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1alpha1.AppProject, revisions []string, sources []v1alpha1.ApplicationSource, noCache bool, noRevisionCache bool, localManifests []string, hasMultipleSources bool, rollback bool) (*comparisonResult, error) {
|
||||
ts := stats.NewTimingStats()
|
||||
appLabelKey, resourceOverrides, resFilter, err := m.getComparisonSettings()
|
||||
appLabelKey, resourceOverrides, resFilter, installationID, err := m.getComparisonSettings()
|
||||
|
||||
ts.AddCheckpoint("settings_ms")
|
||||
|
||||
@@ -585,7 +596,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
|
||||
|
||||
for _, liveObj := range liveObjByKey {
|
||||
if liveObj != nil {
|
||||
appInstanceName := m.resourceTracking.GetAppName(liveObj, appLabelKey, trackingMethod)
|
||||
appInstanceName := m.resourceTracking.GetAppName(liveObj, appLabelKey, trackingMethod, installationID)
|
||||
if appInstanceName != "" && appInstanceName != app.InstanceName(m.namespace) {
|
||||
fqInstanceName := strings.ReplaceAll(appInstanceName, "_", "/")
|
||||
conditions = append(conditions, v1alpha1.ApplicationCondition{
|
||||
@@ -724,7 +735,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
|
||||
}
|
||||
gvk := obj.GroupVersionKind()
|
||||
|
||||
isSelfReferencedObj := m.isSelfReferencedObj(liveObj, targetObj, app.GetName(), appLabelKey, trackingMethod)
|
||||
isSelfReferencedObj := m.isSelfReferencedObj(liveObj, targetObj, app.GetName(), appLabelKey, trackingMethod, installationID)
|
||||
|
||||
resState := v1alpha1.ResourceStatus{
|
||||
Namespace: obj.GetNamespace(),
|
||||
@@ -924,9 +935,7 @@ func useDiffCache(noCache bool, manifestInfos []*apiclient.ManifestResponse, sou
|
||||
return false
|
||||
}
|
||||
|
||||
currentSpec := app.BuildComparedToStatus()
|
||||
specChanged := !reflect.DeepEqual(app.Status.Sync.ComparedTo, currentSpec)
|
||||
if specChanged {
|
||||
if !specEqualsCompareTo(app.Spec, app.Status.Sync.ComparedTo) {
|
||||
log.WithField("useDiffCache", "false").Debug("specChanged")
|
||||
return false
|
||||
}
|
||||
@@ -935,6 +944,29 @@ func useDiffCache(noCache bool, manifestInfos []*apiclient.ManifestResponse, sou
|
||||
return true
|
||||
}
|
||||
|
||||
// specEqualsCompareTo compares the application spec to the comparedTo status. It normalizes the destination to match
|
||||
// the comparedTo destination before comparing. It does not mutate the original spec or comparedTo.
|
||||
func specEqualsCompareTo(spec v1alpha1.ApplicationSpec, comparedTo v1alpha1.ComparedTo) bool {
|
||||
// Make a copy to be sure we don't mutate the original.
|
||||
specCopy := spec.DeepCopy()
|
||||
currentSpec := specCopy.BuildComparedToStatus()
|
||||
|
||||
// The spec might have been augmented to include both server and name, so change it to match the comparedTo before
|
||||
// comparing.
|
||||
if comparedTo.Destination.Server == "" {
|
||||
currentSpec.Destination.Server = ""
|
||||
}
|
||||
if comparedTo.Destination.Name == "" {
|
||||
currentSpec.Destination.Name = ""
|
||||
}
|
||||
|
||||
// Set IsServerInferred to false on both, because that field is not important for comparison.
|
||||
comparedTo.Destination.SetIsServerInferred(false)
|
||||
currentSpec.Destination.SetIsServerInferred(false)
|
||||
|
||||
return reflect.DeepEqual(comparedTo, currentSpec)
|
||||
}
|
||||
|
||||
func (m *appStateManager) persistRevisionHistory(
|
||||
app *v1alpha1.Application,
|
||||
revision string,
|
||||
@@ -1029,7 +1061,7 @@ func NewAppStateManager(
|
||||
// group and kind) match the properties of the live object, or if the tracking method
|
||||
// used does not provide the required properties for matching.
|
||||
// Reference: https://github.com/argoproj/argo-cd/issues/8683
|
||||
func (m *appStateManager) isSelfReferencedObj(live, config *unstructured.Unstructured, appName, appLabelKey string, trackingMethod v1alpha1.TrackingMethod) bool {
|
||||
func (m *appStateManager) isSelfReferencedObj(live, config *unstructured.Unstructured, appName, appLabelKey string, trackingMethod v1alpha1.TrackingMethod, installationID string) bool {
|
||||
if live == nil {
|
||||
return true
|
||||
}
|
||||
@@ -1062,7 +1094,7 @@ func (m *appStateManager) isSelfReferencedObj(live, config *unstructured.Unstruc
|
||||
// to match the properties from the live object. Cluster scoped objects
|
||||
// carry the app's destination namespace in the tracking annotation,
|
||||
// but are unique in GVK + name combination.
|
||||
appInstance := m.resourceTracking.GetAppInstance(live, appLabelKey, trackingMethod)
|
||||
appInstance := m.resourceTracking.GetAppInstance(live, appLabelKey, trackingMethod, installationID)
|
||||
if appInstance != nil {
|
||||
return isSelfReferencedObj(live, *appInstance)
|
||||
}
|
||||
|
||||
@@ -1372,8 +1372,8 @@ func TestIsLiveResourceManaged(t *testing.T) {
|
||||
configObj := managedObj.DeepCopy()
|
||||
|
||||
// then
|
||||
assert.True(t, manager.isSelfReferencedObj(managedObj, configObj, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
|
||||
assert.True(t, manager.isSelfReferencedObj(managedObj, configObj, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
|
||||
assert.True(t, manager.isSelfReferencedObj(managedObj, configObj, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel, ""))
|
||||
assert.True(t, manager.isSelfReferencedObj(managedObj, configObj, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation, ""))
|
||||
})
|
||||
t.Run("will return true if tracked with label", func(t *testing.T) {
|
||||
// given
|
||||
@@ -1381,43 +1381,43 @@ func TestIsLiveResourceManaged(t *testing.T) {
|
||||
configObj := managedObjWithLabel.DeepCopy()
|
||||
|
||||
// then
|
||||
assert.True(t, manager.isSelfReferencedObj(managedObjWithLabel, configObj, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
|
||||
assert.True(t, manager.isSelfReferencedObj(managedObjWithLabel, configObj, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel, ""))
|
||||
})
|
||||
t.Run("will handle if trackingId has wrong resource name and config is nil", func(t *testing.T) {
|
||||
// given
|
||||
t.Parallel()
|
||||
|
||||
// then
|
||||
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongName, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
|
||||
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongName, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
|
||||
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongName, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel, ""))
|
||||
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongName, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation, ""))
|
||||
})
|
||||
t.Run("will handle if trackingId has wrong resource group and config is nil", func(t *testing.T) {
|
||||
// given
|
||||
t.Parallel()
|
||||
|
||||
// then
|
||||
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongGroup, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
|
||||
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongGroup, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
|
||||
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongGroup, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel, ""))
|
||||
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongGroup, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation, ""))
|
||||
})
|
||||
t.Run("will handle if trackingId has wrong kind and config is nil", func(t *testing.T) {
|
||||
// given
|
||||
t.Parallel()
|
||||
|
||||
// then
|
||||
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongKind, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
|
||||
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongKind, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
|
||||
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongKind, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel, ""))
|
||||
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongKind, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation, ""))
|
||||
})
|
||||
t.Run("will handle if trackingId has wrong namespace and config is nil", func(t *testing.T) {
|
||||
// given
|
||||
t.Parallel()
|
||||
|
||||
// then
|
||||
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongNamespace, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
|
||||
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongNamespace, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotationAndLabel))
|
||||
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongNamespace, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel, ""))
|
||||
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongNamespace, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotationAndLabel, ""))
|
||||
})
|
||||
t.Run("will return true if live is nil", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert.True(t, manager.isSelfReferencedObj(nil, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
|
||||
assert.True(t, manager.isSelfReferencedObj(nil, nil, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation, ""))
|
||||
})
|
||||
|
||||
t.Run("will handle upgrade in desired state APIGroup", func(t *testing.T) {
|
||||
@@ -1427,11 +1427,13 @@ func TestIsLiveResourceManaged(t *testing.T) {
|
||||
delete(config.GetAnnotations(), common.AnnotationKeyAppInstance)
|
||||
|
||||
// then
|
||||
assert.True(t, manager.isSelfReferencedObj(managedWrongAPIGroup, config, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
|
||||
assert.True(t, manager.isSelfReferencedObj(managedWrongAPIGroup, config, appName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation, ""))
|
||||
})
|
||||
}
|
||||
|
||||
func TestUseDiffCache(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type fixture struct {
|
||||
testName string
|
||||
noCache bool
|
||||
@@ -1527,6 +1529,10 @@ func TestUseDiffCache(t *testing.T) {
|
||||
t.Fatalf("error merging app: %s", err)
|
||||
}
|
||||
}
|
||||
if app.Spec.Destination.Name != "" && app.Spec.Destination.Server != "" {
|
||||
// Simulate the controller's process for populating both of these fields.
|
||||
app.Spec.Destination.SetInferredServer(app.Spec.Destination.Server)
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
@@ -1692,6 +1698,44 @@ func TestUseDiffCache(t *testing.T) {
|
||||
expectedUseCache: false,
|
||||
serverSideDiff: false,
|
||||
},
|
||||
{
|
||||
// There are code paths that modify the ApplicationSpec and augment the destination field with both the
|
||||
// destination server and name. Since both fields are populated in the app spec but not in the comparedTo,
|
||||
// we need to make sure we correctly compare the fields and don't miss the cache.
|
||||
testName: "will return true if the app spec destination contains both server and name, but otherwise matches comparedTo",
|
||||
noCache: false,
|
||||
manifestInfos: manifestInfos("rev1"),
|
||||
sources: sources(),
|
||||
app: app("httpbin", "rev1", false, &argoappv1.Application{
|
||||
Spec: argoappv1.ApplicationSpec{
|
||||
Destination: argoappv1.ApplicationDestination{
|
||||
Server: "https://kubernetes.default.svc",
|
||||
Name: "httpbin",
|
||||
Namespace: "httpbin",
|
||||
},
|
||||
},
|
||||
Status: argoappv1.ApplicationStatus{
|
||||
Resources: []argoappv1.ResourceStatus{},
|
||||
Sync: argoappv1.SyncStatus{
|
||||
Status: argoappv1.SyncStatusCodeSynced,
|
||||
ComparedTo: argoappv1.ComparedTo{
|
||||
Destination: argoappv1.ApplicationDestination{
|
||||
Server: "https://kubernetes.default.svc",
|
||||
Namespace: "httpbin",
|
||||
},
|
||||
},
|
||||
Revision: "rev1",
|
||||
},
|
||||
ReconciledAt: &metav1.Time{
|
||||
Time: time.Now().Add(-time.Hour),
|
||||
},
|
||||
},
|
||||
}),
|
||||
manifestRevisions: []string{"rev1"},
|
||||
statusRefreshTimeout: time.Hour * 24,
|
||||
expectedUseCache: true,
|
||||
serverSideDiff: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
|
||||
@@ -44,6 +44,10 @@ const (
|
||||
// EnvVarSyncWaveDelay is an environment variable which controls the delay in seconds between
|
||||
// each sync-wave
|
||||
EnvVarSyncWaveDelay = "ARGOCD_SYNC_WAVE_DELAY"
|
||||
|
||||
// serviceAccountDisallowedCharSet contains the characters that are not allowed to be present
|
||||
// in a DefaultServiceAccount configured for a DestinationServiceAccount
|
||||
serviceAccountDisallowedCharSet = "!*[]{}\\/"
|
||||
)
|
||||
|
||||
func (m *appStateManager) getOpenAPISchema(server string) (openapi.Resources, error) {
|
||||
@@ -170,12 +174,18 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
|
||||
state.Phase = common.OperationError
|
||||
state.Message = fmt.Sprintf("Failed to load application project: %v", err)
|
||||
return
|
||||
} else if syncWindowPreventsSync(app, proj) {
|
||||
// If the operation is currently running, simply let the user know the sync is blocked by a current sync window
|
||||
if state.Phase == common.OperationRunning {
|
||||
state.Message = "Sync operation blocked by sync window"
|
||||
} else {
|
||||
isBlocked, err := syncWindowPreventsSync(app, proj)
|
||||
if isBlocked {
|
||||
// If the operation is currently running, simply let the user know the sync is blocked by a current sync window
|
||||
if state.Phase == common.OperationRunning {
|
||||
state.Message = "Sync operation blocked by sync window"
|
||||
if err != nil {
|
||||
state.Message = fmt.Sprintf("%s: %v", state.Message, err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !isMultiSourceRevision {
|
||||
@@ -285,10 +295,20 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
|
||||
log.Errorf("Could not get appInstanceLabelKey: %v", err)
|
||||
return
|
||||
}
|
||||
installationID, err := m.settingsMgr.GetInstallationID()
|
||||
if err != nil {
|
||||
log.Errorf("Could not get installation ID: %v", err)
|
||||
return
|
||||
}
|
||||
trackingMethod := argo.GetTrackingMethod(m.settingsMgr)
|
||||
|
||||
if m.settingsMgr.IsImpersonationEnabled() {
|
||||
serviceAccountToImpersonate, err := deriveServiceAccountName(proj, app)
|
||||
impersonationEnabled, err := m.settingsMgr.IsImpersonationEnabled()
|
||||
if err != nil {
|
||||
log.Errorf("could not get impersonation feature flag: %v", err)
|
||||
return
|
||||
}
|
||||
if impersonationEnabled {
|
||||
serviceAccountToImpersonate, err := deriveServiceAccountToImpersonate(proj, app)
|
||||
if err != nil {
|
||||
state.Phase = common.OperationError
|
||||
state.Message = fmt.Sprintf("failed to find a matching service account to impersonate: %v", err)
|
||||
@@ -331,7 +351,7 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
|
||||
return (len(syncOp.Resources) == 0 ||
|
||||
isPostDeleteHook(target) ||
|
||||
argo.ContainsSyncResource(key.Name, key.Namespace, schema.GroupVersionKind{Kind: key.Kind, Group: key.Group}, syncOp.Resources)) &&
|
||||
m.isSelfReferencedObj(live, target, app.GetName(), appLabelKey, trackingMethod)
|
||||
m.isSelfReferencedObj(live, target, app.GetName(), appLabelKey, trackingMethod, installationID)
|
||||
}),
|
||||
sync.WithManifestValidation(!syncOp.SyncOptions.HasOption(common.SyncOptionsDisableValidation)),
|
||||
sync.WithSyncWaveHook(delayBetweenSyncWaves),
|
||||
@@ -548,18 +568,23 @@ func delayBetweenSyncWaves(phase common.SyncPhase, wave int, finalWave bool) err
|
||||
return nil
|
||||
}
|
||||
|
||||
func syncWindowPreventsSync(app *v1alpha1.Application, proj *v1alpha1.AppProject) bool {
|
||||
func syncWindowPreventsSync(app *v1alpha1.Application, proj *v1alpha1.AppProject) (bool, error) {
|
||||
window := proj.Spec.SyncWindows.Matches(app)
|
||||
isManual := false
|
||||
if app.Status.OperationState != nil {
|
||||
isManual = !app.Status.OperationState.Operation.InitiatedBy.Automated
|
||||
}
|
||||
return !window.CanSync(isManual)
|
||||
canSync, err := window.CanSync(isManual)
|
||||
if err != nil {
|
||||
// prevents sync because sync window has an error
|
||||
return true, err
|
||||
}
|
||||
return !canSync, nil
|
||||
}
|
||||
|
||||
// deriveServiceAccountName determines the service account to be used for impersonation for the sync operation.
|
||||
// deriveServiceAccountToImpersonate determines the service account to be used for impersonation for the sync operation.
|
||||
// The returned service account will be fully qualified including namespace and the service account name in the format system:serviceaccount:<namespace>:<service_account>
|
||||
func deriveServiceAccountName(project *v1alpha1.AppProject, application *v1alpha1.Application) (string, error) {
|
||||
func deriveServiceAccountToImpersonate(project *v1alpha1.AppProject, application *v1alpha1.Application) (string, error) {
|
||||
// spec.Destination.Namespace is optional. If not specified, use the Application's
|
||||
// namespace
|
||||
serviceAccountNamespace := application.Spec.Destination.Namespace
|
||||
@@ -569,10 +594,18 @@ func deriveServiceAccountName(project *v1alpha1.AppProject, application *v1alpha
|
||||
// Loop through the destinationServiceAccounts and see if there is any destination that is a candidate.
|
||||
// if so, return the service account specified for that destination.
|
||||
for _, item := range project.Spec.DestinationServiceAccounts {
|
||||
dstServerMatched := glob.Match(item.Server, application.Spec.Destination.Server)
|
||||
dstNamespaceMatched := glob.Match(item.Namespace, application.Spec.Destination.Namespace)
|
||||
dstServerMatched, err := glob.MatchWithError(item.Server, application.Spec.Destination.Server)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid glob pattern for destination server: %w", err)
|
||||
}
|
||||
dstNamespaceMatched, err := glob.MatchWithError(item.Namespace, application.Spec.Destination.Namespace)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid glob pattern for destination namespace: %w", err)
|
||||
}
|
||||
if dstServerMatched && dstNamespaceMatched {
|
||||
if strings.Contains(item.DefaultServiceAccount, ":") {
|
||||
if strings.Trim(item.DefaultServiceAccount, " ") == "" || strings.ContainsAny(item.DefaultServiceAccount, serviceAccountDisallowedCharSet) {
|
||||
return "", fmt.Errorf("default service account contains invalid chars '%s'", item.DefaultServiceAccount)
|
||||
} else if strings.Contains(item.DefaultServiceAccount, ":") {
|
||||
// service account is specified along with its namespace.
|
||||
return fmt.Sprintf("system:serviceaccount:%s", item.DefaultServiceAccount), nil
|
||||
} else {
|
||||
|
||||
@@ -2,6 +2,7 @@ package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/sync"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -644,6 +646,771 @@ func TestNormalizeTargetResourcesWithList(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeriveServiceAccountMatchingNamespaces(t *testing.T) {
|
||||
type fixture struct {
|
||||
project *v1alpha1.AppProject
|
||||
application *v1alpha1.Application
|
||||
}
|
||||
|
||||
setup := func(destinationServiceAccounts []v1alpha1.ApplicationDestinationServiceAccount, destinationNamespace, destinationServerURL, applicationNamespace string) *fixture {
|
||||
project := &v1alpha1.AppProject{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "argocd-ns",
|
||||
Name: "testProj",
|
||||
},
|
||||
Spec: v1alpha1.AppProjectSpec{
|
||||
DestinationServiceAccounts: destinationServiceAccounts,
|
||||
},
|
||||
}
|
||||
app := &v1alpha1.Application{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: applicationNamespace,
|
||||
Name: "testApp",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Project: "testProj",
|
||||
Destination: v1alpha1.ApplicationDestination{
|
||||
Server: destinationServerURL,
|
||||
Namespace: destinationNamespace,
|
||||
},
|
||||
},
|
||||
}
|
||||
return &fixture{
|
||||
project: project,
|
||||
application: app,
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("empty destination service accounts", func(t *testing.T) {
|
||||
// given an application referring a project with no destination service accounts
|
||||
t.Parallel()
|
||||
destinationServiceAccounts := []v1alpha1.ApplicationDestinationServiceAccount{}
|
||||
destinationNamespace := "testns"
|
||||
destinationServerURL := "https://kubernetes.svc.local"
|
||||
applicationNamespace := "argocd-ns"
|
||||
expectedSA := ""
|
||||
expectedErrMsg := "no matching service account found for destination server https://kubernetes.svc.local and namespace testns"
|
||||
|
||||
f := setup(destinationServiceAccounts, destinationNamespace, destinationServerURL, applicationNamespace)
|
||||
// when
|
||||
sa, err := deriveServiceAccountToImpersonate(f.project, f.application)
|
||||
assert.Equal(t, expectedSA, sa)
|
||||
|
||||
// then, there should be an error saying no valid match was found
|
||||
assert.EqualError(t, err, expectedErrMsg)
|
||||
})
|
||||
|
||||
t.Run("exact match of destination namespace", func(t *testing.T) {
|
||||
// given an application referring a project with exactly one destination service account that matches the application destination,
|
||||
t.Parallel()
|
||||
destinationServiceAccounts := []v1alpha1.ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "testns",
|
||||
DefaultServiceAccount: "test-sa",
|
||||
},
|
||||
}
|
||||
destinationNamespace := "testns"
|
||||
destinationServerURL := "https://kubernetes.svc.local"
|
||||
applicationNamespace := "argocd-ns"
|
||||
expectedSA := "system:serviceaccount:testns:test-sa"
|
||||
|
||||
f := setup(destinationServiceAccounts, destinationNamespace, destinationServerURL, applicationNamespace)
|
||||
// when
|
||||
sa, err := deriveServiceAccountToImpersonate(f.project, f.application)
|
||||
|
||||
// then, there should be no error and should use the right service account for impersonation
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedSA, sa)
|
||||
})
|
||||
|
||||
t.Run("exact one match with multiple destination service accounts", func(t *testing.T) {
|
||||
// given an application referring a project with multiple destination service accounts having one exact match for application destination
|
||||
t.Parallel()
|
||||
destinationServiceAccounts := []v1alpha1.ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "guestbook",
|
||||
DefaultServiceAccount: "guestbook-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "guestbook-test",
|
||||
DefaultServiceAccount: "guestbook-test-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "default",
|
||||
DefaultServiceAccount: "default-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "testns",
|
||||
DefaultServiceAccount: "test-sa",
|
||||
},
|
||||
}
|
||||
destinationNamespace := "testns"
|
||||
destinationServerURL := "https://kubernetes.svc.local"
|
||||
applicationNamespace := "argocd-ns"
|
||||
expectedSA := "system:serviceaccount:testns:test-sa"
|
||||
|
||||
f := setup(destinationServiceAccounts, destinationNamespace, destinationServerURL, applicationNamespace)
|
||||
// when
|
||||
sa, err := deriveServiceAccountToImpersonate(f.project, f.application)
|
||||
|
||||
// then, there should be no error and should use the right service account for impersonation
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedSA, sa)
|
||||
})
|
||||
|
||||
t.Run("first match to be used when multiple matches are available", func(t *testing.T) {
|
||||
// given an application referring a project with multiple destination service accounts having multiple match for application destination
|
||||
t.Parallel()
|
||||
destinationServiceAccounts := []v1alpha1.ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "testns",
|
||||
DefaultServiceAccount: "test-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "testns",
|
||||
DefaultServiceAccount: "test-sa-2",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "testns",
|
||||
DefaultServiceAccount: "test-sa-3",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "guestbook",
|
||||
DefaultServiceAccount: "guestbook-sa",
|
||||
},
|
||||
}
|
||||
destinationNamespace := "testns"
|
||||
destinationServerURL := "https://kubernetes.svc.local"
|
||||
applicationNamespace := "argocd-ns"
|
||||
expectedSA := "system:serviceaccount:testns:test-sa"
|
||||
|
||||
f := setup(destinationServiceAccounts, destinationNamespace, destinationServerURL, applicationNamespace)
|
||||
// when
|
||||
sa, err := deriveServiceAccountToImpersonate(f.project, f.application)
|
||||
|
||||
// then, there should be no error and it should use the first matching service account for impersonation
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedSA, sa)
|
||||
})
|
||||
|
||||
t.Run("first match to be used when glob pattern is used", func(t *testing.T) {
|
||||
// given an application referring a project with multiple destination service accounts with glob patterns matching the application destination
|
||||
t.Parallel()
|
||||
destinationServiceAccounts := []v1alpha1.ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "test*",
|
||||
DefaultServiceAccount: "test-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "testns",
|
||||
DefaultServiceAccount: "test-sa-2",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "default",
|
||||
DefaultServiceAccount: "default-sa",
|
||||
},
|
||||
}
|
||||
destinationNamespace := "testns"
|
||||
destinationServerURL := "https://kubernetes.svc.local"
|
||||
applicationNamespace := "argocd-ns"
|
||||
expectedSA := "system:serviceaccount:testns:test-sa"
|
||||
|
||||
f := setup(destinationServiceAccounts, destinationNamespace, destinationServerURL, applicationNamespace)
|
||||
// when
|
||||
sa, err := deriveServiceAccountToImpersonate(f.project, f.application)
|
||||
|
||||
// then, there should not be any error and should use the first matching glob pattern service account for impersonation
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedSA, sa)
|
||||
})
|
||||
|
||||
t.Run("no match among a valid list", func(t *testing.T) {
|
||||
// given an application referring a project with multiple destination service accounts with no matches for application destination
|
||||
t.Parallel()
|
||||
destinationServiceAccounts := []v1alpha1.ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "test1",
|
||||
DefaultServiceAccount: "test-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "test2",
|
||||
DefaultServiceAccount: "test-sa-2",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "default",
|
||||
DefaultServiceAccount: "default-sa",
|
||||
},
|
||||
}
|
||||
destinationNamespace := "testns"
|
||||
destinationServerURL := "https://kubernetes.svc.local"
|
||||
applicationNamespace := "argocd-ns"
|
||||
expectedSA := ""
|
||||
expectedErrMsg := "no matching service account found for destination server https://kubernetes.svc.local and namespace testns"
|
||||
|
||||
f := setup(destinationServiceAccounts, destinationNamespace, destinationServerURL, applicationNamespace)
|
||||
// when
|
||||
sa, err := deriveServiceAccountToImpersonate(f.project, f.application)
|
||||
|
||||
// then, there should be an error saying no match was found
|
||||
require.EqualError(t, err, expectedErrMsg)
|
||||
assert.Equal(t, expectedSA, sa)
|
||||
})
|
||||
|
||||
t.Run("app destination namespace is empty", func(t *testing.T) {
|
||||
// given an application referring a project with multiple destination service accounts with empty application destination namespace
|
||||
t.Parallel()
|
||||
destinationServiceAccounts := []v1alpha1.ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
DefaultServiceAccount: "test-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "*",
|
||||
DefaultServiceAccount: "test-sa-2",
|
||||
},
|
||||
}
|
||||
destinationNamespace := ""
|
||||
destinationServerURL := "https://kubernetes.svc.local"
|
||||
applicationNamespace := "argocd-ns"
|
||||
expectedSA := "system:serviceaccount:argocd-ns:test-sa"
|
||||
|
||||
f := setup(destinationServiceAccounts, destinationNamespace, destinationServerURL, applicationNamespace)
|
||||
// when
|
||||
sa, err := deriveServiceAccountToImpersonate(f.project, f.application)
|
||||
|
||||
// then, there should not be any error and the service account configured for with empty namespace should be used.
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedSA, sa)
|
||||
})
|
||||
|
||||
t.Run("match done via catch all glob pattern", func(t *testing.T) {
|
||||
// given an application referring a project with multiple destination service accounts having a catch all glob pattern
|
||||
t.Parallel()
|
||||
destinationServiceAccounts := []v1alpha1.ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "testns1",
|
||||
DefaultServiceAccount: "test-sa-2",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "default",
|
||||
DefaultServiceAccount: "default-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "*",
|
||||
DefaultServiceAccount: "test-sa",
|
||||
},
|
||||
}
|
||||
destinationNamespace := "testns"
|
||||
destinationServerURL := "https://kubernetes.svc.local"
|
||||
applicationNamespace := "argocd-ns"
|
||||
expectedSA := "system:serviceaccount:testns:test-sa"
|
||||
|
||||
f := setup(destinationServiceAccounts, destinationNamespace, destinationServerURL, applicationNamespace)
|
||||
// when
|
||||
sa, err := deriveServiceAccountToImpersonate(f.project, f.application)
|
||||
|
||||
// then, there should not be any error and the catch all service account should be returned
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedSA, sa)
|
||||
})
|
||||
|
||||
t.Run("match done via invalid glob pattern", func(t *testing.T) {
|
||||
// given an application referring a project with a destination service account having an invalid glob pattern for namespace
|
||||
t.Parallel()
|
||||
destinationServiceAccounts := []v1alpha1.ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "e[[a*",
|
||||
DefaultServiceAccount: "test-sa",
|
||||
},
|
||||
}
|
||||
destinationNamespace := "testns"
|
||||
destinationServerURL := "https://kubernetes.svc.local"
|
||||
applicationNamespace := "argocd-ns"
|
||||
expectedSA := ""
|
||||
|
||||
f := setup(destinationServiceAccounts, destinationNamespace, destinationServerURL, applicationNamespace)
|
||||
// when
|
||||
sa, err := deriveServiceAccountToImpersonate(f.project, f.application)
|
||||
|
||||
// then, there must be an error as the glob pattern is invalid.
|
||||
require.ErrorContains(t, err, "invalid glob pattern for destination namespace")
|
||||
assert.Equal(t, expectedSA, sa)
|
||||
})
|
||||
|
||||
t.Run("sa specified with a namespace", func(t *testing.T) {
|
||||
// given an application referring a project with multiple destination service accounts having a matching service account specified with its namespace
|
||||
t.Parallel()
|
||||
destinationServiceAccounts := []v1alpha1.ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "testns",
|
||||
DefaultServiceAccount: "myns:test-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "default",
|
||||
DefaultServiceAccount: "default-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "*",
|
||||
DefaultServiceAccount: "test-sa",
|
||||
},
|
||||
}
|
||||
destinationNamespace := "testns"
|
||||
destinationServerURL := "https://kubernetes.svc.local"
|
||||
applicationNamespace := "argocd-ns"
|
||||
expectedSA := "system:serviceaccount:myns:test-sa"
|
||||
|
||||
f := setup(destinationServiceAccounts, destinationNamespace, destinationServerURL, applicationNamespace)
|
||||
// when
|
||||
sa, err := deriveServiceAccountToImpersonate(f.project, f.application)
|
||||
assert.Equal(t, expectedSA, sa)
|
||||
|
||||
// then, there should not be any error and the service account with its namespace should be returned.
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeriveServiceAccountMatchingServers(t *testing.T) {
|
||||
type fixture struct {
|
||||
project *v1alpha1.AppProject
|
||||
application *v1alpha1.Application
|
||||
}
|
||||
|
||||
setup := func(destinationServiceAccounts []v1alpha1.ApplicationDestinationServiceAccount, destinationNamespace, destinationServerURL, applicationNamespace string) *fixture {
|
||||
project := &v1alpha1.AppProject{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "argocd-ns",
|
||||
Name: "testProj",
|
||||
},
|
||||
Spec: v1alpha1.AppProjectSpec{
|
||||
DestinationServiceAccounts: destinationServiceAccounts,
|
||||
},
|
||||
}
|
||||
app := &v1alpha1.Application{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: applicationNamespace,
|
||||
Name: "testApp",
|
||||
},
|
||||
Spec: v1alpha1.ApplicationSpec{
|
||||
Project: "testProj",
|
||||
Destination: v1alpha1.ApplicationDestination{
|
||||
Server: destinationServerURL,
|
||||
Namespace: destinationNamespace,
|
||||
},
|
||||
},
|
||||
}
|
||||
return &fixture{
|
||||
project: project,
|
||||
application: app,
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("exact one match with multiple destination service accounts", func(t *testing.T) {
|
||||
// given an application referring a project with multiple destination service accounts and one exact match for application destination
|
||||
t.Parallel()
|
||||
destinationServiceAccounts := []v1alpha1.ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "guestbook",
|
||||
DefaultServiceAccount: "guestbook-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://abc.svc.local",
|
||||
Namespace: "guestbook",
|
||||
DefaultServiceAccount: "guestbook-test-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://cde.svc.local",
|
||||
Namespace: "guestbook",
|
||||
DefaultServiceAccount: "default-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "testns",
|
||||
DefaultServiceAccount: "test-sa",
|
||||
},
|
||||
}
|
||||
destinationNamespace := "testns"
|
||||
destinationServerURL := "https://kubernetes.svc.local"
|
||||
applicationNamespace := "argocd-ns"
|
||||
expectedSA := "system:serviceaccount:testns:test-sa"
|
||||
|
||||
f := setup(destinationServiceAccounts, destinationNamespace, destinationServerURL, applicationNamespace)
|
||||
// when
|
||||
sa, err := deriveServiceAccountToImpersonate(f.project, f.application)
|
||||
|
||||
// then, there should not be any error and the right service account must be returned.
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedSA, sa)
|
||||
})
|
||||
|
||||
t.Run("first match to be used when multiple matches are available", func(t *testing.T) {
|
||||
// given an application referring a project with multiple destination service accounts and multiple matches for application destination
|
||||
t.Parallel()
|
||||
destinationServiceAccounts := []v1alpha1.ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "testns",
|
||||
DefaultServiceAccount: "test-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "testns",
|
||||
DefaultServiceAccount: "test-sa-2",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "default",
|
||||
DefaultServiceAccount: "default-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "guestbook",
|
||||
DefaultServiceAccount: "guestbook-sa",
|
||||
},
|
||||
}
|
||||
destinationNamespace := "testns"
|
||||
destinationServerURL := "https://kubernetes.svc.local"
|
||||
applicationNamespace := "argocd-ns"
|
||||
expectedSA := "system:serviceaccount:testns:test-sa"
|
||||
|
||||
f := setup(destinationServiceAccounts, destinationNamespace, destinationServerURL, applicationNamespace)
|
||||
// when
|
||||
sa, err := deriveServiceAccountToImpersonate(f.project, f.application)
|
||||
|
||||
// then, there should not be any error and first matching service account should be used
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedSA, sa)
|
||||
})
|
||||
|
||||
t.Run("first match to be used when glob pattern is used", func(t *testing.T) {
|
||||
// given an application referring a project with multiple destination service accounts with a matching glob pattern and exact match
|
||||
t.Parallel()
|
||||
destinationServiceAccounts := []v1alpha1.ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "test*",
|
||||
DefaultServiceAccount: "test-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "testns",
|
||||
DefaultServiceAccount: "test-sa-2",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "default",
|
||||
DefaultServiceAccount: "default-sa",
|
||||
},
|
||||
}
|
||||
destinationNamespace := "testns"
|
||||
destinationServerURL := "https://kubernetes.svc.local"
|
||||
applicationNamespace := "argocd-ns"
|
||||
expectedSA := "system:serviceaccount:testns:test-sa"
|
||||
|
||||
f := setup(destinationServiceAccounts, destinationNamespace, destinationServerURL, applicationNamespace)
|
||||
// when
|
||||
sa, err := deriveServiceAccountToImpersonate(f.project, f.application)
|
||||
assert.Equal(t, expectedSA, sa)
|
||||
|
||||
// then, there should not be any error and the service account of the glob pattern, being the first match should be returned.
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("no match among a valid list", func(t *testing.T) {
|
||||
// given an application referring a project with multiple destination service accounts with no match
|
||||
t.Parallel()
|
||||
destinationServiceAccounts := []v1alpha1.ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "testns",
|
||||
DefaultServiceAccount: "test-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://abc.svc.local",
|
||||
Namespace: "testns",
|
||||
DefaultServiceAccount: "test-sa-2",
|
||||
},
|
||||
{
|
||||
Server: "https://cde.svc.local",
|
||||
Namespace: "default",
|
||||
DefaultServiceAccount: "default-sa",
|
||||
},
|
||||
}
|
||||
destinationNamespace := "testns"
|
||||
destinationServerURL := "https://xyz.svc.local"
|
||||
applicationNamespace := "argocd-ns"
|
||||
expectedSA := ""
|
||||
expectedErr := "no matching service account found for destination server https://xyz.svc.local and namespace testns"
|
||||
|
||||
f := setup(destinationServiceAccounts, destinationNamespace, destinationServerURL, applicationNamespace)
|
||||
// when
|
||||
sa, err := deriveServiceAccountToImpersonate(f.project, f.application)
|
||||
|
||||
// then, there an error with appropriate message must be returned
|
||||
require.EqualError(t, err, expectedErr)
|
||||
assert.Equal(t, expectedSA, sa)
|
||||
})
|
||||
|
||||
t.Run("match done via catch all glob pattern", func(t *testing.T) {
|
||||
// given an application referring a project with multiple destination service accounts with matching catch all glob pattern
|
||||
t.Parallel()
|
||||
destinationServiceAccounts := []v1alpha1.ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "testns1",
|
||||
DefaultServiceAccount: "test-sa-2",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "default",
|
||||
DefaultServiceAccount: "default-sa",
|
||||
},
|
||||
{
|
||||
Server: "*",
|
||||
Namespace: "*",
|
||||
DefaultServiceAccount: "test-sa",
|
||||
},
|
||||
}
|
||||
destinationNamespace := "testns"
|
||||
destinationServerURL := "https://localhost:6443"
|
||||
applicationNamespace := "argocd-ns"
|
||||
expectedSA := "system:serviceaccount:testns:test-sa"
|
||||
|
||||
f := setup(destinationServiceAccounts, destinationNamespace, destinationServerURL, applicationNamespace)
|
||||
// when
|
||||
sa, err := deriveServiceAccountToImpersonate(f.project, f.application)
|
||||
|
||||
// then, there should not be any error and the service account of the glob pattern match must be returned.
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedSA, sa)
|
||||
})
|
||||
|
||||
t.Run("match done via invalid glob pattern", func(t *testing.T) {
|
||||
// given an application referring a project with a destination service account having an invalid glob pattern for server
|
||||
t.Parallel()
|
||||
destinationServiceAccounts := []v1alpha1.ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "e[[a*",
|
||||
Namespace: "test-ns",
|
||||
DefaultServiceAccount: "test-sa",
|
||||
},
|
||||
}
|
||||
destinationNamespace := "testns"
|
||||
destinationServerURL := "https://kubernetes.svc.local"
|
||||
applicationNamespace := "argocd-ns"
|
||||
expectedSA := ""
|
||||
|
||||
f := setup(destinationServiceAccounts, destinationNamespace, destinationServerURL, applicationNamespace)
|
||||
// when
|
||||
sa, err := deriveServiceAccountToImpersonate(f.project, f.application)
|
||||
|
||||
// then, there must be an error as the glob pattern is invalid.
|
||||
require.ErrorContains(t, err, "invalid glob pattern for destination server")
|
||||
assert.Equal(t, expectedSA, sa)
|
||||
})
|
||||
|
||||
t.Run("sa specified with a namespace", func(t *testing.T) {
|
||||
// given app sync impersonation feature is enabled and matching service account is prefixed with a namespace
|
||||
t.Parallel()
|
||||
destinationServiceAccounts := []v1alpha1.ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "https://abc.svc.local",
|
||||
Namespace: "testns",
|
||||
DefaultServiceAccount: "myns:test-sa",
|
||||
},
|
||||
{
|
||||
Server: "https://kubernetes.svc.local",
|
||||
Namespace: "default",
|
||||
DefaultServiceAccount: "default-sa",
|
||||
},
|
||||
{
|
||||
Server: "*",
|
||||
Namespace: "*",
|
||||
DefaultServiceAccount: "test-sa",
|
||||
},
|
||||
}
|
||||
destinationNamespace := "testns"
|
||||
destinationServerURL := "https://abc.svc.local"
|
||||
applicationNamespace := "argocd-ns"
|
||||
expectedSA := "system:serviceaccount:myns:test-sa"
|
||||
|
||||
f := setup(destinationServiceAccounts, destinationNamespace, destinationServerURL, applicationNamespace)
|
||||
// when
|
||||
sa, err := deriveServiceAccountToImpersonate(f.project, f.application)
|
||||
|
||||
// then, there should not be any error and the service account with the given namespace prefix must be returned.
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedSA, sa)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSyncWithImpersonate(t *testing.T) {
|
||||
type fixture struct {
|
||||
project *v1alpha1.AppProject
|
||||
application *v1alpha1.Application
|
||||
controller *ApplicationController
|
||||
}
|
||||
|
||||
setup := func(impersonationEnabled bool, destinationNamespace, serviceAccountName string) *fixture {
|
||||
app := newFakeApp()
|
||||
app.Status.OperationState = nil
|
||||
app.Status.History = nil
|
||||
project := &v1alpha1.AppProject{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: test.FakeArgoCDNamespace,
|
||||
Name: "default",
|
||||
},
|
||||
Spec: v1alpha1.AppProjectSpec{
|
||||
DestinationServiceAccounts: []v1alpha1.
|
||||
ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "https://localhost:6443",
|
||||
Namespace: destinationNamespace,
|
||||
DefaultServiceAccount: serviceAccountName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
additionalObjs := []runtime.Object{}
|
||||
if serviceAccountName != "" {
|
||||
syncServiceAccount := &corev1.ServiceAccount{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: serviceAccountName,
|
||||
Namespace: test.FakeDestNamespace,
|
||||
},
|
||||
}
|
||||
additionalObjs = append(additionalObjs, syncServiceAccount)
|
||||
}
|
||||
data := fakeData{
|
||||
apps: []runtime.Object{app, project},
|
||||
manifestResponse: &apiclient.ManifestResponse{
|
||||
Manifests: []string{},
|
||||
Namespace: test.FakeDestNamespace,
|
||||
Server: "https://localhost:6443",
|
||||
Revision: "abc123",
|
||||
},
|
||||
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{},
|
||||
configMapData: map[string]string{
|
||||
"application.sync.impersonation.enabled": strconv.FormatBool(impersonationEnabled),
|
||||
},
|
||||
additionalObjs: additionalObjs,
|
||||
}
|
||||
ctrl := newFakeController(&data, nil)
|
||||
return &fixture{
|
||||
project: project,
|
||||
application: app,
|
||||
controller: ctrl,
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("sync with impersonation and no matching service account", func(t *testing.T) {
|
||||
// given app sync impersonation feature is enabled with an application referring a project no matching service account
|
||||
f := setup(true, test.FakeArgoCDNamespace, "")
|
||||
opMessage := "failed to find a matching service account to impersonate: no matching service account found for destination server https://localhost:6443 and namespace fake-dest-ns"
|
||||
|
||||
opState := &v1alpha1.OperationState{
|
||||
Operation: v1alpha1.Operation{
|
||||
Sync: &v1alpha1.SyncOperation{
|
||||
Source: &v1alpha1.ApplicationSource{},
|
||||
},
|
||||
},
|
||||
Phase: common.OperationRunning,
|
||||
}
|
||||
// when
|
||||
f.controller.appStateManager.SyncAppState(f.application, opState)
|
||||
|
||||
// then, app sync should fail with expected error message in operation state
|
||||
assert.Equal(t, common.OperationError, opState.Phase)
|
||||
assert.Contains(t, opState.Message, opMessage)
|
||||
})
|
||||
|
||||
t.Run("sync with impersonation and empty service account match", func(t *testing.T) {
|
||||
// given app sync impersonation feature is enabled with an application referring a project matching service account that is an empty string
|
||||
f := setup(true, test.FakeDestNamespace, "")
|
||||
opMessage := "failed to find a matching service account to impersonate: default service account contains invalid chars ''"
|
||||
|
||||
opState := &v1alpha1.OperationState{
|
||||
Operation: v1alpha1.Operation{
|
||||
Sync: &v1alpha1.SyncOperation{
|
||||
Source: &v1alpha1.ApplicationSource{},
|
||||
},
|
||||
},
|
||||
Phase: common.OperationRunning,
|
||||
}
|
||||
// when
|
||||
f.controller.appStateManager.SyncAppState(f.application, opState)
|
||||
|
||||
// then app sync should fail with expected error message in operation state
|
||||
assert.Equal(t, common.OperationError, opState.Phase)
|
||||
assert.Contains(t, opState.Message, opMessage)
|
||||
})
|
||||
|
||||
t.Run("sync with impersonation and matching sa", func(t *testing.T) {
|
||||
// given app sync impersonation feature is enabled with an application referring a project matching service account
|
||||
f := setup(true, test.FakeDestNamespace, "test-sa")
|
||||
opMessage := "successfully synced (no more tasks)"
|
||||
|
||||
opState := &v1alpha1.OperationState{
|
||||
Operation: v1alpha1.Operation{
|
||||
Sync: &v1alpha1.SyncOperation{
|
||||
Source: &v1alpha1.ApplicationSource{},
|
||||
},
|
||||
},
|
||||
Phase: common.OperationRunning,
|
||||
}
|
||||
// when
|
||||
f.controller.appStateManager.SyncAppState(f.application, opState)
|
||||
|
||||
// then app sync should not fail
|
||||
assert.Equal(t, common.OperationSucceeded, opState.Phase)
|
||||
assert.Contains(t, opState.Message, opMessage)
|
||||
})
|
||||
|
||||
t.Run("sync without impersonation", func(t *testing.T) {
|
||||
// given app sync impersonation feature is disabled with an application referring a project matching service account
|
||||
f := setup(false, test.FakeDestNamespace, "")
|
||||
opMessage := "successfully synced (no more tasks)"
|
||||
|
||||
opState := &v1alpha1.OperationState{
|
||||
Operation: v1alpha1.Operation{
|
||||
Sync: &v1alpha1.SyncOperation{
|
||||
Source: &v1alpha1.ApplicationSource{},
|
||||
},
|
||||
},
|
||||
Phase: common.OperationRunning,
|
||||
}
|
||||
// when
|
||||
f.controller.appStateManager.SyncAppState(f.application, opState)
|
||||
|
||||
// then application sync should pass using the control plane service account
|
||||
assert.Equal(t, common.OperationSucceeded, opState.Phase)
|
||||
assert.Contains(t, opState.Message, opMessage)
|
||||
})
|
||||
}
|
||||
|
||||
func dig[T any](obj interface{}, path []interface{}) T {
|
||||
i := obj
|
||||
|
||||
|
||||
@@ -32,23 +32,41 @@ function initializeVersionDropdown() {
|
||||
window[callbackName] = function(response) {
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = response.html;
|
||||
document.querySelector(".md-header__inner > .md-header__title").appendChild(div);
|
||||
const headerTitle = document.querySelector(".md-header__inner > .md-header__title");
|
||||
if (headerTitle) {
|
||||
headerTitle.appendChild(div);
|
||||
}
|
||||
|
||||
const container = div.querySelector('.rst-versions');
|
||||
if (!container) return; // Exit if container not found
|
||||
|
||||
// Add caret icon
|
||||
var caret = document.createElement('div');
|
||||
caret.innerHTML = "<i class='fa fa-caret-down dropdown-caret'></i>";
|
||||
caret.classList.add('dropdown-caret');
|
||||
div.querySelector('.rst-current-version').appendChild(caret);
|
||||
const currentVersionElem = div.querySelector('.rst-current-version');
|
||||
if (currentVersionElem) {
|
||||
currentVersionElem.appendChild(caret);
|
||||
}
|
||||
|
||||
div.querySelector('.rst-current-version').addEventListener('click', function() {
|
||||
container.classList.toggle('shift-up');
|
||||
});
|
||||
// Add click listener to toggle dropdown
|
||||
if (currentVersionElem && container) {
|
||||
currentVersionElem.addEventListener('click', function() {
|
||||
container.classList.toggle('shift-up');
|
||||
});
|
||||
}
|
||||
|
||||
// Sorting Logic
|
||||
sortVersionLinks(container);
|
||||
};
|
||||
|
||||
// Load CSS
|
||||
var CSSLink = document.createElement('link');
|
||||
CSSLink.rel = 'stylesheet';
|
||||
CSSLink.href = '/assets/versions.css';
|
||||
document.getElementsByTagName('head')[0].appendChild(CSSLink);
|
||||
|
||||
// Load JSONP Script
|
||||
var script = document.createElement('script');
|
||||
const currentVersion = getCurrentVersion();
|
||||
script.src = 'https://argo-cd.readthedocs.io/_/api/v2/footer_html/?' +
|
||||
@@ -56,6 +74,58 @@ function initializeVersionDropdown() {
|
||||
document.getElementsByTagName('head')[0].appendChild(script);
|
||||
}
|
||||
|
||||
// Function to sort version links
|
||||
function sortVersionLinks(container) {
|
||||
// Find all <dl> elements within the container
|
||||
const dlElements = container.querySelectorAll('dl');
|
||||
|
||||
dlElements.forEach(dl => {
|
||||
const dt = dl.querySelector('dt');
|
||||
if (dt && dt.textContent.trim().toLowerCase() === 'versions') {
|
||||
// Found the Versions <dl>
|
||||
const ddElements = Array.from(dl.querySelectorAll('dd'));
|
||||
|
||||
// Define sorting criteria
|
||||
ddElements.sort((a, b) => {
|
||||
const aText = a.textContent.trim().toLowerCase();
|
||||
const bText = b.textContent.trim().toLowerCase();
|
||||
|
||||
// Prioritize 'latest' and 'stable'
|
||||
if (aText === 'latest') return -1;
|
||||
if (bText === 'latest') return 1;
|
||||
if (aText === 'stable') return -1;
|
||||
if (bText === 'stable') return 1;
|
||||
|
||||
// Extract version numbers (e.g., release-2.9)
|
||||
const aVersionMatch = aText.match(/release-(\d+(\.\d+)*)/);
|
||||
const bVersionMatch = bText.match(/release-(\d+(\.\d+)*)/);
|
||||
|
||||
if (aVersionMatch && bVersionMatch) {
|
||||
const aVersion = aVersionMatch[1].split('.').map(Number);
|
||||
const bVersion = bVersionMatch[1].split('.').map(Number);
|
||||
|
||||
for (let i = 0; i < Math.max(aVersion.length, bVersion.length); i++) {
|
||||
const aNum = aVersion[i] || 0;
|
||||
const bNum = bVersion[i] || 0;
|
||||
if (aNum > bNum) return -1;
|
||||
if (aNum < bNum) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Fallback to alphabetical order
|
||||
return aText.localeCompare(bText);
|
||||
});
|
||||
|
||||
// Remove existing <dd> elements
|
||||
ddElements.forEach(dd => dl.removeChild(dd));
|
||||
|
||||
// Append sorted <dd> elements
|
||||
ddElements.forEach(dd => dl.appendChild(dd));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// VERSION WARNINGS
|
||||
window.addEventListener("DOMContentLoaded", function() {
|
||||
var margin = 30;
|
||||
|
||||
@@ -60,7 +60,38 @@ data:
|
||||
server: https://some-cluster
|
||||
```
|
||||
|
||||
Note: There is no need to restart Argo CD Server after modifiying the
|
||||
Proxy extensions can also be provided individually using dedicated
|
||||
Argo CD configmap keys for better GitOps operations. The example below
|
||||
demonstrates how to configure the same hypothetical httpbin config
|
||||
above using a dedicated key:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: argocd-cm
|
||||
namespace: argocd
|
||||
data:
|
||||
extension.config.httpbin: |
|
||||
connectionTimeout: 2s
|
||||
keepAlive: 15s
|
||||
idleConnectionTimeout: 60s
|
||||
maxIdleConnections: 30
|
||||
services:
|
||||
- url: http://httpbin.org
|
||||
headers:
|
||||
- name: some-header
|
||||
value: '$some.argocd.secret.key'
|
||||
cluster:
|
||||
name: some-cluster
|
||||
server: https://some-cluster
|
||||
```
|
||||
|
||||
Attention: Extension names must be unique in the Argo CD configmap. If
|
||||
duplicated keys are found, the Argo CD API server will log an error
|
||||
message and no proxy extension will be registered.
|
||||
|
||||
Note: There is no need to restart Argo CD Server after modifying the
|
||||
`extension.config` entry in Argo CD configmap. Changes will be
|
||||
automatically applied. A new proxy registry will be built making
|
||||
all new incoming extensions requests (`<argocd-host>/extensions/*`) to
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Application Sync using impersonation
|
||||
|
||||
!!! warning "Alpha Feature"
|
||||
This is an experimental, alpha-quality feature that allows you to control the service account used for the sync operation. The configured service account, could have lesser privileges required for creating resources compared to the highly privileged access required for the control plane operations.
|
||||
This is an experimental, alpha-quality feature that allows you to control the service account used for the sync operation. The configured service account could have lesser privileges required for creating resources compared to the highly privileged access required for the control plane operations.
|
||||
|
||||
!!! warning
|
||||
Please read this documentation carefully before you enable this feature. Misconfiguration could lead to potential security issues.
|
||||
@@ -94,7 +94,7 @@ spec:
|
||||
sourceRepos:
|
||||
- '*'
|
||||
destinations:
|
||||
- *
|
||||
- '*'
|
||||
destinationServiceAccounts:
|
||||
- server: https://kubernetes.default.svc
|
||||
namespace: guestbook
|
||||
|
||||
@@ -42,6 +42,7 @@ When the ApplicationSet changes, the changes will be applied to each group of Ap
|
||||
* Sync operations are triggered the same way as if they were triggered by the UI or CLI (by directly setting the `operation` status field on the Application resource). This means that a RollingSync will respect sync windows just as if a user had clicked the "Sync" button in the Argo UI.
|
||||
* When a sync is triggered, the sync is performed with the same syncPolicy configured for the Application. For example, this preserves the Application's retry settings.
|
||||
* If an Application is considered "Pending" for `applicationsetcontroller.default.application.progressing.timeout` seconds, the Application is automatically moved to Healthy status (default 300).
|
||||
* If an Application is not selected in any step, it will be excluded from the rolling sync and needs to be manually synced through the CLI or UI.
|
||||
|
||||
#### Example
|
||||
The following example illustrates how to stage a progressive sync over Applications with explicitly configured environment labels.
|
||||
|
||||
@@ -283,6 +283,9 @@ data:
|
||||
# - annotation+label : Also uses an annotation for tracking, but additionally labels the resource with the application name
|
||||
application.resourceTrackingMethod: annotation
|
||||
|
||||
# Optional installation id. Allows to have multiple installations of Argo CD in the same cluster.
|
||||
installationID: "my-unique-id"
|
||||
|
||||
# disables admin user. Admin is enabled by default
|
||||
admin.enabled: "false"
|
||||
# add an additional local user with apiKey and login capabilities
|
||||
@@ -329,14 +332,14 @@ data:
|
||||
# spread out the refreshes and give time to the repo-server to catch up. The jitter is the maximum duration that can be
|
||||
# added to the sync timeout. So, if the sync timeout is 3 minutes and the jitter is 1 minute, then the actual timeout will
|
||||
# be between 3 and 4 minutes. Disabled when the value is 0, defaults to 0.
|
||||
timeout.reconciliation.jitter: 0
|
||||
timeout.reconciliation.jitter: "0"
|
||||
|
||||
# cluster.inClusterEnabled indicates whether to allow in-cluster server address. This is enabled by default.
|
||||
cluster.inClusterEnabled: "true"
|
||||
|
||||
# The maximum number of pod logs to render in UI. If the application has more than this number of pods, the logs will not be rendered.
|
||||
# This is to prevent the UI from becoming unresponsive when rendering a large number of logs. Default is 10.
|
||||
server.maxPodLogsToRender: 10
|
||||
server.maxPodLogsToRender: "10"
|
||||
|
||||
# Application pod logs RBAC enforcement enables control over who can and who can't view application pod logs.
|
||||
# When you enable the switch, pod logs will be visible only to admin role by default. Other roles/users will not be able to view them via cli and UI.
|
||||
@@ -425,7 +428,7 @@ data:
|
||||
name: some-cluster
|
||||
server: https://some-cluster
|
||||
# The maximum size of the payload that can be sent to the webhook server.
|
||||
webhook.maxPayloadSizeMB: 1024
|
||||
webhook.maxPayloadSizeMB: "1024"
|
||||
|
||||
# application.sync.impersonation.enabled indicates whether the application sync can be decoupled from control plane service account using impersonation.
|
||||
# application.sync.impersonation.enabled enables application sync to use a custom service account, via impersonation. This allows decoupling sync from control-plane service account.
|
||||
application.sync.impersonation.enabled: "false"
|
||||
|
||||
@@ -47,8 +47,11 @@ data:
|
||||
controller.log.level: "info"
|
||||
# Prometheus metrics cache expiration (disabled by default. e.g. 24h0m0s)
|
||||
controller.metrics.cache.expiration: "24h0m0s"
|
||||
# Specifies timeout between application self heal attempts (default 5)
|
||||
controller.self.heal.timeout.seconds: "5"
|
||||
# Specifies exponential backoff timeout parameters between application self heal attempts
|
||||
controller.self.heal.timeout.seconds: "2"
|
||||
controller.self.heal.backoff.factor: "3"
|
||||
controller.self.heal.backoff.cap.seconds: "300"
|
||||
|
||||
# Cache expiration for app state (default 1h0m0s)
|
||||
controller.app.state.cache.expiration: "1h0m0s"
|
||||
# Specifies if resource health should be persisted in app CRD (default true)
|
||||
|
||||
@@ -98,20 +98,27 @@ data:
|
||||
return hs
|
||||
```
|
||||
|
||||
In order to prevent duplication of the custom health check for potentially multiple resources, it is also possible to specify a wildcard in the resource kind, and anywhere in the resource group, like this:
|
||||
In order to prevent duplication of custom health checks for potentially multiple resources, it is also possible to
|
||||
specify a wildcard in the resource kind, and anywhere in the resource group, like this:
|
||||
|
||||
```yaml
|
||||
resource.customizations.health.ec2.aws.crossplane.io_*: |
|
||||
...
|
||||
resource.customizations: |
|
||||
ec2.aws.crossplane.io/*:
|
||||
health.lua: |
|
||||
...
|
||||
```
|
||||
|
||||
```yaml
|
||||
resource.customizations.health.*.aws.crossplane.io_*: |
|
||||
...
|
||||
# If a key _begins_ with a wildcard, please ensure that the GVK key is quoted.
|
||||
resource.customizations: |
|
||||
"*.aws.crossplane.io/*":
|
||||
health.lua: |
|
||||
...
|
||||
```
|
||||
|
||||
!!!important
|
||||
Please, note that there can be ambiguous resolution of wildcards, see [#16905](https://github.com/argoproj/argo-cd/issues/16905)
|
||||
Please, note that wildcards are only supported when using the `resource.customizations` key, the `resource.customizations.health.<group>_<kind>`
|
||||
style keys do not work since wildcards (`*`) are not supported in Kubernetes configmap keys.
|
||||
|
||||
The `obj` is a global variable which contains the resource. The script must return an object with status and optional message field.
|
||||
The custom health check might return one of the following health statuses:
|
||||
@@ -121,7 +128,7 @@ The custom health check might return one of the following health statuses:
|
||||
* `Degraded` - the resource is degraded
|
||||
* `Suspended` - the resource is suspended and waiting for some external event to resume (e.g. suspended CronJob or paused Deployment)
|
||||
|
||||
By default health typically returns `Progressing` status.
|
||||
By default, health typically returns a `Progressing` status.
|
||||
|
||||
NOTE: As a security measure, access to the standard Lua libraries will be disabled by default. Admins can control access by
|
||||
setting `resource.customizations.useOpenLibs.<group>_<kind>`. In the following example, standard libraries are enabled for health check of `cert-manager.io/Certificate`.
|
||||
|
||||
@@ -122,9 +122,19 @@ To do so, when the action if performed on an application's resource, the `<actio
|
||||
For instance, to grant access to `example-user` to only delete Pods in the `prod-app` Application, the policy could be:
|
||||
|
||||
```csv
|
||||
p, example-user, applications, delete/*/Pod/*, default/prod-app, allow
|
||||
p, example-user, applications, delete/*/Pod/*/*, default/prod-app, allow
|
||||
```
|
||||
|
||||
!!!warning "Understand glob pattern behavior"
|
||||
|
||||
Argo CD RBAC does not use `/` as a separator when evaluating glob patterns. So the pattern `delete/*/kind/*`
|
||||
will match `delete/<group>/kind/<namespace>/<name>` but also `delete/<group>/<kind>/kind/<name>`.
|
||||
|
||||
The fact that both of these match will generally not be a problem, because resource kinds generally contain capital
|
||||
letters, and namespaces cannot contain capital letters. However, it is possible for a resource kind to be lowercase.
|
||||
So it is better to just always include all the parts of the resource in the pattern (in other words, always use four
|
||||
slashes).
|
||||
|
||||
If we want to grant access to the user to update all resources of an application, but not the application itself:
|
||||
|
||||
```csv
|
||||
@@ -135,7 +145,7 @@ If we want to explicitly deny delete of the application, but allow the user to d
|
||||
|
||||
```csv
|
||||
p, example-user, applications, delete, default/prod-app, deny
|
||||
p, example-user, applications, delete/*/Pod/*, default/prod-app, allow
|
||||
p, example-user, applications, delete/*/Pod/*/*, default/prod-app, allow
|
||||
```
|
||||
|
||||
!!! note
|
||||
@@ -145,7 +155,7 @@ p, example-user, applications, delete/*/Pod/*, default/prod-app, allow
|
||||
|
||||
```csv
|
||||
p, example-user, applications, delete, default/prod-app, allow
|
||||
p, example-user, applications, delete/*/Pod/*, default/prod-app, deny
|
||||
p, example-user, applications, delete/*/Pod/*/*, default/prod-app, deny
|
||||
```
|
||||
|
||||
#### The `action` action
|
||||
|
||||
@@ -31,6 +31,7 @@ argocd-application-controller [flags]
|
||||
--default-cache-expiration duration Cache expiration default (default 24h0m0s)
|
||||
--disable-compression If true, opt-out of response compression for all requests to the server
|
||||
--dynamic-cluster-distribution-enabled Enables dynamic cluster distribution.
|
||||
--enable-k8s-event none Enable ArgoCD to use k8s event. For disabling all events, set the value as none. (e.g --enable-k8s-event=none), For enabling specific events, set the value as `event reason`. (e.g --enable-k8s-event=StatusRefreshed,ResourceCreated) (default [all])
|
||||
--gloglevel int Set the glog logging level
|
||||
-h, --help help for argocd-application-controller
|
||||
--ignore-normalizer-jq-execution-timeout-seconds duration Set ignore normalizer JQ execution timeout
|
||||
@@ -66,7 +67,10 @@ argocd-application-controller [flags]
|
||||
--repo-server-strict-tls Whether to use strict validation of the TLS cert presented by the repo server
|
||||
--repo-server-timeout-seconds int Repo server RPC call timeout seconds. (default 60)
|
||||
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
|
||||
--self-heal-timeout-seconds int Specifies timeout between application self heal attempts (default 5)
|
||||
--self-heal-backoff-cap-seconds int Specifies max timeout of exponential backoff between application self heal attempts (default 300)
|
||||
--self-heal-backoff-factor int Specifies factor of exponential timeout between application self heal attempts (default 3)
|
||||
--self-heal-backoff-timeout-seconds int Specifies initial timeout of exponential backoff between self heal attempts (default 2)
|
||||
--self-heal-timeout-seconds int Specifies timeout between application self heal attempts
|
||||
--sentinel stringArray Redis sentinel hostname and port (e.g. argocd-redis-ha-announce-0:6379).
|
||||
--sentinelmaster string Redis sentinel master group name. (default "master")
|
||||
--server string The address and port of the Kubernetes API server
|
||||
|
||||
@@ -51,6 +51,7 @@ argocd-server [flags]
|
||||
--disable-auth Disable client authentication
|
||||
--disable-compression If true, opt-out of response compression for all requests to the server
|
||||
--enable-gzip Enable GZIP compression (default true)
|
||||
--enable-k8s-event none Enable ArgoCD to use k8s event. For disabling all events, set the value as none. (e.g --enable-k8s-event=none), For enabling specific events, set the value as `event reason`. (e.g --enable-k8s-event=StatusRefreshed,ResourceCreated) (default [all])
|
||||
--enable-proxy-extension Enable Proxy Extension feature
|
||||
--gloglevel int Set the glog logging level
|
||||
-h, --help help for argocd-server
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
This page is populated for released Argo CD versions. Use the version selector to view this table for a specific
|
||||
version.
|
||||
| Argo CD version | Kubernetes versions |
|
||||
|-----------------|---------------------|
|
||||
| 2.13 | v1.30, v1.29, v1.28, v1.27 |
|
||||
| 2.12 | v1.29, v1.28, v1.27, v1.26 |
|
||||
| 2.11 | v1.29, v1.28, v1.27, v1.26, v1.25 |
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# v2.12 to 2.13
|
||||
|
||||
## Upgraded Helm Version
|
||||
|
||||
Note that bundled Helm version has been upgraded from 3.15.2 to 3.15.4.
|
||||
|
||||
## Custom Resource Actions for Flux Resources
|
||||
|
||||
[`Custom Resource Actions`](../resource_actions.md#Custom-Resource-Actions) have been added for Flux Resources.
|
||||
|
||||
@@ -68,9 +68,9 @@ This proposal would allow ArgoCD administrators to manage the cluster permission
|
||||
|
||||
### Goals
|
||||
- Applications may only impersonate ServiceAccounts that live in the same namespace as the destination namespace configured in the application.If the service account is created in a different namespace, then the user can provide the service account name in the format `<namespace>:<service_account_name>` . ServiceAccount to be used for syncing each application is determined by the target destination configured in the `AppProject` associated with the `Application`.
|
||||
- If impersonation feature is enabled, and no service account name is provided in the associated `AppProject`, then the sync operation would fail with an appropriate error message. Users can configure a catch all service account matching all destinations to avoid such sync errors.
|
||||
- If impersonation feature is enabled, and no service account name is provided in the associated `AppProject`, then the default service account of the destination namespace of the `Application` should be used.
|
||||
- Access restrictions implemented through properties in AppProject (if done) must have the existing behavior. From a security standpoint, any restrictions that were available before switching to a service account based approach should continue to exist even when the impersonation feature is enabled.
|
||||
- The feature can be enabled/disabled only at the system level. Once enabled/disabled, it is applicable to all ArgoCD `Applications`.
|
||||
- The feature can be enabled/disabled only at the system level. Once enabled/disabled, it is applicable to all Argo CD `Applications`.
|
||||
|
||||
### Non-Goals
|
||||
|
||||
@@ -82,7 +82,7 @@ As part of this proposal, it would be possible for an ArgoCD Admin to specify a
|
||||
|
||||
When applications gets synced, based on its destination (target cluster and namespace combination), the `defaultServiceAccount` configured in the `AppProject` will be selected and used for impersonation when executing the kubectl commands for the sync operation.
|
||||
|
||||
We would be introducing a new element `destinationServiceAccounts` in `AppProject.spec`. This element is used for the sole purpose of specifying the impersonation configuration. The `defaultServiceAccount` configured for the `AppProject` would be used for the sync operation for a particular destination cluster and namespace. If impersonation feature is enabled and no specific service account is provided in the `AppProject` CR, then the sync operation will fail with an error. Users can configure a catch all service account matching all destinations to avoid such sync errors.
|
||||
We would be introducing a new element `destinationServiceAccounts` in `AppProject.spec`. This element is used for the sole purpose of specifying the impersonation configuration. The `defaultServiceAccount` configured for the `AppProject` would be used for the sync operation for a particular destination cluster and namespace. If impersonation feature is enabled and no specific service account is provided in the `AppProject` CR, then the `default` service account in the destination namespace would be used for impersonation.
|
||||
|
||||
```yaml
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
@@ -109,7 +109,7 @@ spec:
|
||||
- server: https://kubernetes.default.svc
|
||||
namespace: guestbook-stage
|
||||
defaultServiceAccount: guestbook-stage-deployer
|
||||
- server: '*
|
||||
- server: '*'
|
||||
namespace: '*'
|
||||
defaultServiceAccount: default # catch all service account to be used when all other matches fail.
|
||||
```
|
||||
@@ -161,7 +161,10 @@ So that, I can use a generic convention of naming service accounts and avoid ass
|
||||
|
||||
#### Component: ArgoCD Application Controller
|
||||
|
||||
- Provide a configuration in `argocd-cm` which can be modified to enable the Impersonation feature. Set `application.sync.impersonation.enabled: "true"` in the Argo CD ConfigMap. Default value of `application.sync.impersonation.enabled` would be `"false"` and user has to explicitly override it to use this feature.
|
||||
- Provide a configuration in `argocd-cm` which can be modified to enable the Impersonation feature. Set `applicationcontroller.enable.impersonation: true` in the Argo CD ConfigMap. Default value of `applicationcontroller.enable.impersonation` would be `false` and user has to explicitly override it to use this feature.
|
||||
- Provide an option to override the Impersonation feature using environment variables.
|
||||
Set `ARGOCD_APPLICATION_CONTROLLER_ENABLE_IMPERSONATION=true` in the Application controller environment variables. Default value of the environment variable must be `false` and user has to explicitly set it to `true` to use this feature.
|
||||
- Provide an option to enable this feature using a command line flag `--enable-impersonation`. This new argument option needs to be added to the Application controller args.
|
||||
- Fix Application Controller `sync.go` to set the Impersonate configuration from the AppProject CR to the `SyncContext` Object (rawConfig and restConfig field, need to understand which config is used for the actual sync and if both configs need to be impersonated.)
|
||||
|
||||
#### Component: ArgoCD UI
|
||||
|
||||
@@ -30,6 +30,7 @@ argocd admin proj generate-spec PROJECT [flags]
|
||||
--deny-namespaced-resource stringArray List of denied namespaced resources
|
||||
--description string Project description
|
||||
-d, --dest stringArray Permitted destination server and namespace (e.g. https://192.168.99.100:8443,default)
|
||||
--dest-service-accounts stringArray Destination server, namespace and target service account (e.g. https://192.168.99.100:8443,default,default-sa)
|
||||
-f, --file string Filename or URL to Kubernetes manifests for the project
|
||||
-h, --help help for generate-spec
|
||||
-i, --inline If set then generated resource is written back to the file specified in --file flag
|
||||
|
||||
1
docs/user-guide/commands/argocd_proj_create.md
generated
1
docs/user-guide/commands/argocd_proj_create.md
generated
@@ -27,6 +27,7 @@ argocd proj create PROJECT [flags]
|
||||
--deny-namespaced-resource stringArray List of denied namespaced resources
|
||||
--description string Project description
|
||||
-d, --dest stringArray Permitted destination server and namespace (e.g. https://192.168.99.100:8443,default)
|
||||
--dest-service-accounts stringArray Destination server, namespace and target service account (e.g. https://192.168.99.100:8443,default,default-sa)
|
||||
-f, --file string Filename or URL to Kubernetes manifests for the project
|
||||
-h, --help help for create
|
||||
--orphaned-resources Enables orphaned resources monitoring
|
||||
|
||||
1
docs/user-guide/commands/argocd_proj_set.md
generated
1
docs/user-guide/commands/argocd_proj_set.md
generated
@@ -27,6 +27,7 @@ argocd proj set PROJECT [flags]
|
||||
--deny-namespaced-resource stringArray List of denied namespaced resources
|
||||
--description string Project description
|
||||
-d, --dest stringArray Permitted destination server and namespace (e.g. https://192.168.99.100:8443,default)
|
||||
--dest-service-accounts stringArray Destination server, namespace and target service account (e.g. https://192.168.99.100:8443,default,default-sa)
|
||||
-h, --help help for set
|
||||
--orphaned-resources Enables orphaned resources monitoring
|
||||
--orphaned-resources-warn Specifies if applications should have a warning condition when orphaned resources detected
|
||||
|
||||
@@ -65,6 +65,14 @@ metadata:
|
||||
The advantages of using the tracking id annotation is that there are no clashes any
|
||||
more with other Kubernetes tools and Argo CD is never confused about the owner of a resource. The `annotation+label` can also be used if you want other tools to understand resources managed by Argo CD.
|
||||
|
||||
### Installation ID
|
||||
|
||||
If you are managing one cluster using multiple Argo CD instances, you will need to set `installationID` in the Argo CD ConfigMap. This will prevent conflicts between
|
||||
the different Argo CD instances:
|
||||
|
||||
* Each managed resource will have the annotation `argocd.argoproj.io/tracking-id: <installation-id>`
|
||||
* It is possible to have applications with the same name in Argo CD instances without causing conflicts.
|
||||
|
||||
### Non self-referencing annotations
|
||||
When using the tracking method `annotation` or `annotation+label`, Argo CD will consider the resource properties in the annotation (name, namespace, group and kind) to determine whether the resource should be compared against the desired state. If the tracking annotation does not reference the resource it is applied to, the resource will neither affect the application's sync status nor be marked for pruning.
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1bc3f354f7ce4d7fd9cfa5bcc701c1f32c88d27076d96c2792d5b5226062aee5 helm-v3.15.4-darwin-amd64.tar.gz
|
||||
@@ -0,0 +1 @@
|
||||
88115846a1fb58f8eb8f64fec5c343d95ca394f1be811602fa54a887c98730ac helm-v3.15.4-darwin-arm64.tar.gz
|
||||
@@ -0,0 +1 @@
|
||||
11400fecfc07fd6f034863e4e0c4c4445594673fd2a129e701fe41f31170cfa9 helm-v3.15.4-linux-amd64.tar.gz
|
||||
@@ -0,0 +1 @@
|
||||
fa419ecb139442e8a594c242343fafb7a46af3af34041c4eac1efcc49d74e626 helm-v3.15.4-linux-arm64.tar.gz
|
||||
@@ -0,0 +1 @@
|
||||
e4efce93723f52dd858e9046ea836c9c75f346facce1b87b8cf78c817b97e6ac helm-v3.15.4-linux-ppc64le.tar.gz
|
||||
@@ -0,0 +1 @@
|
||||
c6e0cdea598196895ac7b627ce972699ef9f06b0eba51dc4db7cc21b3369f24a helm-v3.15.4-linux-s390x.tar.gz
|
||||
@@ -11,7 +11,7 @@
|
||||
# Use ./hack/installers/checksums/add-helm-checksums.sh and
|
||||
# add-kustomize-checksums.sh to help download checksums.
|
||||
###############################################################################
|
||||
helm3_version=3.15.2
|
||||
helm3_version=3.15.4
|
||||
kubectl_version=1.17.8
|
||||
kubectx_version=0.6.3
|
||||
kustomize5_version=5.4.3
|
||||
|
||||
6
hack/update-supported-versions.sh
Normal file → Executable file
6
hack/update-supported-versions.sh
Normal file → Executable file
@@ -11,7 +11,11 @@ for n in 0 1 2; do
|
||||
minor_version_num=$((argocd_minor_version_num - n))
|
||||
minor_version="${argocd_major_version_num}.${minor_version_num}"
|
||||
git checkout "release-$minor_version" > /dev/null || exit 1
|
||||
line=$(yq '.jobs["test-e2e"].strategy.matrix["k3s-version"][]' .github/workflows/ci-build.yaml | \
|
||||
|
||||
line=$(yq '.jobs["test-e2e"].strategy.matrix |
|
||||
# k3s-version was an array prior to 2.12. This checks for the old format first and then falls back to the new format.
|
||||
(.["k3s-version"] // (.k3s | map(.version))) |
|
||||
.[]' .github/workflows/ci-build.yaml | \
|
||||
jq --arg minor_version "$minor_version" --raw-input --slurp --raw-output \
|
||||
'split("\n")[:-1] | map(sub("\\.[0-9]+$"; "")) | join(", ") | "| \($minor_version) | \(.) |"')
|
||||
out+="$line\n"
|
||||
|
||||
@@ -97,6 +97,24 @@ spec:
|
||||
name: argocd-cmd-params-cm
|
||||
key: controller.self.heal.timeout.seconds
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_TIMEOUT_SECONDS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: controller.self.heal.backoff.timeout.seconds
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_FACTOR
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: controller.self.heal.backoff.factor
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_CAP_SECONDS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: controller.self.heal.backoff.cap.seconds
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
|
||||
@@ -100,6 +100,24 @@ spec:
|
||||
name: argocd-cmd-params-cm
|
||||
key: controller.self.heal.timeout.seconds
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_TIMEOUT_SECONDS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: controller.self.heal.backoff.timeout.seconds
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_FACTOR
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: controller.self.heal.backoff.factor
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_CAP_SECONDS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: controller.self.heal.backoff.cap.seconds
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
|
||||
@@ -5,7 +5,7 @@ kind: Kustomization
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: latest
|
||||
newTag: v2.13.2
|
||||
resources:
|
||||
- ./application-controller
|
||||
- ./dex
|
||||
|
||||
43
manifests/core-install.yaml
generated
43
manifests/core-install.yaml
generated
@@ -115,6 +115,11 @@ spec:
|
||||
sync:
|
||||
description: Sync contains parameters for the operation
|
||||
properties:
|
||||
autoHealAttemptsCount:
|
||||
description: SelfHealAttemptsCount contains the number of auto-heal
|
||||
attempts
|
||||
format: int64
|
||||
type: integer
|
||||
dryRun:
|
||||
description: DryRun specifies to perform a `kubectl apply --dry-run`
|
||||
without actually performing the sync
|
||||
@@ -2710,6 +2715,11 @@ spec:
|
||||
sync:
|
||||
description: Sync contains parameters for the operation
|
||||
properties:
|
||||
autoHealAttemptsCount:
|
||||
description: SelfHealAttemptsCount contains the number
|
||||
of auto-heal attempts
|
||||
format: int64
|
||||
type: integer
|
||||
dryRun:
|
||||
description: DryRun specifies to perform a `kubectl apply
|
||||
--dry-run` without actually performing the sync
|
||||
@@ -21735,7 +21745,7 @@ spec:
|
||||
sync operation.
|
||||
properties:
|
||||
defaultServiceAccount:
|
||||
description: ServiceAccountName to be used for impersonation
|
||||
description: DefaultServiceAccount to be used for impersonation
|
||||
during the sync operation
|
||||
type: string
|
||||
namespace:
|
||||
@@ -21746,6 +21756,9 @@ spec:
|
||||
description: Server specifies the URL of the target cluster's
|
||||
Kubernetes control plane API.
|
||||
type: string
|
||||
required:
|
||||
- defaultServiceAccount
|
||||
- server
|
||||
type: object
|
||||
type: array
|
||||
destinations:
|
||||
@@ -22558,7 +22571,7 @@ spec:
|
||||
key: applicationsetcontroller.webhook.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -22676,7 +22689,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -22929,7 +22942,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -22981,7 +22994,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -23145,6 +23158,24 @@ spec:
|
||||
key: controller.self.heal.timeout.seconds
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_TIMEOUT_SECONDS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.self.heal.backoff.timeout.seconds
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_FACTOR
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.self.heal.backoff.factor
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_CAP_SECONDS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.self.heal.backoff.cap.seconds
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
@@ -23253,7 +23284,7 @@ spec:
|
||||
key: controller.ignore.normalizer.jq.timeout
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -12,4 +12,4 @@ resources:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: latest
|
||||
newTag: v2.13.2
|
||||
|
||||
10
manifests/crds/application-crd.yaml
generated
10
manifests/crds/application-crd.yaml
generated
@@ -114,6 +114,11 @@ spec:
|
||||
sync:
|
||||
description: Sync contains parameters for the operation
|
||||
properties:
|
||||
autoHealAttemptsCount:
|
||||
description: SelfHealAttemptsCount contains the number of auto-heal
|
||||
attempts
|
||||
format: int64
|
||||
type: integer
|
||||
dryRun:
|
||||
description: DryRun specifies to perform a `kubectl apply --dry-run`
|
||||
without actually performing the sync
|
||||
@@ -2709,6 +2714,11 @@ spec:
|
||||
sync:
|
||||
description: Sync contains parameters for the operation
|
||||
properties:
|
||||
autoHealAttemptsCount:
|
||||
description: SelfHealAttemptsCount contains the number
|
||||
of auto-heal attempts
|
||||
format: int64
|
||||
type: integer
|
||||
dryRun:
|
||||
description: DryRun specifies to perform a `kubectl apply
|
||||
--dry-run` without actually performing the sync
|
||||
|
||||
5
manifests/crds/appproject-crd.yaml
generated
5
manifests/crds/appproject-crd.yaml
generated
@@ -95,7 +95,7 @@ spec:
|
||||
sync operation.
|
||||
properties:
|
||||
defaultServiceAccount:
|
||||
description: ServiceAccountName to be used for impersonation
|
||||
description: DefaultServiceAccount to be used for impersonation
|
||||
during the sync operation
|
||||
type: string
|
||||
namespace:
|
||||
@@ -106,6 +106,9 @@ spec:
|
||||
description: Server specifies the URL of the target cluster's
|
||||
Kubernetes control plane API.
|
||||
type: string
|
||||
required:
|
||||
- defaultServiceAccount
|
||||
- server
|
||||
type: object
|
||||
type: array
|
||||
destinations:
|
||||
|
||||
@@ -12,7 +12,7 @@ patches:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: latest
|
||||
newTag: v2.13.2
|
||||
resources:
|
||||
- ../../base/application-controller
|
||||
- ../../base/applicationset-controller
|
||||
|
||||
49
manifests/ha/install.yaml
generated
49
manifests/ha/install.yaml
generated
@@ -115,6 +115,11 @@ spec:
|
||||
sync:
|
||||
description: Sync contains parameters for the operation
|
||||
properties:
|
||||
autoHealAttemptsCount:
|
||||
description: SelfHealAttemptsCount contains the number of auto-heal
|
||||
attempts
|
||||
format: int64
|
||||
type: integer
|
||||
dryRun:
|
||||
description: DryRun specifies to perform a `kubectl apply --dry-run`
|
||||
without actually performing the sync
|
||||
@@ -2710,6 +2715,11 @@ spec:
|
||||
sync:
|
||||
description: Sync contains parameters for the operation
|
||||
properties:
|
||||
autoHealAttemptsCount:
|
||||
description: SelfHealAttemptsCount contains the number
|
||||
of auto-heal attempts
|
||||
format: int64
|
||||
type: integer
|
||||
dryRun:
|
||||
description: DryRun specifies to perform a `kubectl apply
|
||||
--dry-run` without actually performing the sync
|
||||
@@ -21735,7 +21745,7 @@ spec:
|
||||
sync operation.
|
||||
properties:
|
||||
defaultServiceAccount:
|
||||
description: ServiceAccountName to be used for impersonation
|
||||
description: DefaultServiceAccount to be used for impersonation
|
||||
during the sync operation
|
||||
type: string
|
||||
namespace:
|
||||
@@ -21746,6 +21756,9 @@ spec:
|
||||
description: Server specifies the URL of the target cluster's
|
||||
Kubernetes control plane API.
|
||||
type: string
|
||||
required:
|
||||
- defaultServiceAccount
|
||||
- server
|
||||
type: object
|
||||
type: array
|
||||
destinations:
|
||||
@@ -23901,7 +23914,7 @@ spec:
|
||||
key: applicationsetcontroller.webhook.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -24036,7 +24049,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -24124,7 +24137,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -24243,7 +24256,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -24524,7 +24537,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -24576,7 +24589,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -24930,7 +24943,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -25130,6 +25143,24 @@ spec:
|
||||
key: controller.self.heal.timeout.seconds
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_TIMEOUT_SECONDS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.self.heal.backoff.timeout.seconds
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_FACTOR
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.self.heal.backoff.factor
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_CAP_SECONDS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.self.heal.backoff.cap.seconds
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
@@ -25238,7 +25269,7 @@ spec:
|
||||
key: controller.ignore.normalizer.jq.timeout
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
34
manifests/ha/namespace-install.yaml
generated
34
manifests/ha/namespace-install.yaml
generated
@@ -1694,7 +1694,7 @@ spec:
|
||||
key: applicationsetcontroller.webhook.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -1829,7 +1829,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -1917,7 +1917,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -2036,7 +2036,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -2317,7 +2317,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -2369,7 +2369,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -2723,7 +2723,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -2923,6 +2923,24 @@ spec:
|
||||
key: controller.self.heal.timeout.seconds
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_TIMEOUT_SECONDS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.self.heal.backoff.timeout.seconds
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_FACTOR
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.self.heal.backoff.factor
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_CAP_SECONDS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.self.heal.backoff.cap.seconds
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
@@ -3031,7 +3049,7 @@ spec:
|
||||
key: controller.ignore.normalizer.jq.timeout
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
49
manifests/install.yaml
generated
49
manifests/install.yaml
generated
@@ -115,6 +115,11 @@ spec:
|
||||
sync:
|
||||
description: Sync contains parameters for the operation
|
||||
properties:
|
||||
autoHealAttemptsCount:
|
||||
description: SelfHealAttemptsCount contains the number of auto-heal
|
||||
attempts
|
||||
format: int64
|
||||
type: integer
|
||||
dryRun:
|
||||
description: DryRun specifies to perform a `kubectl apply --dry-run`
|
||||
without actually performing the sync
|
||||
@@ -2710,6 +2715,11 @@ spec:
|
||||
sync:
|
||||
description: Sync contains parameters for the operation
|
||||
properties:
|
||||
autoHealAttemptsCount:
|
||||
description: SelfHealAttemptsCount contains the number
|
||||
of auto-heal attempts
|
||||
format: int64
|
||||
type: integer
|
||||
dryRun:
|
||||
description: DryRun specifies to perform a `kubectl apply
|
||||
--dry-run` without actually performing the sync
|
||||
@@ -21735,7 +21745,7 @@ spec:
|
||||
sync operation.
|
||||
properties:
|
||||
defaultServiceAccount:
|
||||
description: ServiceAccountName to be used for impersonation
|
||||
description: DefaultServiceAccount to be used for impersonation
|
||||
during the sync operation
|
||||
type: string
|
||||
namespace:
|
||||
@@ -21746,6 +21756,9 @@ spec:
|
||||
description: Server specifies the URL of the target cluster's
|
||||
Kubernetes control plane API.
|
||||
type: string
|
||||
required:
|
||||
- defaultServiceAccount
|
||||
- server
|
||||
type: object
|
||||
type: array
|
||||
destinations:
|
||||
@@ -23018,7 +23031,7 @@ spec:
|
||||
key: applicationsetcontroller.webhook.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -23153,7 +23166,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -23241,7 +23254,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -23341,7 +23354,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -23594,7 +23607,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -23646,7 +23659,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -23998,7 +24011,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -24198,6 +24211,24 @@ spec:
|
||||
key: controller.self.heal.timeout.seconds
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_TIMEOUT_SECONDS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.self.heal.backoff.timeout.seconds
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_FACTOR
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.self.heal.backoff.factor
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_CAP_SECONDS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.self.heal.backoff.cap.seconds
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
@@ -24306,7 +24337,7 @@ spec:
|
||||
key: controller.ignore.normalizer.jq.timeout
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
34
manifests/namespace-install.yaml
generated
34
manifests/namespace-install.yaml
generated
@@ -811,7 +811,7 @@ spec:
|
||||
key: applicationsetcontroller.webhook.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -946,7 +946,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -1034,7 +1034,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -1134,7 +1134,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -1387,7 +1387,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -1439,7 +1439,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -1791,7 +1791,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -1991,6 +1991,24 @@ spec:
|
||||
key: controller.self.heal.timeout.seconds
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_TIMEOUT_SECONDS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.self.heal.backoff.timeout.seconds
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_FACTOR
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.self.heal.backoff.factor
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_SELF_HEAL_BACKOFF_CAP_SECONDS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.self.heal.backoff.cap.seconds
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_CONTROLLER_REPO_SERVER_PLAINTEXT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
@@ -2099,7 +2117,7 @@ spec:
|
||||
key: controller.ignore.normalizer.jq.timeout
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
image: quay.io/argoproj/argocd:v2.13.2
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
211
pkg/apiclient/settings/settings.pb.go
generated
211
pkg/apiclient/settings/settings.pb.go
generated
@@ -102,6 +102,7 @@ type Settings struct {
|
||||
ControllerNamespace string `protobuf:"bytes,23,opt,name=controllerNamespace,proto3" json:"controllerNamespace,omitempty"`
|
||||
AppsInAnyNamespaceEnabled bool `protobuf:"varint,24,opt,name=appsInAnyNamespaceEnabled,proto3" json:"appsInAnyNamespaceEnabled,omitempty"`
|
||||
ImpersonationEnabled bool `protobuf:"varint,25,opt,name=impersonationEnabled,proto3" json:"impersonationEnabled,omitempty"`
|
||||
InstallationID string `protobuf:"bytes,26,opt,name=installationID,proto3" json:"installationID,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
@@ -315,6 +316,13 @@ func (m *Settings) GetImpersonationEnabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Settings) GetInstallationID() string {
|
||||
if m != nil {
|
||||
return m.InstallationID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type GoogleAnalyticsConfig struct {
|
||||
TrackingID string `protobuf:"bytes,1,opt,name=trackingID,proto3" json:"trackingID,omitempty"`
|
||||
AnonymizeUsers bool `protobuf:"varint,2,opt,name=anonymizeUsers,proto3" json:"anonymizeUsers,omitempty"`
|
||||
@@ -748,84 +756,86 @@ func init() {
|
||||
func init() { proto.RegisterFile("server/settings/settings.proto", fileDescriptor_a480d494da040caa) }
|
||||
|
||||
var fileDescriptor_a480d494da040caa = []byte{
|
||||
// 1232 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0x4f, 0x6f, 0x1b, 0x45,
|
||||
0x14, 0x97, 0xeb, 0x34, 0xb1, 0x9f, 0x9b, 0x3a, 0x99, 0xa6, 0xe9, 0xd6, 0x2a, 0x89, 0xf1, 0xa1,
|
||||
0x32, 0x08, 0xd6, 0x8d, 0x2b, 0x04, 0xaa, 0xa8, 0xa0, 0xb6, 0xab, 0xd6, 0x34, 0x6d, 0xc3, 0xb4,
|
||||
0xe9, 0x81, 0x4b, 0x35, 0x59, 0x3f, 0xd6, 0x4b, 0xd6, 0x33, 0xab, 0x99, 0x59, 0x13, 0xf7, 0xc8,
|
||||
0x07, 0xe0, 0x02, 0x9f, 0x86, 0x3b, 0x82, 0x23, 0x12, 0xf7, 0x08, 0x59, 0x9c, 0xf8, 0x14, 0x68,
|
||||
0x67, 0xff, 0x64, 0xb3, 0x76, 0x0a, 0x52, 0x6f, 0x33, 0xbf, 0xdf, 0xfb, 0x37, 0x6f, 0xde, 0x9b,
|
||||
0x79, 0xb0, 0xa3, 0x50, 0x4e, 0x51, 0x76, 0x14, 0x6a, 0xed, 0x71, 0x57, 0x65, 0x0b, 0x3b, 0x90,
|
||||
0x42, 0x0b, 0xb2, 0xe6, 0xf8, 0xa1, 0xd2, 0x28, 0x1b, 0x5b, 0xae, 0x70, 0x85, 0xc1, 0x3a, 0xd1,
|
||||
0x2a, 0xa6, 0x1b, 0xb7, 0x5c, 0x21, 0x5c, 0x1f, 0x3b, 0x2c, 0xf0, 0x3a, 0x8c, 0x73, 0xa1, 0x99,
|
||||
0xf6, 0x04, 0x4f, 0x94, 0x1b, 0xfb, 0xae, 0xa7, 0xc7, 0xe1, 0x91, 0xed, 0x88, 0x49, 0x87, 0x49,
|
||||
0xa3, 0xfe, 0x9d, 0x59, 0x7c, 0xec, 0x8c, 0x3a, 0xd3, 0x6e, 0x27, 0x38, 0x76, 0x23, 0x4d, 0xd5,
|
||||
0x61, 0x41, 0xe0, 0x7b, 0x8e, 0xd1, 0xed, 0x4c, 0xf7, 0x98, 0x1f, 0x8c, 0xd9, 0x5e, 0xc7, 0x45,
|
||||
0x8e, 0x92, 0x69, 0x1c, 0x25, 0xd6, 0xbe, 0xfc, 0x0f, 0x6b, 0xc5, 0x93, 0x08, 0x6f, 0xe4, 0x74,
|
||||
0x1c, 0x9f, 0x79, 0x93, 0x24, 0x9e, 0x56, 0x1d, 0xd6, 0x5f, 0x24, 0xec, 0xd7, 0x21, 0xca, 0x59,
|
||||
0xeb, 0x9f, 0x1a, 0x54, 0x52, 0x84, 0xdc, 0x84, 0x72, 0x28, 0x7d, 0xab, 0xd4, 0x2c, 0xb5, 0xab,
|
||||
0xbd, 0xb5, 0xf9, 0xe9, 0x6e, 0xf9, 0x90, 0xee, 0xd3, 0x08, 0x23, 0x77, 0xa0, 0x3a, 0xc2, 0x93,
|
||||
0xbe, 0xe0, 0xdf, 0x7a, 0xae, 0x75, 0xa9, 0x59, 0x6a, 0xd7, 0xba, 0xc4, 0x4e, 0x32, 0x63, 0x0f,
|
||||
0x52, 0x86, 0x9e, 0x09, 0x91, 0x3e, 0x40, 0xe4, 0x3f, 0x51, 0x29, 0x1b, 0x95, 0x6b, 0x99, 0xca,
|
||||
0xf3, 0xe1, 0xa0, 0x1f, 0x53, 0xbd, 0xab, 0xf3, 0xd3, 0x5d, 0x38, 0xdb, 0xd3, 0x9c, 0x1a, 0x69,
|
||||
0x42, 0x8d, 0x05, 0xc1, 0x3e, 0x3b, 0x42, 0xff, 0x09, 0xce, 0xac, 0x95, 0x28, 0x32, 0x9a, 0x87,
|
||||
0xc8, 0x2b, 0xd8, 0x94, 0xa8, 0x44, 0x28, 0x1d, 0x7c, 0x3e, 0x45, 0x29, 0xbd, 0x11, 0x2a, 0xeb,
|
||||
0x72, 0xb3, 0xdc, 0xae, 0x75, 0xdb, 0x99, 0xb7, 0xf4, 0x84, 0x36, 0x2d, 0x8a, 0x3e, 0xe4, 0x5a,
|
||||
0xce, 0xe8, 0xa2, 0x09, 0x62, 0x03, 0x51, 0x9a, 0xe9, 0x50, 0xf5, 0xd8, 0xc8, 0xc5, 0x87, 0x9c,
|
||||
0x1d, 0xf9, 0x38, 0xb2, 0x56, 0x9b, 0xa5, 0x76, 0x85, 0x2e, 0x61, 0xc8, 0x63, 0xa8, 0xc7, 0x95,
|
||||
0xf0, 0x80, 0x33, 0x7f, 0xa6, 0x3d, 0x47, 0x59, 0x6b, 0xe6, 0xcc, 0x3b, 0x59, 0x14, 0x8f, 0xce,
|
||||
0xf3, 0xc9, 0x71, 0x8b, 0x6a, 0xe4, 0x0d, 0x6c, 0x1c, 0x87, 0x4a, 0x8b, 0x89, 0xf7, 0x06, 0x9f,
|
||||
0x07, 0xa6, 0x9a, 0xac, 0x8a, 0x31, 0xf5, 0xcc, 0x3e, 0x2b, 0x00, 0x3b, 0x2d, 0x00, 0xb3, 0x78,
|
||||
0xed, 0x8c, 0xec, 0x69, 0xd7, 0x0e, 0x8e, 0x5d, 0x3b, 0x2a, 0x27, 0x3b, 0x57, 0x4e, 0x76, 0x5a,
|
||||
0x4e, 0xf6, 0x93, 0x82, 0x55, 0xba, 0xe0, 0x87, 0xbc, 0x0f, 0x2b, 0x63, 0xf4, 0x03, 0xab, 0x6a,
|
||||
0xfc, 0xad, 0x67, 0xa1, 0x3f, 0x46, 0x3f, 0xa0, 0x86, 0x22, 0x1f, 0xc0, 0x5a, 0xe0, 0x87, 0xae,
|
||||
0xc7, 0x95, 0x05, 0x26, 0xcd, 0xf5, 0x4c, 0xea, 0xc0, 0xe0, 0x34, 0xe5, 0xa3, 0x1c, 0x86, 0x0a,
|
||||
0xe5, 0xbe, 0x88, 0x76, 0x03, 0x4f, 0xc5, 0x39, 0xac, 0xc5, 0x39, 0x5c, 0x64, 0xc8, 0x8f, 0x25,
|
||||
0xb8, 0xe1, 0x98, 0xac, 0x3c, 0x65, 0x9c, 0xb9, 0x38, 0x41, 0xae, 0x0f, 0x12, 0x5f, 0x57, 0x8c,
|
||||
0xaf, 0x97, 0xef, 0x96, 0x81, 0xfe, 0x52, 0xe3, 0xf4, 0x22, 0xa7, 0xe4, 0x23, 0xd8, 0xcc, 0x52,
|
||||
0xf4, 0x0a, 0xa5, 0x32, 0x77, 0xb1, 0xde, 0x2c, 0xb7, 0xab, 0x74, 0x91, 0x20, 0x0d, 0xa8, 0x84,
|
||||
0x5e, 0x5f, 0xa9, 0x43, 0xba, 0x6f, 0x5d, 0x35, 0x95, 0x9a, 0xed, 0x49, 0x1b, 0xea, 0xa1, 0xd7,
|
||||
0x63, 0x9c, 0xa3, 0xec, 0x0b, 0xae, 0x91, 0x6b, 0xab, 0x6e, 0x44, 0x8a, 0x70, 0x54, 0xf2, 0x29,
|
||||
0x14, 0x19, 0xda, 0x88, 0x4b, 0x3e, 0x07, 0x45, 0xb6, 0x02, 0xa6, 0xd4, 0xf7, 0x42, 0x8e, 0x0e,
|
||||
0x98, 0xd6, 0x28, 0xb9, 0xb5, 0x19, 0xdb, 0x2a, 0xc0, 0xe4, 0x36, 0x5c, 0xd5, 0x92, 0x39, 0xc7,
|
||||
0x1e, 0x77, 0x9f, 0xa2, 0x1e, 0x8b, 0x91, 0x45, 0x8c, 0x60, 0x01, 0x8d, 0xce, 0x99, 0x3a, 0x38,
|
||||
0x40, 0x39, 0x61, 0x3c, 0x8a, 0xef, 0x9a, 0xb9, 0xa7, 0x45, 0x82, 0x7c, 0x08, 0x1b, 0x19, 0x28,
|
||||
0x94, 0x17, 0xa5, 0xd8, 0xda, 0x32, 0x76, 0x17, 0xf0, 0x42, 0x1b, 0x51, 0x21, 0xf4, 0xa1, 0xf4,
|
||||
0xad, 0xeb, 0x46, 0x7a, 0x09, 0x13, 0x9d, 0x1e, 0x4f, 0xd0, 0x49, 0xfb, 0x6d, 0xdb, 0xc4, 0x90,
|
||||
0x87, 0xc8, 0x1d, 0xb8, 0xe6, 0x08, 0xae, 0xa5, 0xf0, 0x7d, 0x94, 0xcf, 0xd8, 0x04, 0x55, 0xc0,
|
||||
0x1c, 0xb4, 0x6e, 0x18, 0x93, 0xcb, 0x28, 0xf2, 0x39, 0xdc, 0x64, 0x41, 0xa0, 0x86, 0xfc, 0x01,
|
||||
0x9f, 0x65, 0x68, 0xea, 0xc1, 0x32, 0x1e, 0x2e, 0x16, 0x20, 0x5d, 0xd8, 0xf2, 0x26, 0x01, 0x4a,
|
||||
0x25, 0xb8, 0xa9, 0xa6, 0x54, 0xf1, 0xa6, 0x51, 0x5c, 0xca, 0x35, 0x7e, 0x2e, 0xc1, 0xf6, 0xf2,
|
||||
0xa7, 0x86, 0x6c, 0x40, 0xf9, 0x18, 0x67, 0xf1, 0x1b, 0x4b, 0xa3, 0x25, 0x19, 0xc1, 0xe5, 0x29,
|
||||
0xf3, 0x43, 0x4c, 0x9e, 0xd5, 0x77, 0x6c, 0xf2, 0xa2, 0x5b, 0x1a, 0x1b, 0xbf, 0x77, 0xe9, 0xb3,
|
||||
0x52, 0xeb, 0x35, 0x5c, 0x5f, 0xfa, 0x06, 0x91, 0x1d, 0x80, 0xb4, 0x22, 0x86, 0x83, 0x24, 0xb6,
|
||||
0x1c, 0x12, 0xd5, 0x11, 0xe3, 0x82, 0xcf, 0xa2, 0x72, 0x3f, 0x54, 0x28, 0x95, 0x89, 0xb5, 0x42,
|
||||
0x0b, 0x68, 0x6b, 0x00, 0x37, 0xd2, 0xa7, 0x36, 0x69, 0x21, 0x8a, 0x2a, 0x10, 0x5c, 0x61, 0xfe,
|
||||
0xd9, 0x28, 0xbd, 0xfd, 0xd9, 0x68, 0xfd, 0x52, 0x82, 0x95, 0xe8, 0xc1, 0x21, 0x16, 0xac, 0x39,
|
||||
0x63, 0x66, 0x2a, 0x26, 0x8e, 0x29, 0xdd, 0x46, 0xad, 0x16, 0x2d, 0x5f, 0xe2, 0x89, 0x36, 0xa1,
|
||||
0x54, 0x69, 0xb6, 0x27, 0xf7, 0x01, 0x8e, 0x3c, 0xce, 0xe4, 0xec, 0x50, 0xfa, 0xca, 0x2a, 0x1b,
|
||||
0x67, 0xef, 0x9d, 0x7b, 0xc9, 0xec, 0x5e, 0xc6, 0xc7, 0xef, 0x7f, 0x4e, 0xa1, 0x71, 0x1f, 0xea,
|
||||
0x05, 0x7a, 0xc9, 0x9d, 0x6d, 0xe5, 0xef, 0xac, 0x9a, 0xcf, 0xf1, 0x2d, 0x58, 0x8d, 0xcf, 0x43,
|
||||
0x08, 0xac, 0x70, 0x36, 0xc1, 0x44, 0xcd, 0xac, 0x5b, 0x5f, 0x40, 0x35, 0xfb, 0x2c, 0x49, 0x17,
|
||||
0xc0, 0x11, 0x9c, 0xa3, 0xa3, 0x85, 0x4c, 0xb3, 0x72, 0xf6, 0xa9, 0xf6, 0x53, 0x8a, 0xe6, 0xa4,
|
||||
0x5a, 0x77, 0xa1, 0x9a, 0x11, 0xcb, 0x3c, 0x44, 0x98, 0x9e, 0x05, 0x69, 0x60, 0x66, 0xdd, 0xfa,
|
||||
0xb5, 0x0c, 0xb9, 0x0f, 0x76, 0xa9, 0xda, 0x36, 0xac, 0x7a, 0x4a, 0x85, 0x28, 0x13, 0xc5, 0x64,
|
||||
0x47, 0xda, 0x50, 0x71, 0x7c, 0x0f, 0xb9, 0x1e, 0x0e, 0xcc, 0x1f, 0x5e, 0xed, 0x5d, 0x99, 0x9f,
|
||||
0xee, 0x56, 0xfa, 0x09, 0x46, 0x33, 0x96, 0xec, 0x41, 0xcd, 0xf1, 0xbd, 0x94, 0x88, 0xbf, 0xea,
|
||||
0x5e, 0x7d, 0x7e, 0xba, 0x5b, 0xeb, 0xef, 0x0f, 0x33, 0xf9, 0xbc, 0x4c, 0xe4, 0x54, 0x39, 0x22,
|
||||
0x48, 0x3e, 0xec, 0x2a, 0x4d, 0x76, 0xe4, 0x35, 0xac, 0x7b, 0xa3, 0x97, 0xe2, 0x18, 0x79, 0xdf,
|
||||
0x0c, 0x2f, 0xd6, 0xaa, 0xc9, 0xcd, 0xed, 0x25, 0xd3, 0x83, 0x3d, 0xcc, 0x0b, 0x9a, 0xeb, 0xea,
|
||||
0x6d, 0xce, 0x4f, 0x77, 0xd7, 0x87, 0x83, 0x1c, 0x4e, 0xcf, 0xdb, 0x23, 0xf7, 0xc0, 0x42, 0xd3,
|
||||
0xaa, 0x07, 0x4f, 0xfa, 0x0f, 0x1f, 0x84, 0x7a, 0x8c, 0x5c, 0x27, 0x9d, 0x64, 0x7e, 0xed, 0x0a,
|
||||
0xbd, 0x90, 0x6f, 0xcc, 0x80, 0x2c, 0xfa, 0x5c, 0x52, 0x22, 0x4f, 0xcf, 0xb7, 0xf5, 0xa7, 0x6f,
|
||||
0x6d, 0xeb, 0x78, 0x72, 0xb3, 0xb3, 0xd1, 0x33, 0x1a, 0x81, 0x6c, 0x63, 0x3f, 0x57, 0x5b, 0xdd,
|
||||
0xdf, 0x4a, 0x50, 0x4f, 0xfb, 0xeb, 0x05, 0xca, 0xa9, 0xe7, 0x20, 0xf9, 0x0a, 0xca, 0x8f, 0x50,
|
||||
0x93, 0xed, 0x85, 0x59, 0xc7, 0xcc, 0x77, 0x8d, 0xcd, 0x05, 0xbc, 0x65, 0xfd, 0xf0, 0xe7, 0xdf,
|
||||
0x3f, 0x5d, 0x22, 0x64, 0xc3, 0xcc, 0xac, 0xd3, 0xbd, 0x6c, 0x5e, 0x24, 0x63, 0x80, 0x47, 0x98,
|
||||
0x7d, 0x7e, 0x17, 0x99, 0x6c, 0x2e, 0xe0, 0x85, 0x5e, 0x6f, 0x35, 0x8d, 0x87, 0x06, 0xb1, 0x8a,
|
||||
0x1e, 0x3a, 0x49, 0x8b, 0xf7, 0xfa, 0xbf, 0xcf, 0x77, 0x4a, 0x7f, 0xcc, 0x77, 0x4a, 0x7f, 0xcd,
|
||||
0x77, 0x4a, 0xdf, 0x7c, 0xf2, 0xff, 0xa6, 0xe4, 0xb8, 0xd4, 0x32, 0x63, 0x47, 0xab, 0x66, 0xa6,
|
||||
0xbd, 0xfb, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8d, 0xd6, 0x25, 0xb7, 0xc2, 0x0b, 0x00, 0x00,
|
||||
// 1249 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x56, 0x4f, 0x6f, 0x1b, 0xb7,
|
||||
0x12, 0xc7, 0x46, 0x8e, 0x2d, 0x8d, 0xe3, 0xc8, 0x66, 0x1c, 0x67, 0x23, 0xe4, 0xd9, 0x7a, 0x3a,
|
||||
0x04, 0x7a, 0x0f, 0xed, 0x2a, 0x56, 0x50, 0xb4, 0x08, 0x1a, 0xb4, 0x91, 0x14, 0x24, 0x6a, 0x9c,
|
||||
0xc4, 0xdd, 0xc4, 0x39, 0xf4, 0x12, 0xd0, 0xab, 0xe9, 0x6a, 0xeb, 0x15, 0xb9, 0x20, 0xb9, 0x6a,
|
||||
0x94, 0x63, 0x3f, 0x40, 0x0f, 0x6d, 0x3f, 0x4d, 0xef, 0x45, 0x7b, 0x2c, 0xd0, 0xbb, 0x51, 0x08,
|
||||
0xfd, 0x20, 0x05, 0xb9, 0x7f, 0xbc, 0x5e, 0xc9, 0x69, 0x81, 0xdc, 0xc8, 0xdf, 0x6f, 0xfe, 0x71,
|
||||
0x38, 0x43, 0x0e, 0xec, 0x4a, 0x14, 0x53, 0x14, 0x1d, 0x89, 0x4a, 0x05, 0xcc, 0x97, 0xf9, 0xc2,
|
||||
0x89, 0x04, 0x57, 0x9c, 0xac, 0x79, 0x61, 0x2c, 0x15, 0x8a, 0xc6, 0xb6, 0xcf, 0x7d, 0x6e, 0xb0,
|
||||
0x8e, 0x5e, 0x25, 0x74, 0xe3, 0x96, 0xcf, 0xb9, 0x1f, 0x62, 0x87, 0x46, 0x41, 0x87, 0x32, 0xc6,
|
||||
0x15, 0x55, 0x01, 0x67, 0xa9, 0x72, 0xe3, 0xc0, 0x0f, 0xd4, 0x38, 0x3e, 0x76, 0x3c, 0x3e, 0xe9,
|
||||
0x50, 0x61, 0xd4, 0xbf, 0x31, 0x8b, 0x0f, 0xbd, 0x51, 0x67, 0xda, 0xed, 0x44, 0x27, 0xbe, 0xd6,
|
||||
0x94, 0x1d, 0x1a, 0x45, 0x61, 0xe0, 0x19, 0xdd, 0xce, 0x74, 0x9f, 0x86, 0xd1, 0x98, 0xee, 0x77,
|
||||
0x7c, 0x64, 0x28, 0xa8, 0xc2, 0x51, 0x6a, 0xed, 0xf3, 0x7f, 0xb0, 0x56, 0x3e, 0x09, 0x0f, 0x46,
|
||||
0x5e, 0xc7, 0x0b, 0x69, 0x30, 0x49, 0xe3, 0x69, 0xd5, 0x61, 0xe3, 0x45, 0xca, 0x7e, 0x19, 0xa3,
|
||||
0x98, 0xb5, 0x7e, 0xb8, 0x02, 0xd5, 0x0c, 0x21, 0x37, 0xa1, 0x12, 0x8b, 0xd0, 0xb6, 0x9a, 0x56,
|
||||
0xbb, 0xd6, 0x5b, 0x9b, 0x9f, 0xee, 0x55, 0x8e, 0xdc, 0x03, 0x57, 0x63, 0xe4, 0x0e, 0xd4, 0x46,
|
||||
0xf8, 0xa6, 0xcf, 0xd9, 0xd7, 0x81, 0x6f, 0x5f, 0x6a, 0x5a, 0xed, 0xf5, 0x2e, 0x71, 0xd2, 0xcc,
|
||||
0x38, 0x83, 0x8c, 0x71, 0xcf, 0x84, 0x48, 0x1f, 0x40, 0xfb, 0x4f, 0x55, 0x2a, 0x46, 0xe5, 0x5a,
|
||||
0xae, 0xf2, 0x7c, 0x38, 0xe8, 0x27, 0x54, 0xef, 0xea, 0xfc, 0x74, 0x0f, 0xce, 0xf6, 0x6e, 0x41,
|
||||
0x8d, 0x34, 0x61, 0x9d, 0x46, 0xd1, 0x01, 0x3d, 0xc6, 0xf0, 0x09, 0xce, 0xec, 0x15, 0x1d, 0x99,
|
||||
0x5b, 0x84, 0xc8, 0x2b, 0xd8, 0x12, 0x28, 0x79, 0x2c, 0x3c, 0x7c, 0x3e, 0x45, 0x21, 0x82, 0x11,
|
||||
0x4a, 0xfb, 0x72, 0xb3, 0xd2, 0x5e, 0xef, 0xb6, 0x73, 0x6f, 0xd9, 0x09, 0x1d, 0xb7, 0x2c, 0xfa,
|
||||
0x90, 0x29, 0x31, 0x73, 0x17, 0x4d, 0x10, 0x07, 0x88, 0x54, 0x54, 0xc5, 0xb2, 0x47, 0x47, 0x3e,
|
||||
0x3e, 0x64, 0xf4, 0x38, 0xc4, 0x91, 0xbd, 0xda, 0xb4, 0xda, 0x55, 0x77, 0x09, 0x43, 0x1e, 0x43,
|
||||
0x3d, 0xa9, 0x84, 0x07, 0x8c, 0x86, 0x33, 0x15, 0x78, 0xd2, 0x5e, 0x33, 0x67, 0xde, 0xcd, 0xa3,
|
||||
0x78, 0x74, 0x9e, 0x4f, 0x8f, 0x5b, 0x56, 0x23, 0x6f, 0x61, 0xf3, 0x24, 0x96, 0x8a, 0x4f, 0x82,
|
||||
0xb7, 0xf8, 0x3c, 0x32, 0xd5, 0x64, 0x57, 0x8d, 0xa9, 0x67, 0xce, 0x59, 0x01, 0x38, 0x59, 0x01,
|
||||
0x98, 0xc5, 0x6b, 0x6f, 0xe4, 0x4c, 0xbb, 0x4e, 0x74, 0xe2, 0x3b, 0xba, 0x9c, 0x9c, 0x42, 0x39,
|
||||
0x39, 0x59, 0x39, 0x39, 0x4f, 0x4a, 0x56, 0xdd, 0x05, 0x3f, 0xe4, 0xbf, 0xb0, 0x32, 0xc6, 0x30,
|
||||
0xb2, 0x6b, 0xc6, 0xdf, 0x46, 0x1e, 0xfa, 0x63, 0x0c, 0x23, 0xd7, 0x50, 0xe4, 0x7f, 0xb0, 0x16,
|
||||
0x85, 0xb1, 0x1f, 0x30, 0x69, 0x83, 0x49, 0x73, 0x3d, 0x97, 0x3a, 0x34, 0xb8, 0x9b, 0xf1, 0x3a,
|
||||
0x87, 0xb1, 0x44, 0x71, 0xc0, 0xf5, 0x6e, 0x10, 0xc8, 0x24, 0x87, 0xeb, 0x49, 0x0e, 0x17, 0x19,
|
||||
0xf2, 0xbd, 0x05, 0x37, 0x3c, 0x93, 0x95, 0xa7, 0x94, 0x51, 0x1f, 0x27, 0xc8, 0xd4, 0x61, 0xea,
|
||||
0xeb, 0x8a, 0xf1, 0xf5, 0xf2, 0xfd, 0x32, 0xd0, 0x5f, 0x6a, 0xdc, 0xbd, 0xc8, 0x29, 0xf9, 0x00,
|
||||
0xb6, 0xf2, 0x14, 0xbd, 0x42, 0x21, 0xcd, 0x5d, 0x6c, 0x34, 0x2b, 0xed, 0x9a, 0xbb, 0x48, 0x90,
|
||||
0x06, 0x54, 0xe3, 0xa0, 0x2f, 0xe5, 0x91, 0x7b, 0x60, 0x5f, 0x35, 0x95, 0x9a, 0xef, 0x49, 0x1b,
|
||||
0xea, 0x71, 0xd0, 0xa3, 0x8c, 0xa1, 0xe8, 0x73, 0xa6, 0x90, 0x29, 0xbb, 0x6e, 0x44, 0xca, 0xb0,
|
||||
0x2e, 0xf9, 0x0c, 0xd2, 0x86, 0x36, 0x93, 0x92, 0x2f, 0x40, 0xda, 0x56, 0x44, 0xa5, 0xfc, 0x96,
|
||||
0x8b, 0xd1, 0x21, 0x55, 0x0a, 0x05, 0xb3, 0xb7, 0x12, 0x5b, 0x25, 0x98, 0xdc, 0x86, 0xab, 0x4a,
|
||||
0x50, 0xef, 0x24, 0x60, 0xfe, 0x53, 0x54, 0x63, 0x3e, 0xb2, 0x89, 0x11, 0x2c, 0xa1, 0xfa, 0x9c,
|
||||
0x99, 0x83, 0x43, 0x14, 0x13, 0xca, 0x74, 0x7c, 0xd7, 0xcc, 0x3d, 0x2d, 0x12, 0xe4, 0xff, 0xb0,
|
||||
0x99, 0x83, 0x5c, 0x06, 0x3a, 0xc5, 0xf6, 0xb6, 0xb1, 0xbb, 0x80, 0x97, 0xda, 0xc8, 0xe5, 0x5c,
|
||||
0x1d, 0x89, 0xd0, 0xbe, 0x6e, 0xa4, 0x97, 0x30, 0xfa, 0xf4, 0xf8, 0x06, 0xbd, 0xac, 0xdf, 0x76,
|
||||
0x4c, 0x0c, 0x45, 0x88, 0xdc, 0x81, 0x6b, 0x1e, 0x67, 0x4a, 0xf0, 0x30, 0x44, 0xf1, 0x8c, 0x4e,
|
||||
0x50, 0x46, 0xd4, 0x43, 0xfb, 0x86, 0x31, 0xb9, 0x8c, 0x22, 0x9f, 0xc2, 0x4d, 0x1a, 0x45, 0x72,
|
||||
0xc8, 0x1e, 0xb0, 0x59, 0x8e, 0x66, 0x1e, 0x6c, 0xe3, 0xe1, 0x62, 0x01, 0xd2, 0x85, 0xed, 0x60,
|
||||
0x12, 0xa1, 0x90, 0x9c, 0x99, 0x6a, 0xca, 0x14, 0x6f, 0x1a, 0xc5, 0xa5, 0x9c, 0xce, 0x7b, 0xc0,
|
||||
0xa4, 0xa2, 0x61, 0x68, 0xe0, 0xe1, 0xc0, 0x6e, 0x24, 0x79, 0x3f, 0x8f, 0x36, 0x7e, 0xb2, 0x60,
|
||||
0x67, 0xf9, 0x93, 0x44, 0x36, 0xa1, 0x72, 0x82, 0xb3, 0xe4, 0x2d, 0x76, 0xf5, 0x92, 0x8c, 0xe0,
|
||||
0xf2, 0x94, 0x86, 0x31, 0xa6, 0xcf, 0xef, 0x7b, 0x3e, 0x06, 0x65, 0xb7, 0x6e, 0x62, 0xfc, 0xde,
|
||||
0xa5, 0x4f, 0xac, 0xd6, 0x6b, 0xb8, 0xbe, 0xf4, 0xad, 0x22, 0xbb, 0x00, 0x59, 0xe5, 0x0c, 0x07,
|
||||
0x69, 0x6c, 0x05, 0x44, 0x9f, 0x9b, 0x32, 0xce, 0x66, 0xba, 0x2d, 0x8e, 0x24, 0x0a, 0x69, 0x62,
|
||||
0xad, 0xba, 0x25, 0xb4, 0x35, 0x80, 0x1b, 0xd9, 0x93, 0x9c, 0xb6, 0x9a, 0x8b, 0x32, 0xe2, 0x4c,
|
||||
0x62, 0xf1, 0x79, 0xb1, 0xde, 0xfd, 0xbc, 0xb4, 0x7e, 0xb6, 0x60, 0x45, 0x3f, 0x4c, 0xc4, 0x86,
|
||||
0x35, 0x6f, 0x4c, 0x4d, 0x65, 0x25, 0x31, 0x65, 0x5b, 0xdd, 0x92, 0x7a, 0xf9, 0x12, 0xdf, 0x28,
|
||||
0x13, 0x4a, 0xcd, 0xcd, 0xf7, 0xe4, 0x3e, 0xc0, 0x71, 0xc0, 0xa8, 0x98, 0x1d, 0x89, 0x50, 0xda,
|
||||
0x15, 0xe3, 0xec, 0x3f, 0xe7, 0x5e, 0x3c, 0xa7, 0x97, 0xf3, 0xc9, 0x3f, 0x51, 0x50, 0x68, 0xdc,
|
||||
0x87, 0x7a, 0x89, 0x5e, 0x72, 0x67, 0xdb, 0xc5, 0x3b, 0xab, 0x15, 0x73, 0x7c, 0x0b, 0x56, 0x93,
|
||||
0xf3, 0x10, 0x02, 0x2b, 0x8c, 0x4e, 0x30, 0x55, 0x33, 0xeb, 0xd6, 0x67, 0x50, 0xcb, 0x3f, 0x55,
|
||||
0xd2, 0x05, 0xf0, 0x38, 0x63, 0xe8, 0x29, 0x2e, 0xb2, 0xac, 0x9c, 0x7d, 0xbe, 0xfd, 0x8c, 0x72,
|
||||
0x0b, 0x52, 0xad, 0xbb, 0x50, 0xcb, 0x89, 0x65, 0x1e, 0x34, 0xa6, 0x66, 0x51, 0x16, 0x98, 0x59,
|
||||
0xb7, 0x7e, 0xa9, 0x40, 0xe1, 0x23, 0x5e, 0xaa, 0xb6, 0x03, 0xab, 0x81, 0x94, 0x31, 0x8a, 0x54,
|
||||
0x31, 0xdd, 0x91, 0x36, 0x54, 0xbd, 0x30, 0x40, 0xa6, 0x86, 0x03, 0xf3, 0xd7, 0xd7, 0x7a, 0x57,
|
||||
0xe6, 0xa7, 0x7b, 0xd5, 0x7e, 0x8a, 0xb9, 0x39, 0x4b, 0xf6, 0x61, 0xdd, 0x0b, 0x83, 0x8c, 0x48,
|
||||
0xbe, 0xf4, 0x5e, 0x7d, 0x7e, 0xba, 0xb7, 0xde, 0x3f, 0x18, 0xe6, 0xf2, 0x45, 0x19, 0xed, 0x54,
|
||||
0x7a, 0x3c, 0x4a, 0x3f, 0xf6, 0x9a, 0x9b, 0xee, 0xc8, 0x6b, 0xd8, 0x08, 0x46, 0x2f, 0xf9, 0x09,
|
||||
0xb2, 0xbe, 0x19, 0x72, 0xec, 0x55, 0x93, 0x9b, 0xdb, 0x4b, 0xa6, 0x0c, 0x67, 0x58, 0x14, 0x34,
|
||||
0xd7, 0xd5, 0xdb, 0x9a, 0x9f, 0xee, 0x6d, 0x0c, 0x07, 0x05, 0xdc, 0x3d, 0x6f, 0x8f, 0xdc, 0x03,
|
||||
0x1b, 0x4d, 0x4b, 0x1f, 0x3e, 0xe9, 0x3f, 0x7c, 0x10, 0xab, 0x31, 0x32, 0x95, 0x76, 0x92, 0xf9,
|
||||
0xdd, 0xab, 0xee, 0x85, 0x7c, 0x63, 0x06, 0x64, 0xd1, 0xe7, 0x92, 0x12, 0x79, 0x7a, 0xbe, 0xad,
|
||||
0x3f, 0x7e, 0x67, 0x5b, 0x27, 0x13, 0x9e, 0x93, 0x8f, 0xa8, 0x7a, 0x54, 0x72, 0x8c, 0xfd, 0x42,
|
||||
0x6d, 0x75, 0x7f, 0xb5, 0xa0, 0x9e, 0xf5, 0xd7, 0x0b, 0x14, 0xd3, 0xc0, 0x43, 0xf2, 0x05, 0x54,
|
||||
0x1e, 0xa1, 0x22, 0x3b, 0x0b, 0x33, 0x91, 0x99, 0x03, 0x1b, 0x5b, 0x0b, 0x78, 0xcb, 0xfe, 0xee,
|
||||
0x8f, 0xbf, 0x7e, 0xbc, 0x44, 0xc8, 0xa6, 0x99, 0x6d, 0xa7, 0xfb, 0xf9, 0x5c, 0x49, 0xc6, 0x00,
|
||||
0x8f, 0x30, 0xff, 0x24, 0x2f, 0x32, 0xd9, 0x5c, 0xc0, 0x4b, 0xbd, 0xde, 0x6a, 0x1a, 0x0f, 0x0d,
|
||||
0x62, 0x97, 0x3d, 0x74, 0xd2, 0x16, 0xef, 0xf5, 0x7f, 0x9b, 0xef, 0x5a, 0xbf, 0xcf, 0x77, 0xad,
|
||||
0x3f, 0xe7, 0xbb, 0xd6, 0x57, 0x1f, 0xfd, 0xbb, 0x69, 0x3a, 0x29, 0xb5, 0xdc, 0xd8, 0xf1, 0xaa,
|
||||
0x99, 0x7d, 0xef, 0xfe, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x99, 0x28, 0x60, 0x2e, 0xea, 0x0b, 0x00,
|
||||
0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
@@ -999,6 +1009,15 @@ func (m *Settings) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i -= len(m.XXX_unrecognized)
|
||||
copy(dAtA[i:], m.XXX_unrecognized)
|
||||
}
|
||||
if len(m.InstallationID) > 0 {
|
||||
i -= len(m.InstallationID)
|
||||
copy(dAtA[i:], m.InstallationID)
|
||||
i = encodeVarintSettings(dAtA, i, uint64(len(m.InstallationID)))
|
||||
i--
|
||||
dAtA[i] = 0x1
|
||||
i--
|
||||
dAtA[i] = 0xd2
|
||||
}
|
||||
if m.ImpersonationEnabled {
|
||||
i--
|
||||
if m.ImpersonationEnabled {
|
||||
@@ -1774,6 +1793,10 @@ func (m *Settings) Size() (n int) {
|
||||
if m.ImpersonationEnabled {
|
||||
n += 3
|
||||
}
|
||||
l = len(m.InstallationID)
|
||||
if l > 0 {
|
||||
n += 2 + l + sovSettings(uint64(l))
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
}
|
||||
@@ -2884,6 +2907,38 @@ func (m *Settings) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
}
|
||||
m.ImpersonationEnabled = bool(v != 0)
|
||||
case 26:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field InstallationID", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowSettings
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthSettings
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthSettings
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.InstallationID = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipSettings(dAtA[iNdEx:])
|
||||
|
||||
@@ -6,15 +6,22 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/util/git"
|
||||
"github.com/argoproj/argo-cd/v2/util/glob"
|
||||
|
||||
globutil "github.com/gobwas/glob"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/util/git"
|
||||
"github.com/argoproj/argo-cd/v2/util/glob"
|
||||
)
|
||||
|
||||
const (
|
||||
// serviceAccountDisallowedCharSet contains the characters that are not allowed to be present
|
||||
// in a DefaultServiceAccount configured for a DestinationServiceAccount
|
||||
serviceAccountDisallowedCharSet = "!*[]{}\\/"
|
||||
)
|
||||
|
||||
type ErrApplicationNotAllowedToUseProject struct {
|
||||
@@ -267,12 +274,27 @@ func (p *AppProject) ValidateProject() error {
|
||||
|
||||
destServiceAccts := make(map[string]bool)
|
||||
for _, destServiceAcct := range p.Spec.DestinationServiceAccounts {
|
||||
if destServiceAcct.Server == "!*" {
|
||||
return status.Errorf(codes.InvalidArgument, "server has an invalid format, '!*'")
|
||||
if strings.Contains(destServiceAcct.Server, "!") {
|
||||
return status.Errorf(codes.InvalidArgument, "server has an invalid format, '%s'", destServiceAcct.Server)
|
||||
}
|
||||
|
||||
if destServiceAcct.Namespace == "!*" {
|
||||
return status.Errorf(codes.InvalidArgument, "namespace has an invalid format, '!*'")
|
||||
if strings.Contains(destServiceAcct.Namespace, "!") {
|
||||
return status.Errorf(codes.InvalidArgument, "namespace has an invalid format, '%s'", destServiceAcct.Namespace)
|
||||
}
|
||||
|
||||
if strings.Trim(destServiceAcct.DefaultServiceAccount, " ") == "" ||
|
||||
strings.ContainsAny(destServiceAcct.DefaultServiceAccount, serviceAccountDisallowedCharSet) {
|
||||
return status.Errorf(codes.InvalidArgument, "defaultServiceAccount has an invalid format, '%s'", destServiceAcct.DefaultServiceAccount)
|
||||
}
|
||||
|
||||
_, err := globutil.Compile(destServiceAcct.Server)
|
||||
if err != nil {
|
||||
return status.Errorf(codes.InvalidArgument, "server has an invalid format, '%s'", destServiceAcct.Server)
|
||||
}
|
||||
|
||||
_, err = globutil.Compile(destServiceAcct.Namespace)
|
||||
if err != nil {
|
||||
return status.Errorf(codes.InvalidArgument, "namespace has an invalid format, '%s'", destServiceAcct.Namespace)
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%s/%s", destServiceAcct.Server, destServiceAcct.Namespace)
|
||||
|
||||
1448
pkg/apis/application/v1alpha1/generated.pb.go
generated
1448
pkg/apis/application/v1alpha1/generated.pb.go
generated
File diff suppressed because it is too large
Load Diff
@@ -156,7 +156,7 @@ message ApplicationDestinationServiceAccount {
|
||||
// Namespace specifies the target namespace for the application's resources.
|
||||
optional string namespace = 2;
|
||||
|
||||
// ServiceAccountName to be used for impersonation during the sync operation
|
||||
// DefaultServiceAccount to be used for impersonation during the sync operation
|
||||
optional string defaultServiceAccount = 3;
|
||||
}
|
||||
|
||||
@@ -2256,6 +2256,9 @@ message SyncOperation {
|
||||
// Revisions is the list of revision (Git) or chart version (Helm) which to sync each source in sources field for the application to
|
||||
// If omitted, will use the revision specified in app spec.
|
||||
repeated string revisions = 11;
|
||||
|
||||
// SelfHealAttemptsCount contains the number of auto-heal attempts
|
||||
optional int64 autoHealAttemptsCount = 12;
|
||||
}
|
||||
|
||||
// SyncOperationResource contains resources to sync.
|
||||
|
||||
@@ -999,6 +999,12 @@ type ApplicationDestination struct {
|
||||
isServerInferred bool `json:"-"`
|
||||
}
|
||||
|
||||
// SetIsServerInferred sets the isServerInferred flag. This is used to allow comparison between two destinations where
|
||||
// one server is inferred and the other is not.
|
||||
func (d *ApplicationDestination) SetIsServerInferred(inferred bool) {
|
||||
d.isServerInferred = inferred
|
||||
}
|
||||
|
||||
type ResourceHealthLocation string
|
||||
|
||||
var (
|
||||
@@ -1053,15 +1059,15 @@ func (a *ApplicationStatus) GetRevisions() []string {
|
||||
|
||||
// BuildComparedToStatus will build a ComparedTo object based on the current
|
||||
// Application state.
|
||||
func (app *Application) BuildComparedToStatus() ComparedTo {
|
||||
func (spec *ApplicationSpec) BuildComparedToStatus() ComparedTo {
|
||||
ct := ComparedTo{
|
||||
Destination: app.Spec.Destination,
|
||||
IgnoreDifferences: app.Spec.IgnoreDifferences,
|
||||
Destination: spec.Destination,
|
||||
IgnoreDifferences: spec.IgnoreDifferences,
|
||||
}
|
||||
if app.Spec.HasMultipleSources() {
|
||||
ct.Sources = app.Spec.Sources
|
||||
if spec.HasMultipleSources() {
|
||||
ct.Sources = spec.Sources
|
||||
} else {
|
||||
ct.Source = app.Spec.GetSource()
|
||||
ct.Source = spec.GetSource()
|
||||
}
|
||||
return ct
|
||||
}
|
||||
@@ -1171,6 +1177,8 @@ type SyncOperation struct {
|
||||
// Revisions is the list of revision (Git) or chart version (Helm) which to sync each source in sources field for the application to
|
||||
// If omitted, will use the revision specified in app spec.
|
||||
Revisions []string `json:"revisions,omitempty" protobuf:"bytes,11,opt,name=revisions"`
|
||||
// SelfHealAttemptsCount contains the number of auto-heal attempts
|
||||
SelfHealAttemptsCount int64 `json:"autoHealAttemptsCount,omitempty" protobuf:"bytes,12,opt,name=autoHealAttemptsCount"`
|
||||
}
|
||||
|
||||
// IsApplyStrategy returns true if the sync strategy is "apply"
|
||||
@@ -2379,11 +2387,11 @@ func (s *SyncWindows) HasWindows() bool {
|
||||
}
|
||||
|
||||
// Active returns a list of sync windows that are currently active
|
||||
func (s *SyncWindows) Active() *SyncWindows {
|
||||
func (s *SyncWindows) Active() (*SyncWindows, error) {
|
||||
return s.active(time.Now())
|
||||
}
|
||||
|
||||
func (s *SyncWindows) active(currentTime time.Time) *SyncWindows {
|
||||
func (s *SyncWindows) active(currentTime time.Time) (*SyncWindows, error) {
|
||||
// If SyncWindows.Active() is called outside of a UTC locale, it should be
|
||||
// first converted to UTC before we scan through the SyncWindows.
|
||||
currentTime = currentTime.In(time.UTC)
|
||||
@@ -2392,8 +2400,14 @@ func (s *SyncWindows) active(currentTime time.Time) *SyncWindows {
|
||||
var active SyncWindows
|
||||
specParser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
|
||||
for _, w := range *s {
|
||||
schedule, _ := specParser.Parse(w.Schedule)
|
||||
duration, _ := time.ParseDuration(w.Duration)
|
||||
schedule, sErr := specParser.Parse(w.Schedule)
|
||||
if sErr != nil {
|
||||
return nil, fmt.Errorf("cannot parse schedule '%s': %w", w.Schedule, sErr)
|
||||
}
|
||||
duration, dErr := time.ParseDuration(w.Duration)
|
||||
if dErr != nil {
|
||||
return nil, fmt.Errorf("cannot parse duration '%s': %w", w.Duration, dErr)
|
||||
}
|
||||
|
||||
// Offset the nextWindow time to consider the timeZone of the sync window
|
||||
timeZoneOffsetDuration := w.scheduleOffsetByTimeZone()
|
||||
@@ -2403,20 +2417,20 @@ func (s *SyncWindows) active(currentTime time.Time) *SyncWindows {
|
||||
}
|
||||
}
|
||||
if len(active) > 0 {
|
||||
return &active
|
||||
return &active, nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// InactiveAllows will iterate over the SyncWindows and return all inactive allow windows
|
||||
// for the current time. If the current time is in an inactive allow window, syncs will
|
||||
// be denied.
|
||||
func (s *SyncWindows) InactiveAllows() *SyncWindows {
|
||||
func (s *SyncWindows) InactiveAllows() (*SyncWindows, error) {
|
||||
return s.inactiveAllows(time.Now())
|
||||
}
|
||||
|
||||
func (s *SyncWindows) inactiveAllows(currentTime time.Time) *SyncWindows {
|
||||
func (s *SyncWindows) inactiveAllows(currentTime time.Time) (*SyncWindows, error) {
|
||||
// If SyncWindows.InactiveAllows() is called outside of a UTC locale, it should be
|
||||
// first converted to UTC before we scan through the SyncWindows.
|
||||
currentTime = currentTime.In(time.UTC)
|
||||
@@ -2427,21 +2441,27 @@ func (s *SyncWindows) inactiveAllows(currentTime time.Time) *SyncWindows {
|
||||
for _, w := range *s {
|
||||
if w.Kind == "allow" {
|
||||
schedule, sErr := specParser.Parse(w.Schedule)
|
||||
if sErr != nil {
|
||||
return nil, fmt.Errorf("cannot parse schedule '%s': %w", w.Schedule, sErr)
|
||||
}
|
||||
duration, dErr := time.ParseDuration(w.Duration)
|
||||
if dErr != nil {
|
||||
return nil, fmt.Errorf("cannot parse duration '%s': %w", w.Duration, dErr)
|
||||
}
|
||||
// Offset the nextWindow time to consider the timeZone of the sync window
|
||||
timeZoneOffsetDuration := w.scheduleOffsetByTimeZone()
|
||||
nextWindow := schedule.Next(currentTime.Add(timeZoneOffsetDuration - duration))
|
||||
|
||||
if !nextWindow.Before(currentTime.Add(timeZoneOffsetDuration)) && sErr == nil && dErr == nil {
|
||||
if !nextWindow.Before(currentTime.Add(timeZoneOffsetDuration)) {
|
||||
inactive = append(inactive, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(inactive) > 0 {
|
||||
return &inactive
|
||||
return &inactive, nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (w *SyncWindow) scheduleOffsetByTimeZone() time.Duration {
|
||||
@@ -2545,36 +2565,42 @@ func (w *SyncWindows) Matches(app *Application) *SyncWindows {
|
||||
}
|
||||
|
||||
// CanSync returns true if a sync window currently allows a sync. isManual indicates whether the sync has been triggered manually.
|
||||
func (w *SyncWindows) CanSync(isManual bool) bool {
|
||||
func (w *SyncWindows) CanSync(isManual bool) (bool, error) {
|
||||
if !w.HasWindows() {
|
||||
return true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
active := w.Active()
|
||||
active, err := w.Active()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid sync windows: %w", err)
|
||||
}
|
||||
hasActiveDeny, manualEnabled := active.hasDeny()
|
||||
|
||||
if hasActiveDeny {
|
||||
if isManual && manualEnabled {
|
||||
return true
|
||||
return true, nil
|
||||
} else {
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if active.hasAllow() {
|
||||
return true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
inactiveAllows := w.InactiveAllows()
|
||||
inactiveAllows, err := w.InactiveAllows()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid sync windows: %w", err)
|
||||
}
|
||||
if inactiveAllows.HasWindows() {
|
||||
if isManual && inactiveAllows.manualEnabled() {
|
||||
return true
|
||||
return true, nil
|
||||
} else {
|
||||
return false
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// hasDeny will iterate over the SyncWindows and return if a deny window is found and if
|
||||
@@ -2629,24 +2655,30 @@ func (w *SyncWindows) manualEnabled() bool {
|
||||
}
|
||||
|
||||
// Active returns true if the sync window is currently active
|
||||
func (w SyncWindow) Active() bool {
|
||||
func (w SyncWindow) Active() (bool, error) {
|
||||
return w.active(time.Now())
|
||||
}
|
||||
|
||||
func (w SyncWindow) active(currentTime time.Time) bool {
|
||||
func (w SyncWindow) active(currentTime time.Time) (bool, error) {
|
||||
// If SyncWindow.Active() is called outside of a UTC locale, it should be
|
||||
// first converted to UTC before search
|
||||
currentTime = currentTime.UTC()
|
||||
|
||||
specParser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
|
||||
schedule, _ := specParser.Parse(w.Schedule)
|
||||
duration, _ := time.ParseDuration(w.Duration)
|
||||
schedule, sErr := specParser.Parse(w.Schedule)
|
||||
if sErr != nil {
|
||||
return false, fmt.Errorf("cannot parse schedule '%s': %w", w.Schedule, sErr)
|
||||
}
|
||||
duration, dErr := time.ParseDuration(w.Duration)
|
||||
if dErr != nil {
|
||||
return false, fmt.Errorf("cannot parse duration '%s': %w", w.Duration, dErr)
|
||||
}
|
||||
|
||||
// Offset the nextWindow time to consider the timeZone of the sync window
|
||||
timeZoneOffsetDuration := w.scheduleOffsetByTimeZone()
|
||||
nextWindow := schedule.Next(currentTime.Add(timeZoneOffsetDuration - duration))
|
||||
|
||||
return nextWindow.Before(currentTime.Add(timeZoneOffsetDuration))
|
||||
return nextWindow.Before(currentTime.Add(timeZoneOffsetDuration)), nil
|
||||
}
|
||||
|
||||
// Update updates a sync window's settings with the given parameter
|
||||
@@ -2767,11 +2799,11 @@ type KustomizeOptions struct {
|
||||
// ApplicationDestinationServiceAccount holds information about the service account to be impersonated for the application sync operation.
|
||||
type ApplicationDestinationServiceAccount struct {
|
||||
// Server specifies the URL of the target cluster's Kubernetes control plane API.
|
||||
Server string `json:"server,omitempty" protobuf:"bytes,1,opt,name=server"`
|
||||
Server string `json:"server" protobuf:"bytes,1,opt,name=server"`
|
||||
// Namespace specifies the target namespace for the application's resources.
|
||||
Namespace string `json:"namespace,omitempty" protobuf:"bytes,2,opt,name=namespace"`
|
||||
// ServiceAccountName to be used for impersonation during the sync operation
|
||||
DefaultServiceAccount string `json:"defaultServiceAccount,omitempty" protobuf:"bytes,3,opt,name=defaultServiceAccount"`
|
||||
// DefaultServiceAccount to be used for impersonation during the sync operation
|
||||
DefaultServiceAccount string `json:"defaultServiceAccount" protobuf:"bytes,3,opt,name=defaultServiceAccount"`
|
||||
}
|
||||
|
||||
// CascadedDeletion indicates if the deletion finalizer is set and controller should delete the application and it's cascaded resources
|
||||
|
||||
@@ -1778,7 +1778,9 @@ func TestSyncWindows_HasWindows(t *testing.T) {
|
||||
func TestSyncWindows_Active(t *testing.T) {
|
||||
t.Run("WithTestProject", func(t *testing.T) {
|
||||
proj := newTestProjectWithSyncWindows()
|
||||
assert.Len(t, *proj.Spec.SyncWindows.Active(), 1)
|
||||
activeWindows, err := proj.Spec.SyncWindows.Active()
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, *activeWindows, 1)
|
||||
})
|
||||
|
||||
syncWindow := func(kind string, schedule string, duration string, timeZone string) *SyncWindow {
|
||||
@@ -1805,6 +1807,7 @@ func TestSyncWindows_Active(t *testing.T) {
|
||||
currentTime time.Time
|
||||
matchingIndex int
|
||||
expectedLength int
|
||||
isErr bool
|
||||
}{
|
||||
{
|
||||
name: "MatchFirst",
|
||||
@@ -1912,11 +1915,36 @@ func TestSyncWindows_Active(t *testing.T) {
|
||||
matchingIndex: 0,
|
||||
expectedLength: 1,
|
||||
},
|
||||
{
|
||||
name: "MatchNone-InvalidSchedule",
|
||||
syncWindow: SyncWindows{
|
||||
syncWindow("allow", "* 10 * * 7", "3h", ""),
|
||||
syncWindow("allow", "* 11 * * 7", "3h", ""),
|
||||
},
|
||||
currentTime: timeWithHour(12, time.UTC),
|
||||
expectedLength: 0,
|
||||
isErr: true,
|
||||
},
|
||||
{
|
||||
name: "MatchNone-InvalidDuration",
|
||||
syncWindow: SyncWindows{
|
||||
syncWindow("allow", "* 10 * * *", "3a", ""),
|
||||
syncWindow("allow", "* 11 * * *", "3a", ""),
|
||||
},
|
||||
currentTime: timeWithHour(12, time.UTC),
|
||||
expectedLength: 0,
|
||||
isErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.syncWindow.active(tt.currentTime)
|
||||
result, err := tt.syncWindow.active(tt.currentTime)
|
||||
if tt.isErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
if result == nil {
|
||||
result = &SyncWindows{}
|
||||
}
|
||||
@@ -1933,7 +1961,9 @@ func TestSyncWindows_InactiveAllows(t *testing.T) {
|
||||
t.Run("WithTestProject", func(t *testing.T) {
|
||||
proj := newTestProjectWithSyncWindows()
|
||||
proj.Spec.SyncWindows[0].Schedule = "0 0 1 1 1"
|
||||
assert.Len(t, *proj.Spec.SyncWindows.InactiveAllows(), 1)
|
||||
inactiveAllowWindows, err := proj.Spec.SyncWindows.InactiveAllows()
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, *inactiveAllowWindows, 1)
|
||||
})
|
||||
|
||||
syncWindow := func(kind string, schedule string, duration string, timeZone string) *SyncWindow {
|
||||
@@ -1960,6 +1990,7 @@ func TestSyncWindows_InactiveAllows(t *testing.T) {
|
||||
currentTime time.Time
|
||||
matchingIndex int
|
||||
expectedLength int
|
||||
isErr bool
|
||||
}{
|
||||
{
|
||||
name: "MatchFirst",
|
||||
@@ -2085,11 +2116,34 @@ func TestSyncWindows_InactiveAllows(t *testing.T) {
|
||||
matchingIndex: 0,
|
||||
expectedLength: 1,
|
||||
},
|
||||
{
|
||||
name: "MatchNone-InvalidSchedule",
|
||||
syncWindow: SyncWindows{
|
||||
syncWindow("allow", "* 10 * * 7", "2h", ""),
|
||||
},
|
||||
currentTime: timeWithHour(17, time.UTC),
|
||||
expectedLength: 0,
|
||||
isErr: true,
|
||||
},
|
||||
{
|
||||
name: "MatchNone-InvalidDuration",
|
||||
syncWindow: SyncWindows{
|
||||
syncWindow("allow", "* 10 * * *", "2a", ""),
|
||||
},
|
||||
currentTime: timeWithHour(17, time.UTC),
|
||||
expectedLength: 0,
|
||||
isErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.syncWindow.inactiveAllows(tt.currentTime)
|
||||
result, err := tt.syncWindow.inactiveAllows(tt.currentTime)
|
||||
if tt.isErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
if result == nil {
|
||||
result = &SyncWindows{}
|
||||
}
|
||||
@@ -2200,9 +2254,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
proj := newProjectBuilder().withInactiveDenyWindow(true).build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(true)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(true)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.True(t, canSync)
|
||||
})
|
||||
t.Run("will allow manual sync if inactive-deny-window set with manual false", func(t *testing.T) {
|
||||
@@ -2211,9 +2266,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
proj := newProjectBuilder().withInactiveDenyWindow(false).build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(true)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(true)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.True(t, canSync)
|
||||
})
|
||||
t.Run("will deny manual sync if one inactive-allow-windows set with manual false", func(t *testing.T) {
|
||||
@@ -2225,9 +2281,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(true)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(true)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.False(t, canSync)
|
||||
})
|
||||
t.Run("will allow manual sync if on active-allow-window set with manual true", func(t *testing.T) {
|
||||
@@ -2238,9 +2295,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(true)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(true)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.True(t, canSync)
|
||||
})
|
||||
t.Run("will allow manual sync if on active-allow-window set with manual false", func(t *testing.T) {
|
||||
@@ -2251,9 +2309,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(true)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(true)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.True(t, canSync)
|
||||
})
|
||||
t.Run("will allow auto sync if on active-allow-window", func(t *testing.T) {
|
||||
@@ -2264,9 +2323,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(false)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(false)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.True(t, canSync)
|
||||
})
|
||||
t.Run("will allow manual sync active-allow and inactive-deny", func(t *testing.T) {
|
||||
@@ -2278,9 +2338,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(true)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(true)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.True(t, canSync)
|
||||
})
|
||||
t.Run("will allow auto sync active-allow and inactive-deny", func(t *testing.T) {
|
||||
@@ -2292,9 +2353,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(false)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(false)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.True(t, canSync)
|
||||
})
|
||||
t.Run("will deny manual sync inactive-allow", func(t *testing.T) {
|
||||
@@ -2305,9 +2367,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(true)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(true)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.False(t, canSync)
|
||||
})
|
||||
t.Run("will deny auto sync inactive-allow", func(t *testing.T) {
|
||||
@@ -2318,9 +2381,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(false)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(false)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.False(t, canSync)
|
||||
})
|
||||
t.Run("will allow manual sync inactive-allow with ManualSync enabled", func(t *testing.T) {
|
||||
@@ -2331,9 +2395,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(true)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(true)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.True(t, canSync)
|
||||
})
|
||||
t.Run("will deny auto sync inactive-allow with ManualSync enabled", func(t *testing.T) {
|
||||
@@ -2344,9 +2409,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(false)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(false)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.False(t, canSync)
|
||||
})
|
||||
t.Run("will deny manual sync with inactive-allow and inactive-deny", func(t *testing.T) {
|
||||
@@ -2358,9 +2424,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(true)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(true)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.False(t, canSync)
|
||||
})
|
||||
t.Run("will deny auto sync with inactive-allow and inactive-deny", func(t *testing.T) {
|
||||
@@ -2372,9 +2439,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(false)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(false)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.False(t, canSync)
|
||||
})
|
||||
t.Run("will allow auto sync with active-allow and inactive-allow", func(t *testing.T) {
|
||||
@@ -2386,9 +2454,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(false)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(false)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.True(t, canSync)
|
||||
})
|
||||
t.Run("will deny manual sync with active-deny", func(t *testing.T) {
|
||||
@@ -2399,9 +2468,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(true)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(true)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.False(t, canSync)
|
||||
})
|
||||
t.Run("will deny auto sync with active-deny", func(t *testing.T) {
|
||||
@@ -2412,9 +2482,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(false)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(false)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.False(t, canSync)
|
||||
})
|
||||
t.Run("will allow manual sync with active-deny with ManualSync enabled", func(t *testing.T) {
|
||||
@@ -2425,9 +2496,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(true)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(true)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.True(t, canSync)
|
||||
})
|
||||
t.Run("will deny auto sync with active-deny with ManualSync enabled", func(t *testing.T) {
|
||||
@@ -2438,9 +2510,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(false)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(false)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.False(t, canSync)
|
||||
})
|
||||
t.Run("will deny manual sync with many active-deny having one with ManualSync disabled", func(t *testing.T) {
|
||||
@@ -2454,9 +2527,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(true)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(true)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.False(t, canSync)
|
||||
})
|
||||
t.Run("will deny auto sync with many active-deny having one with ManualSync disabled", func(t *testing.T) {
|
||||
@@ -2470,9 +2544,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(false)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(false)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.False(t, canSync)
|
||||
})
|
||||
t.Run("will deny manual sync with active-deny and active-allow windows with ManualSync disabled", func(t *testing.T) {
|
||||
@@ -2484,9 +2559,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(true)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(true)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.False(t, canSync)
|
||||
})
|
||||
t.Run("will allow manual sync with active-deny and active-allow windows with ManualSync enabled", func(t *testing.T) {
|
||||
@@ -2498,9 +2574,10 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(true)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(true)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.True(t, canSync)
|
||||
})
|
||||
t.Run("will deny auto sync with active-deny and active-allow windows with ManualSync enabled", func(t *testing.T) {
|
||||
@@ -2512,9 +2589,24 @@ func TestSyncWindows_CanSync(t *testing.T) {
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync := proj.Spec.SyncWindows.CanSync(false)
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(false)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
assert.False(t, canSync)
|
||||
})
|
||||
t.Run("will deny and return error with invalid windows", func(t *testing.T) {
|
||||
// given
|
||||
t.Parallel()
|
||||
proj := newProjectBuilder().
|
||||
withInvalidWindows().
|
||||
build()
|
||||
|
||||
// when
|
||||
canSync, err := proj.Spec.SyncWindows.CanSync(false)
|
||||
|
||||
// then
|
||||
require.Error(t, err)
|
||||
assert.False(t, canSync)
|
||||
})
|
||||
}
|
||||
@@ -2564,8 +2656,9 @@ func TestSyncWindows_hasAllow(t *testing.T) {
|
||||
func TestSyncWindow_Active(t *testing.T) {
|
||||
window := &SyncWindow{Schedule: "* * * * *", Duration: "1h"}
|
||||
t.Run("ActiveWindow", func(t *testing.T) {
|
||||
window.Active()
|
||||
assert.True(t, window.Active())
|
||||
isActive, err := window.Active()
|
||||
require.NoError(t, err)
|
||||
assert.True(t, isActive)
|
||||
})
|
||||
|
||||
syncWindow := func(kind string, schedule string, duration string) SyncWindow {
|
||||
@@ -2590,6 +2683,7 @@ func TestSyncWindow_Active(t *testing.T) {
|
||||
syncWindow SyncWindow
|
||||
currentTime time.Time
|
||||
expectedResult bool
|
||||
isErr bool
|
||||
}{
|
||||
{
|
||||
name: "Allow-active",
|
||||
@@ -2639,11 +2733,44 @@ func TestSyncWindow_Active(t *testing.T) {
|
||||
currentTime: timeWithHour(13-4, utcM4Zone),
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "Allow-inactive-InvalidSchedule",
|
||||
syncWindow: syncWindow("allow", "* 10 * * 7", "2h"),
|
||||
currentTime: timeWithHour(11, time.UTC),
|
||||
expectedResult: false,
|
||||
isErr: true,
|
||||
},
|
||||
{
|
||||
name: "Deny-inactive-InvalidSchedule",
|
||||
syncWindow: syncWindow("deny", "* 10 * * 7", "2h"),
|
||||
currentTime: timeWithHour(11, time.UTC),
|
||||
expectedResult: false,
|
||||
isErr: true,
|
||||
},
|
||||
{
|
||||
name: "Allow-inactive-InvalidDuration",
|
||||
syncWindow: syncWindow("allow", "* 10 * * *", "2a"),
|
||||
currentTime: timeWithHour(11, time.UTC),
|
||||
expectedResult: false,
|
||||
isErr: true,
|
||||
},
|
||||
{
|
||||
name: "Deny-inactive-InvalidDuration",
|
||||
syncWindow: syncWindow("deny", "* 10 * * *", "2a"),
|
||||
currentTime: timeWithHour(11, time.UTC),
|
||||
expectedResult: false,
|
||||
isErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.syncWindow.active(tt.currentTime)
|
||||
result, err := tt.syncWindow.active(tt.currentTime)
|
||||
if tt.isErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tt.expectedResult, result)
|
||||
})
|
||||
}
|
||||
@@ -2755,6 +2882,16 @@ func (b *projectBuilder) withInactiveDenyWindow(allowManual bool) *projectBuilde
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *projectBuilder) withInvalidWindows() *projectBuilder {
|
||||
b.proj.Spec.SyncWindows = append(b.proj.Spec.SyncWindows,
|
||||
newSyncWindow("allow", "* 10 * * 7", false),
|
||||
newSyncWindow("deny", "* 10 * * 7", false),
|
||||
newSyncWindow("allow", "* 10 * * 7", true),
|
||||
newSyncWindow("deny", "* 10 * * 7", true),
|
||||
)
|
||||
return b
|
||||
}
|
||||
|
||||
func inactiveCronSchedule() string {
|
||||
hourPlus10, _, _ := time.Now().Add(10 * time.Hour).Clock()
|
||||
return fmt.Sprintf("0 %d * * *", hourPlus10)
|
||||
@@ -3959,3 +4096,158 @@ func TestApplicationTree_Merge(t *testing.T) {
|
||||
},
|
||||
}, tree)
|
||||
}
|
||||
|
||||
func TestAppProject_ValidateDestinationServiceAccount(t *testing.T) {
|
||||
testData := []struct {
|
||||
server string
|
||||
namespace string
|
||||
defaultServiceAccount string
|
||||
expectedErrMsg string
|
||||
}{
|
||||
{
|
||||
// Given, a project
|
||||
// When, a default destination service account with all valid fields is added to it,
|
||||
// Then, there is no error.
|
||||
server: "https://192.168.99.100:8443",
|
||||
namespace: "test-ns",
|
||||
defaultServiceAccount: "test-sa",
|
||||
expectedErrMsg: "",
|
||||
},
|
||||
{
|
||||
// Given, a project
|
||||
// When, a default destination service account with negation glob pattern for server is added,
|
||||
// Then, there is an error with appropriate message.
|
||||
server: "!abc",
|
||||
namespace: "test-ns",
|
||||
defaultServiceAccount: "test-sa",
|
||||
expectedErrMsg: "server has an invalid format, '!abc'",
|
||||
},
|
||||
{
|
||||
// Given, a project
|
||||
// When, a default destination service account with empty namespace is added to it,
|
||||
// Then, there is no error.
|
||||
server: "https://192.168.99.100:8443",
|
||||
namespace: "",
|
||||
defaultServiceAccount: "test-sa",
|
||||
expectedErrMsg: "",
|
||||
},
|
||||
{
|
||||
// Given, a project,
|
||||
// When, a default destination service account with negation glob pattern for server is added,
|
||||
// Then, there is an error with appropriate message.
|
||||
server: "!*",
|
||||
namespace: "test-ns",
|
||||
defaultServiceAccount: "test-sa",
|
||||
expectedErrMsg: "server has an invalid format, '!*'",
|
||||
},
|
||||
{
|
||||
// Given, a project,
|
||||
// When, a default destination service account with negation glob pattern for namespace is added,
|
||||
// Then, there is an error with appropriate message.
|
||||
server: "https://192.168.99.100:8443",
|
||||
namespace: "!*",
|
||||
defaultServiceAccount: "test-sa",
|
||||
expectedErrMsg: "namespace has an invalid format, '!*'",
|
||||
},
|
||||
{
|
||||
// Given, a project,
|
||||
// When, a default destination service account with negation glob pattern for namespace is added,
|
||||
// Then, there is an error with appropriate message.
|
||||
server: "https://192.168.99.100:8443",
|
||||
namespace: "!abc",
|
||||
defaultServiceAccount: "test-sa",
|
||||
expectedErrMsg: "namespace has an invalid format, '!abc'",
|
||||
},
|
||||
{
|
||||
// Given, a project,
|
||||
// When, a default destination service account with empty service account is added,
|
||||
// Then, there is an error with appropriate message.
|
||||
server: "https://192.168.99.100:8443",
|
||||
namespace: "test-ns",
|
||||
defaultServiceAccount: "",
|
||||
expectedErrMsg: "defaultServiceAccount has an invalid format, ''",
|
||||
},
|
||||
{
|
||||
// Given, a project,
|
||||
// When, a default destination service account with service account having just white spaces is added,
|
||||
// Then, there is an error with appropriate message.
|
||||
server: "https://192.168.99.100:8443",
|
||||
namespace: "test-ns",
|
||||
defaultServiceAccount: " ",
|
||||
expectedErrMsg: "defaultServiceAccount has an invalid format, ' '",
|
||||
},
|
||||
{
|
||||
// Given, a project,
|
||||
// When, a default destination service account with service account having backwards slash char is added,
|
||||
// Then, there is an error with appropriate message.
|
||||
server: "https://192.168.99.100:8443",
|
||||
namespace: "test-ns",
|
||||
defaultServiceAccount: "test\\sa",
|
||||
expectedErrMsg: "defaultServiceAccount has an invalid format, 'test\\sa'",
|
||||
},
|
||||
{
|
||||
// Given, a project,
|
||||
// When, a default destination service account with service account having forward slash char is added,
|
||||
// Then, there is an error with appropriate message.
|
||||
server: "https://192.168.99.100:8443",
|
||||
namespace: "test-ns",
|
||||
defaultServiceAccount: "test/sa",
|
||||
expectedErrMsg: "defaultServiceAccount has an invalid format, 'test/sa'",
|
||||
},
|
||||
{
|
||||
// Given, a project,
|
||||
// When, a default destination service account with service account having square braces char is added,
|
||||
// Then, there is an error with appropriate message.
|
||||
server: "https://192.168.99.100:8443",
|
||||
namespace: "test-ns",
|
||||
defaultServiceAccount: "[test-sa]",
|
||||
expectedErrMsg: "defaultServiceAccount has an invalid format, '[test-sa]'",
|
||||
},
|
||||
{
|
||||
// Given, a project,
|
||||
// When, a default destination service account with service account having curly braces char is added,
|
||||
// Then, there is an error with appropriate message.
|
||||
server: "https://192.168.99.100:8443",
|
||||
namespace: "test-ns",
|
||||
defaultServiceAccount: "{test-sa}",
|
||||
expectedErrMsg: "defaultServiceAccount has an invalid format, '{test-sa}'",
|
||||
},
|
||||
{
|
||||
// Given, a project,
|
||||
// When, a default destination service account with service account having curly braces char is added,
|
||||
// Then, there is an error with appropriate message.
|
||||
server: "[[ech*",
|
||||
namespace: "test-ns",
|
||||
defaultServiceAccount: "test-sa",
|
||||
expectedErrMsg: "server has an invalid format, '[[ech*'",
|
||||
},
|
||||
{
|
||||
// Given, a project,
|
||||
// When, a default destination service account with service account having curly braces char is added,
|
||||
// Then, there is an error with appropriate message.
|
||||
server: "https://192.168.99.100:8443",
|
||||
namespace: "[[ech*",
|
||||
defaultServiceAccount: "test-sa",
|
||||
expectedErrMsg: "namespace has an invalid format, '[[ech*'",
|
||||
},
|
||||
}
|
||||
for _, data := range testData {
|
||||
proj := AppProject{
|
||||
Spec: AppProjectSpec{
|
||||
DestinationServiceAccounts: []ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: data.server,
|
||||
Namespace: data.namespace,
|
||||
DefaultServiceAccount: data.defaultServiceAccount,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
err := proj.ValidateProject()
|
||||
if data.expectedErrMsg == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.ErrorContains(t, err, data.expectedErrMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
403
reposerver/apiclient/repository.pb.go
generated
403
reposerver/apiclient/repository.pb.go
generated
@@ -59,7 +59,9 @@ type ManifestRequest struct {
|
||||
// This is used to surface "source not permitted" errors for Helm repositories
|
||||
ProjectSourceRepos []string `protobuf:"bytes,24,rep,name=projectSourceRepos,proto3" json:"projectSourceRepos,omitempty"`
|
||||
// This is used to surface "source not permitted" errors for Helm repositories
|
||||
ProjectName string `protobuf:"bytes,25,opt,name=projectName,proto3" json:"projectName,omitempty"`
|
||||
ProjectName string `protobuf:"bytes,25,opt,name=projectName,proto3" json:"projectName,omitempty"`
|
||||
// Holds instance installation id
|
||||
InstallationID string `protobuf:"bytes,27,opt,name=installationID,proto3" json:"installationID,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
@@ -252,6 +254,13 @@ func (m *ManifestRequest) GetProjectName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *ManifestRequest) GetInstallationID() string {
|
||||
if m != nil {
|
||||
return m.InstallationID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ManifestRequestWithFiles struct {
|
||||
// Types that are valid to be assigned to Part:
|
||||
// *ManifestRequestWithFiles_Request
|
||||
@@ -2208,6 +2217,7 @@ type UpdateRevisionForPathsRequest struct {
|
||||
Revision string `protobuf:"bytes,12,opt,name=revision,proto3" json:"revision,omitempty"`
|
||||
Paths []string `protobuf:"bytes,13,rep,name=paths,proto3" json:"paths,omitempty"`
|
||||
NoRevisionCache bool `protobuf:"varint,14,opt,name=noRevisionCache,proto3" json:"noRevisionCache,omitempty"`
|
||||
InstallationID string `protobuf:"bytes,15,opt,name=installationID,proto3" json:"installationID,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
@@ -2344,6 +2354,13 @@ func (m *UpdateRevisionForPathsRequest) GetNoRevisionCache() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *UpdateRevisionForPathsRequest) GetInstallationID() string {
|
||||
if m != nil {
|
||||
return m.InstallationID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type UpdateRevisionForPathsResponse struct {
|
||||
Changes bool `protobuf:"varint,1,opt,name=changes,proto3" json:"changes,omitempty"`
|
||||
Revision string `protobuf:"bytes,2,opt,name=revision,proto3" json:"revision,omitempty"`
|
||||
@@ -2449,153 +2466,155 @@ func init() {
|
||||
}
|
||||
|
||||
var fileDescriptor_dd8723cfcc820480 = []byte{
|
||||
// 2332 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x5a, 0xcd, 0x73, 0x1c, 0x47,
|
||||
0x15, 0xd7, 0x7e, 0x6a, 0xf7, 0xad, 0x2c, 0xad, 0xda, 0xb6, 0x3c, 0xde, 0xd8, 0x2a, 0x65, 0xc0,
|
||||
0x2e, 0xc7, 0x4e, 0x76, 0xcb, 0x72, 0x25, 0x06, 0x27, 0x84, 0x52, 0x14, 0x5b, 0x72, 0x6c, 0xd9,
|
||||
0x62, 0xec, 0x84, 0x32, 0x18, 0xa8, 0xde, 0xd9, 0xde, 0xd9, 0xc9, 0xce, 0x47, 0x7b, 0xa6, 0x47,
|
||||
0x61, 0x5d, 0xc5, 0x09, 0x8a, 0x0b, 0x77, 0x0e, 0x5c, 0xf9, 0x1b, 0x28, 0x8e, 0x1c, 0x28, 0x0a,
|
||||
0x8e, 0x14, 0x17, 0xaa, 0xb8, 0x40, 0xf9, 0xc8, 0x5f, 0x41, 0x75, 0x4f, 0xcf, 0xe7, 0xce, 0xae,
|
||||
0x15, 0xd6, 0x56, 0x20, 0x17, 0x69, 0xfa, 0x75, 0xf7, 0x7b, 0xaf, 0x5f, 0xbf, 0xf7, 0xfa, 0xf7,
|
||||
0xba, 0x17, 0x2e, 0x7b, 0x84, 0xba, 0x3e, 0xf1, 0x8e, 0x88, 0xd7, 0x13, 0x9f, 0x26, 0x73, 0xbd,
|
||||
0x49, 0xea, 0xb3, 0x4b, 0x3d, 0x97, 0xb9, 0x08, 0x12, 0x4a, 0xe7, 0xbe, 0x61, 0xb2, 0x51, 0xd0,
|
||||
0xef, 0xea, 0xae, 0xdd, 0xc3, 0x9e, 0xe1, 0x52, 0xcf, 0xfd, 0x5c, 0x7c, 0xbc, 0xa3, 0x0f, 0x7a,
|
||||
0x47, 0xdb, 0x3d, 0x3a, 0x36, 0x7a, 0x98, 0x9a, 0x7e, 0x0f, 0x53, 0x6a, 0x99, 0x3a, 0x66, 0xa6,
|
||||
0xeb, 0xf4, 0x8e, 0xae, 0x63, 0x8b, 0x8e, 0xf0, 0xf5, 0x9e, 0x41, 0x1c, 0xe2, 0x61, 0x46, 0x06,
|
||||
0x21, 0xe7, 0xce, 0x1b, 0x86, 0xeb, 0x1a, 0x16, 0xe9, 0x89, 0x56, 0x3f, 0x18, 0xf6, 0x88, 0x4d,
|
||||
0x99, 0x14, 0xab, 0xfe, 0x7b, 0x05, 0xd6, 0x0e, 0xb0, 0x63, 0x0e, 0x89, 0xcf, 0x34, 0xf2, 0x2c,
|
||||
0x20, 0x3e, 0x43, 0x4f, 0xa1, 0xca, 0x95, 0x51, 0x4a, 0x5b, 0xa5, 0x2b, 0xad, 0xed, 0xfd, 0x6e,
|
||||
0xa2, 0x4d, 0x37, 0xd2, 0x46, 0x7c, 0xfc, 0x44, 0x1f, 0x74, 0x8f, 0xb6, 0xbb, 0x74, 0x6c, 0x74,
|
||||
0xb9, 0x36, 0xdd, 0x94, 0x36, 0xdd, 0x48, 0x9b, 0xae, 0x16, 0x2f, 0x4b, 0x13, 0x5c, 0x51, 0x07,
|
||||
0x1a, 0x1e, 0x39, 0x32, 0x7d, 0xd3, 0x75, 0x94, 0xf2, 0x56, 0xe9, 0x4a, 0x53, 0x8b, 0xdb, 0x48,
|
||||
0x81, 0x65, 0xc7, 0xdd, 0xc5, 0xfa, 0x88, 0x28, 0x95, 0xad, 0xd2, 0x95, 0x86, 0x16, 0x35, 0xd1,
|
||||
0x16, 0xb4, 0x30, 0xa5, 0xf7, 0x71, 0x9f, 0x58, 0xf7, 0xc8, 0x44, 0xa9, 0x8a, 0x89, 0x69, 0x12,
|
||||
0x9f, 0x8b, 0x29, 0x7d, 0x80, 0x6d, 0xa2, 0xd4, 0x44, 0x6f, 0xd4, 0x44, 0x17, 0xa0, 0xe9, 0x60,
|
||||
0x9b, 0xf8, 0x14, 0xeb, 0x44, 0x69, 0x88, 0xbe, 0x84, 0x80, 0x7e, 0x06, 0xeb, 0x29, 0xc5, 0x1f,
|
||||
0xb9, 0x81, 0xa7, 0x13, 0x05, 0xc4, 0xd2, 0x1f, 0x2e, 0xb6, 0xf4, 0x9d, 0x3c, 0x5b, 0x6d, 0x5a,
|
||||
0x12, 0xfa, 0x31, 0xd4, 0xc4, 0xce, 0x2b, 0xad, 0xad, 0xca, 0x2b, 0xb5, 0x76, 0xc8, 0x16, 0x39,
|
||||
0xb0, 0x4c, 0xad, 0xc0, 0x30, 0x1d, 0x5f, 0x59, 0x11, 0x12, 0x1e, 0x2f, 0x26, 0x61, 0xd7, 0x75,
|
||||
0x86, 0xa6, 0x71, 0x80, 0x1d, 0x6c, 0x10, 0x9b, 0x38, 0xec, 0x50, 0x30, 0xd7, 0x22, 0x21, 0xe8,
|
||||
0x39, 0xb4, 0xc7, 0x81, 0xcf, 0x5c, 0xdb, 0x7c, 0x4e, 0x1e, 0x52, 0x3e, 0xd7, 0x57, 0x4e, 0x09,
|
||||
0x6b, 0x3e, 0x58, 0x4c, 0xf0, 0xbd, 0x1c, 0x57, 0x6d, 0x4a, 0x0e, 0x77, 0x92, 0x71, 0xd0, 0x27,
|
||||
0x9f, 0x11, 0x4f, 0x78, 0xd7, 0x6a, 0xe8, 0x24, 0x29, 0x52, 0xe8, 0x46, 0xa6, 0x6c, 0xf9, 0xca,
|
||||
0xda, 0x56, 0x25, 0x74, 0xa3, 0x98, 0x84, 0xae, 0xc0, 0xda, 0x11, 0xf1, 0xcc, 0xe1, 0xe4, 0x91,
|
||||
0x69, 0x38, 0x98, 0x05, 0x1e, 0x51, 0xda, 0xc2, 0x15, 0xf3, 0x64, 0x64, 0xc3, 0xa9, 0x11, 0xb1,
|
||||
0x6c, 0x6e, 0xf2, 0x5d, 0x8f, 0x0c, 0x7c, 0x65, 0x5d, 0xd8, 0x77, 0x6f, 0xf1, 0x1d, 0x14, 0xec,
|
||||
0xb4, 0x2c, 0x77, 0xae, 0x98, 0xe3, 0x6a, 0x32, 0x52, 0xc2, 0x18, 0x41, 0xa1, 0x62, 0x39, 0x32,
|
||||
0xba, 0x0c, 0xab, 0xcc, 0xc3, 0xfa, 0xd8, 0x74, 0x8c, 0x03, 0xc2, 0x46, 0xee, 0x40, 0x39, 0x2d,
|
||||
0x2c, 0x91, 0xa3, 0x22, 0x1d, 0x10, 0x71, 0x70, 0xdf, 0x22, 0x83, 0xd0, 0x17, 0x1f, 0x4f, 0x28,
|
||||
0xf1, 0x95, 0x33, 0x62, 0x15, 0x37, 0xba, 0xa9, 0x0c, 0x95, 0x4b, 0x10, 0xdd, 0xdb, 0x53, 0xb3,
|
||||
0x6e, 0x3b, 0xcc, 0x9b, 0x68, 0x05, 0xec, 0xd0, 0x18, 0x5a, 0x7c, 0x1d, 0x91, 0x2b, 0x9c, 0x15,
|
||||
0xae, 0x70, 0x77, 0x31, 0x1b, 0xed, 0x27, 0x0c, 0xb5, 0x34, 0x77, 0xd4, 0x05, 0x34, 0xc2, 0xfe,
|
||||
0x41, 0x60, 0x31, 0x93, 0x5a, 0x24, 0x54, 0xc3, 0x57, 0x36, 0x84, 0x99, 0x0a, 0x7a, 0xd0, 0x3d,
|
||||
0x00, 0x8f, 0x0c, 0xa3, 0x71, 0xe7, 0xc4, 0xca, 0xaf, 0xcd, 0x5b, 0xb9, 0x16, 0x8f, 0x0e, 0x57,
|
||||
0x9c, 0x9a, 0xce, 0x85, 0xf3, 0x65, 0x10, 0x9d, 0xc9, 0x68, 0x17, 0x61, 0xad, 0x08, 0x17, 0x2b,
|
||||
0xe8, 0xe1, 0xbe, 0x28, 0xa9, 0x22, 0x69, 0x9d, 0x0f, 0xbd, 0x35, 0x45, 0xea, 0xdc, 0x86, 0x73,
|
||||
0x33, 0x4c, 0x8d, 0xda, 0x50, 0x19, 0x93, 0x89, 0x48, 0xd1, 0x4d, 0x8d, 0x7f, 0xa2, 0x33, 0x50,
|
||||
0x3b, 0xc2, 0x56, 0x40, 0x44, 0x52, 0x6d, 0x68, 0x61, 0xe3, 0x56, 0xf9, 0x5b, 0xa5, 0xce, 0x2f,
|
||||
0x4b, 0xb0, 0x96, 0x53, 0xbc, 0x60, 0xfe, 0x8f, 0xd2, 0xf3, 0x5f, 0x81, 0x1b, 0x0f, 0x1f, 0x63,
|
||||
0xcf, 0x20, 0x2c, 0xa5, 0x88, 0xfa, 0xb7, 0x12, 0x28, 0x39, 0x8b, 0x7e, 0xdf, 0x64, 0xa3, 0x3b,
|
||||
0xa6, 0x45, 0x7c, 0x74, 0x13, 0x96, 0xbd, 0x90, 0x26, 0x0f, 0x9e, 0x37, 0xe6, 0x6c, 0xc4, 0xfe,
|
||||
0x92, 0x16, 0x8d, 0x46, 0x1f, 0x42, 0xc3, 0x26, 0x0c, 0x0f, 0x30, 0xc3, 0x52, 0xf7, 0xad, 0xa2,
|
||||
0x99, 0x5c, 0xca, 0x81, 0x1c, 0xb7, 0xbf, 0xa4, 0xc5, 0x73, 0xd0, 0xbb, 0x50, 0xd3, 0x47, 0x81,
|
||||
0x33, 0x16, 0x47, 0x4e, 0x6b, 0xfb, 0xe2, 0xac, 0xc9, 0xbb, 0x7c, 0xd0, 0xfe, 0x92, 0x16, 0x8e,
|
||||
0xfe, 0xa8, 0x0e, 0x55, 0x8a, 0x3d, 0xa6, 0xde, 0x81, 0x33, 0x45, 0x22, 0xf8, 0x39, 0xa7, 0x8f,
|
||||
0x88, 0x3e, 0xf6, 0x03, 0x5b, 0x9a, 0x39, 0x6e, 0x23, 0x04, 0x55, 0xdf, 0x7c, 0x1e, 0x9a, 0xba,
|
||||
0xa2, 0x89, 0x6f, 0xf5, 0x2d, 0x58, 0x9f, 0x92, 0xc6, 0x37, 0x35, 0xd4, 0x8d, 0x73, 0x58, 0x91,
|
||||
0xa2, 0xd5, 0x00, 0xce, 0x3e, 0x16, 0xb6, 0x88, 0x93, 0xfd, 0x49, 0x9c, 0xdc, 0xea, 0x3e, 0x6c,
|
||||
0xe4, 0xc5, 0xfa, 0xd4, 0x75, 0x7c, 0xc2, 0x5d, 0x5f, 0x64, 0x47, 0x93, 0x0c, 0x92, 0x5e, 0xa1,
|
||||
0x45, 0x43, 0x2b, 0xe8, 0x51, 0x7f, 0x5b, 0x86, 0x0d, 0x8d, 0xf8, 0xae, 0x75, 0x44, 0xa2, 0xd4,
|
||||
0x75, 0x32, 0xe0, 0xe3, 0x87, 0x50, 0xc1, 0x94, 0x4a, 0x37, 0xb9, 0xfb, 0xca, 0x8e, 0x77, 0x8d,
|
||||
0x73, 0x45, 0x6f, 0xc3, 0x3a, 0xb6, 0xfb, 0xa6, 0x11, 0xb8, 0x81, 0x1f, 0x2d, 0x4b, 0x38, 0x55,
|
||||
0x53, 0x9b, 0xee, 0xe0, 0xe1, 0xef, 0x8b, 0x88, 0xbc, 0xeb, 0x0c, 0xc8, 0x4f, 0x05, 0xa2, 0xa9,
|
||||
0x68, 0x69, 0x92, 0xaa, 0xc3, 0xb9, 0x29, 0x23, 0x49, 0x83, 0xa7, 0x41, 0x54, 0x29, 0x07, 0xa2,
|
||||
0x0a, 0xd5, 0x28, 0xcf, 0x50, 0x43, 0x7d, 0x51, 0x82, 0x76, 0x12, 0x5c, 0x92, 0xfd, 0x05, 0x68,
|
||||
0xda, 0x92, 0xe6, 0x2b, 0x25, 0x91, 0xc1, 0x12, 0x42, 0x16, 0x4f, 0x95, 0xf3, 0x78, 0x6a, 0x03,
|
||||
0xea, 0x21, 0xdc, 0x95, 0x4b, 0x97, 0xad, 0x8c, 0xca, 0xd5, 0x9c, 0xca, 0x9b, 0x00, 0x7e, 0x9c,
|
||||
0xe1, 0x94, 0xba, 0xe8, 0x4d, 0x51, 0x90, 0x0a, 0x2b, 0xe1, 0xe9, 0xab, 0x11, 0x3f, 0xb0, 0x98,
|
||||
0xb2, 0x2c, 0x46, 0x64, 0x68, 0x22, 0xde, 0x5c, 0xdb, 0xc6, 0xce, 0xc0, 0x57, 0x1a, 0x42, 0xe5,
|
||||
0xb8, 0xad, 0xba, 0xb0, 0x76, 0xdf, 0xe4, 0xeb, 0x1b, 0xfa, 0x27, 0x13, 0x2a, 0xef, 0x41, 0x95,
|
||||
0x0b, 0xe3, 0x4a, 0xf5, 0x3d, 0xec, 0xe8, 0x23, 0x12, 0xd9, 0x31, 0x6e, 0xf3, 0x24, 0xc0, 0xb0,
|
||||
0xe1, 0x2b, 0x65, 0x41, 0x17, 0xdf, 0xea, 0xef, 0xcb, 0xa1, 0xa6, 0x3b, 0x94, 0xfa, 0x5f, 0x3d,
|
||||
0x1c, 0x2f, 0x06, 0x08, 0x95, 0x69, 0x80, 0x90, 0x53, 0xf9, 0xcb, 0x00, 0x84, 0x57, 0x74, 0xc8,
|
||||
0xa9, 0x01, 0x2c, 0xef, 0x50, 0xca, 0x15, 0x41, 0xd7, 0xa1, 0x8a, 0x29, 0x0d, 0x0d, 0x9e, 0xcb,
|
||||
0xe7, 0x72, 0x08, 0xff, 0x2f, 0x55, 0x12, 0x43, 0x3b, 0x37, 0xa1, 0x19, 0x93, 0x5e, 0x26, 0xb6,
|
||||
0x99, 0x16, 0xbb, 0x05, 0x10, 0x22, 0xe0, 0xbb, 0xce, 0xd0, 0xe5, 0x5b, 0xca, 0x03, 0x41, 0x4e,
|
||||
0x15, 0xdf, 0xea, 0xad, 0x68, 0x84, 0xd0, 0xed, 0x6d, 0xa8, 0x99, 0x8c, 0xd8, 0x91, 0x72, 0x1b,
|
||||
0x69, 0xe5, 0x12, 0x46, 0x5a, 0x38, 0x48, 0xfd, 0x73, 0x03, 0xce, 0xf3, 0x1d, 0x7b, 0x24, 0x42,
|
||||
0x68, 0x87, 0xd2, 0x8f, 0x09, 0xc3, 0xa6, 0xe5, 0x7f, 0x2f, 0x20, 0xde, 0xe4, 0x35, 0x3b, 0x86,
|
||||
0x01, 0xf5, 0x30, 0x02, 0x65, 0xb6, 0x7c, 0xe5, 0xc5, 0x90, 0x64, 0x9f, 0x54, 0x40, 0x95, 0xd7,
|
||||
0x53, 0x01, 0x15, 0x55, 0x24, 0xd5, 0x13, 0xaa, 0x48, 0x66, 0x17, 0xa5, 0xa9, 0x52, 0xb7, 0x9e,
|
||||
0x2d, 0x75, 0x0b, 0x80, 0xfe, 0xf2, 0x71, 0x81, 0x7e, 0xa3, 0x10, 0xe8, 0xdb, 0x85, 0x71, 0xdc,
|
||||
0x14, 0xe6, 0xfe, 0x4e, 0xda, 0x03, 0x67, 0xfa, 0xda, 0x22, 0x90, 0x1f, 0x5e, 0x2b, 0xe4, 0xff,
|
||||
0x34, 0x03, 0xe1, 0xc3, 0x22, 0xfa, 0xdd, 0xe3, 0xad, 0x69, 0x0e, 0x98, 0xff, 0xda, 0x41, 0xef,
|
||||
0x5f, 0x08, 0xc4, 0x45, 0xdd, 0xc4, 0x06, 0xf1, 0x61, 0xcf, 0xcf, 0x21, 0x7e, 0xec, 0xca, 0xa4,
|
||||
0xc5, 0xbf, 0xd1, 0x35, 0xa8, 0x72, 0x23, 0x4b, 0x48, 0x7c, 0x2e, 0x6d, 0x4f, 0xbe, 0x13, 0x3b,
|
||||
0x94, 0x3e, 0xa2, 0x44, 0xd7, 0xc4, 0x20, 0x74, 0x0b, 0x9a, 0xb1, 0xe3, 0xcb, 0xc8, 0xba, 0x90,
|
||||
0x9e, 0x11, 0xc7, 0x49, 0x34, 0x2d, 0x19, 0xce, 0xe7, 0x0e, 0x4c, 0x8f, 0xe8, 0x02, 0x30, 0xd6,
|
||||
0xa6, 0xe7, 0x7e, 0x1c, 0x75, 0xc6, 0x73, 0xe3, 0xe1, 0xe8, 0x3a, 0xd4, 0xc3, 0x5b, 0x07, 0x11,
|
||||
0x41, 0xad, 0xed, 0xf3, 0xd3, 0xc9, 0x34, 0x9a, 0x25, 0x07, 0xaa, 0x7f, 0x2a, 0xc1, 0x9b, 0x89,
|
||||
0x43, 0x44, 0xd1, 0x14, 0x61, 0xf6, 0xaf, 0xfe, 0xc4, 0xbd, 0x0c, 0xab, 0xa2, 0x48, 0x48, 0x2e,
|
||||
0x1f, 0xc2, 0x7b, 0xb0, 0x1c, 0x55, 0xfd, 0x5d, 0x09, 0x2e, 0x4d, 0xaf, 0x63, 0x77, 0x84, 0x3d,
|
||||
0x16, 0x6f, 0xef, 0x49, 0xac, 0x25, 0x3a, 0xf0, 0xca, 0xc9, 0x81, 0x97, 0x59, 0x5f, 0x25, 0xbb,
|
||||
0x3e, 0xf5, 0x0f, 0x65, 0x68, 0xa5, 0x1c, 0xa8, 0xe8, 0xc0, 0xe4, 0x60, 0x50, 0xf8, 0xad, 0x28,
|
||||
0x0b, 0xc5, 0xa1, 0xd0, 0xd4, 0x52, 0x14, 0x34, 0x06, 0xa0, 0xd8, 0xc3, 0x36, 0x61, 0xc4, 0xe3,
|
||||
0x99, 0x9c, 0x47, 0xfc, 0xbd, 0xc5, 0xb3, 0xcb, 0x61, 0xc4, 0x53, 0x4b, 0xb1, 0xe7, 0x68, 0x56,
|
||||
0x88, 0xf6, 0x65, 0xfe, 0x96, 0x2d, 0xf4, 0x05, 0xac, 0x0e, 0x4d, 0x8b, 0x1c, 0x26, 0x8a, 0xd4,
|
||||
0x85, 0x22, 0x0f, 0x17, 0x57, 0xe4, 0x4e, 0x9a, 0xaf, 0x96, 0x13, 0xa3, 0x5e, 0x85, 0x76, 0x3e,
|
||||
0x9e, 0xb8, 0x92, 0xa6, 0x8d, 0x8d, 0xd8, 0x5a, 0xb2, 0xa5, 0x22, 0x68, 0xe7, 0xe3, 0x47, 0xfd,
|
||||
0x67, 0x19, 0xce, 0xc6, 0xec, 0x76, 0x1c, 0xc7, 0x0d, 0x1c, 0x5d, 0x5c, 0xe4, 0x15, 0xee, 0xc5,
|
||||
0x19, 0xa8, 0x31, 0x93, 0x59, 0x31, 0xf0, 0x11, 0x0d, 0x7e, 0x76, 0x31, 0xd7, 0xb5, 0x98, 0x49,
|
||||
0xe5, 0x06, 0x47, 0xcd, 0x70, 0xef, 0x9f, 0x05, 0xa6, 0x47, 0x06, 0x22, 0x13, 0x34, 0xb4, 0xb8,
|
||||
0xcd, 0xfb, 0x38, 0xaa, 0x11, 0x10, 0x3f, 0x34, 0x66, 0xdc, 0x16, 0x7e, 0xef, 0x5a, 0x16, 0xd1,
|
||||
0xb9, 0x39, 0x52, 0x45, 0x40, 0x8e, 0x2a, 0x8a, 0x0b, 0xe6, 0x99, 0x8e, 0x21, 0x4b, 0x00, 0xd9,
|
||||
0xe2, 0x7a, 0x62, 0xcf, 0xc3, 0x13, 0x89, 0xfc, 0xc3, 0x06, 0xfa, 0x00, 0x2a, 0x36, 0xa6, 0xf2,
|
||||
0xa0, 0xbb, 0x9a, 0xc9, 0x0e, 0x45, 0x16, 0xe8, 0x1e, 0x60, 0x1a, 0x9e, 0x04, 0x7c, 0x5a, 0xe7,
|
||||
0x3d, 0x68, 0x44, 0x84, 0x2f, 0x05, 0x09, 0x3f, 0x87, 0x53, 0x99, 0xe4, 0x83, 0x9e, 0xc0, 0x46,
|
||||
0xe2, 0x51, 0x69, 0x81, 0x12, 0x04, 0xbe, 0xf9, 0x52, 0xcd, 0xb4, 0x19, 0x0c, 0xd4, 0x67, 0xb0,
|
||||
0xce, 0x5d, 0x46, 0x04, 0xfe, 0x09, 0x95, 0x36, 0xef, 0x43, 0x33, 0x16, 0x59, 0xe8, 0x33, 0x1d,
|
||||
0x68, 0x1c, 0x45, 0x17, 0xac, 0x61, 0x6d, 0x13, 0xb7, 0xd5, 0x1d, 0x40, 0x69, 0x7d, 0xe5, 0x09,
|
||||
0x74, 0x2d, 0x0b, 0x8a, 0xcf, 0xe6, 0x8f, 0x1b, 0x31, 0x3c, 0xc2, 0xc4, 0x7f, 0x2f, 0xc3, 0xda,
|
||||
0x9e, 0x29, 0xee, 0x48, 0x4e, 0x28, 0xc9, 0x5d, 0x85, 0xb6, 0x1f, 0xf4, 0x6d, 0x77, 0x10, 0x58,
|
||||
0x44, 0x82, 0x02, 0x79, 0xd2, 0x4f, 0xd1, 0xe7, 0x25, 0x3f, 0x6e, 0x2c, 0x8a, 0xd9, 0x48, 0x56,
|
||||
0xbf, 0xe2, 0x1b, 0x7d, 0x00, 0xe7, 0x1f, 0x90, 0x2f, 0xe4, 0x7a, 0xf6, 0x2c, 0xb7, 0xdf, 0x37,
|
||||
0x1d, 0x23, 0x12, 0x52, 0x13, 0x42, 0x66, 0x0f, 0x28, 0x82, 0x8a, 0xf5, 0x62, 0xa8, 0x18, 0x57,
|
||||
0xd0, 0xbb, 0xae, 0x6d, 0x9b, 0x4c, 0x22, 0xca, 0x0c, 0x4d, 0xfd, 0x79, 0x09, 0xda, 0x89, 0x65,
|
||||
0xe5, 0xde, 0xdc, 0x0c, 0x63, 0x28, 0xdc, 0x99, 0x4b, 0xe9, 0x9d, 0xc9, 0x0f, 0xfd, 0xef, 0xc3,
|
||||
0x67, 0x25, 0x1d, 0x3e, 0xbf, 0x2a, 0xc3, 0xd9, 0x3d, 0x93, 0x45, 0x89, 0xcb, 0xfc, 0x7f, 0xdb,
|
||||
0xe5, 0x82, 0x3d, 0xa9, 0x1e, 0x6f, 0x4f, 0x6a, 0x05, 0x7b, 0xd2, 0x85, 0x8d, 0xbc, 0x31, 0xe4,
|
||||
0xc6, 0x9c, 0x81, 0x1a, 0xf7, 0xa0, 0xe8, 0x5e, 0x21, 0x6c, 0xa8, 0xff, 0xa8, 0xc3, 0xc5, 0x4f,
|
||||
0xe9, 0x00, 0xb3, 0xf8, 0xce, 0xe8, 0x8e, 0xeb, 0x1d, 0xf2, 0xae, 0x93, 0xb1, 0x62, 0xee, 0x9d,
|
||||
0xae, 0x3c, 0xf7, 0x9d, 0xae, 0x32, 0xe7, 0x9d, 0xae, 0x7a, 0xac, 0x77, 0xba, 0xda, 0x89, 0xbd,
|
||||
0xd3, 0x4d, 0xd7, 0x5a, 0xf5, 0xc2, 0x5a, 0xeb, 0x49, 0xa6, 0x1e, 0x59, 0x16, 0x61, 0xf3, 0xed,
|
||||
0x74, 0xd8, 0xcc, 0xdd, 0x9d, 0xb9, 0x0f, 0x0c, 0xb9, 0xe7, 0xad, 0xc6, 0x4b, 0x9f, 0xb7, 0x9a,
|
||||
0xd3, 0xcf, 0x5b, 0xc5, 0x2f, 0x24, 0x30, 0xf3, 0x85, 0xe4, 0x32, 0xac, 0xfa, 0x13, 0x47, 0x27,
|
||||
0x83, 0xf8, 0x26, 0xb1, 0x15, 0x2e, 0x3b, 0x4b, 0xcd, 0x44, 0xc4, 0x4a, 0x2e, 0x22, 0x62, 0x4f,
|
||||
0x3d, 0x95, 0xf2, 0xd4, 0xa2, 0x38, 0x59, 0x2d, 0x8c, 0x93, 0xff, 0x9d, 0x22, 0xea, 0x33, 0xd8,
|
||||
0x9c, 0xb5, 0x7b, 0x32, 0x28, 0x15, 0x58, 0xd6, 0x47, 0xd8, 0x31, 0xc4, 0x75, 0x9f, 0xa8, 0xea,
|
||||
0x65, 0x73, 0x1e, 0xea, 0xdf, 0xfe, 0x23, 0xc0, 0x7a, 0x82, 0xe6, 0xf9, 0x5f, 0x53, 0x27, 0xe8,
|
||||
0x21, 0xb4, 0xf7, 0xe4, 0x53, 0x7e, 0x74, 0x41, 0x8b, 0xe6, 0xbd, 0x89, 0x74, 0x2e, 0x14, 0x77,
|
||||
0x86, 0xaa, 0xa9, 0x4b, 0x48, 0x87, 0xf3, 0x79, 0x86, 0xc9, 0xf3, 0xcb, 0x37, 0xe7, 0x70, 0x8e,
|
||||
0x47, 0xbd, 0x4c, 0xc4, 0x95, 0x12, 0x7a, 0x02, 0xab, 0xd9, 0x47, 0x02, 0x94, 0x81, 0x37, 0x85,
|
||||
0xef, 0x16, 0x1d, 0x75, 0xde, 0x90, 0x58, 0xff, 0xa7, 0xdc, 0x0d, 0x32, 0xf7, 0xe1, 0x48, 0xcd,
|
||||
0x56, 0xfa, 0x45, 0x2f, 0x0a, 0x9d, 0x6f, 0xcc, 0x1d, 0x13, 0x73, 0x7f, 0x1f, 0x1a, 0xd1, 0x1d,
|
||||
0x71, 0xd6, 0xcc, 0xb9, 0x9b, 0xe3, 0x4e, 0x3b, 0xcb, 0x6f, 0xe8, 0xab, 0x4b, 0xe8, 0xc3, 0x70,
|
||||
0xf2, 0x0e, 0xa5, 0x05, 0x93, 0x53, 0x37, 0xa3, 0x9d, 0xd3, 0x05, 0xb7, 0x91, 0xea, 0x12, 0xfa,
|
||||
0x2e, 0xb4, 0xf8, 0xd7, 0xa1, 0x7c, 0x44, 0xdf, 0xe8, 0x86, 0xbf, 0xd9, 0xe8, 0x46, 0xbf, 0xd9,
|
||||
0xe8, 0xde, 0xb6, 0x29, 0x9b, 0x74, 0x0a, 0xae, 0x0b, 0x25, 0x83, 0xa7, 0x70, 0x6a, 0x8f, 0xb0,
|
||||
0xa4, 0xba, 0x47, 0x97, 0x8e, 0x75, 0x07, 0xd2, 0x51, 0xf3, 0xc3, 0xa6, 0x2f, 0x08, 0xd4, 0x25,
|
||||
0xf4, 0xeb, 0x12, 0x9c, 0xde, 0x23, 0x2c, 0x5f, 0x2f, 0xa3, 0x77, 0x8a, 0x85, 0xcc, 0xa8, 0xab,
|
||||
0x3b, 0x0f, 0x16, 0x8d, 0xc9, 0x2c, 0x5b, 0x75, 0x09, 0xfd, 0xa6, 0x04, 0xe7, 0x52, 0x8a, 0xa5,
|
||||
0x0b, 0x60, 0x74, 0x7d, 0xbe, 0x72, 0x05, 0xc5, 0x72, 0xe7, 0x93, 0x05, 0x7f, 0x1b, 0x91, 0x62,
|
||||
0xa9, 0x2e, 0xa1, 0x43, 0xb1, 0x27, 0x09, 0xde, 0x45, 0x17, 0x0b, 0x81, 0x6d, 0x2c, 0x7d, 0x73,
|
||||
0x56, 0x77, 0xbc, 0x0f, 0x9f, 0x40, 0x6b, 0x8f, 0xb0, 0x08, 0x78, 0x65, 0x3d, 0x2d, 0x87, 0x89,
|
||||
0xb3, 0xa1, 0x9a, 0xc7, 0x6a, 0xc2, 0x63, 0xd6, 0x43, 0x5e, 0x29, 0x70, 0x91, 0x8d, 0xd5, 0x42,
|
||||
0x14, 0x96, 0xf5, 0x98, 0x62, 0x6c, 0xa2, 0x2e, 0xa1, 0x67, 0xb0, 0x51, 0x9c, 0x2a, 0xd1, 0x5b,
|
||||
0xc7, 0x3e, 0x0c, 0x3b, 0x57, 0x8f, 0x33, 0x34, 0x12, 0xf9, 0xd1, 0xce, 0x5f, 0x5e, 0x6c, 0x96,
|
||||
0xfe, 0xfa, 0x62, 0xb3, 0xf4, 0xaf, 0x17, 0x9b, 0xa5, 0x1f, 0xdc, 0x78, 0xc9, 0x6f, 0xa8, 0x52,
|
||||
0x3f, 0xcb, 0xc2, 0xd4, 0xd4, 0x2d, 0x93, 0x38, 0xac, 0x5f, 0x17, 0xf1, 0x76, 0xe3, 0x3f, 0x01,
|
||||
0x00, 0x00, 0xff, 0xff, 0xe6, 0x4d, 0x67, 0x16, 0xb5, 0x25, 0x00, 0x00,
|
||||
// 2353 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x1a, 0x4d, 0x73, 0x1c, 0x47,
|
||||
0x55, 0xfb, 0x25, 0xed, 0x3e, 0x59, 0x5f, 0x6d, 0x5b, 0x1e, 0xaf, 0x6d, 0x95, 0x32, 0x60, 0x97,
|
||||
0x63, 0x27, 0xab, 0xb2, 0x5c, 0x89, 0xc1, 0x09, 0xa1, 0x14, 0xd9, 0x96, 0x1c, 0x5b, 0xb6, 0x18,
|
||||
0x3b, 0xa1, 0x0c, 0x06, 0xaa, 0x77, 0xb6, 0xb5, 0xdb, 0xd1, 0x7c, 0xb4, 0x67, 0x7a, 0x14, 0xe4,
|
||||
0x2a, 0x2e, 0x40, 0x71, 0xe1, 0xc6, 0x81, 0x03, 0x57, 0x7e, 0x03, 0xc5, 0x91, 0x03, 0x45, 0xc1,
|
||||
0x91, 0xe2, 0xc2, 0x11, 0xca, 0xbf, 0x84, 0xea, 0x8f, 0xf9, 0xdc, 0xd9, 0xb5, 0xc2, 0xca, 0x0a,
|
||||
0xe4, 0x22, 0x4d, 0xbf, 0x7e, 0xfd, 0xde, 0xeb, 0xf7, 0xd5, 0xef, 0x75, 0x2f, 0x5c, 0x09, 0x08,
|
||||
0xf3, 0x43, 0x12, 0x1c, 0x90, 0x60, 0x4d, 0x7e, 0x52, 0xee, 0x07, 0x87, 0x99, 0xcf, 0x0e, 0x0b,
|
||||
0x7c, 0xee, 0x23, 0x48, 0x21, 0xed, 0x87, 0x7d, 0xca, 0x07, 0x51, 0xb7, 0x63, 0xfb, 0xee, 0x1a,
|
||||
0x0e, 0xfa, 0x3e, 0x0b, 0xfc, 0xcf, 0xe5, 0xc7, 0xbb, 0x76, 0x6f, 0xed, 0x60, 0x7d, 0x8d, 0xed,
|
||||
0xf7, 0xd7, 0x30, 0xa3, 0xe1, 0x1a, 0x66, 0xcc, 0xa1, 0x36, 0xe6, 0xd4, 0xf7, 0xd6, 0x0e, 0x6e,
|
||||
0x60, 0x87, 0x0d, 0xf0, 0x8d, 0xb5, 0x3e, 0xf1, 0x48, 0x80, 0x39, 0xe9, 0x29, 0xca, 0xed, 0x0b,
|
||||
0x7d, 0xdf, 0xef, 0x3b, 0x64, 0x4d, 0x8e, 0xba, 0xd1, 0xde, 0x1a, 0x71, 0x19, 0xd7, 0x6c, 0xcd,
|
||||
0xdf, 0xcc, 0xc1, 0xc2, 0x0e, 0xf6, 0xe8, 0x1e, 0x09, 0xb9, 0x45, 0x5e, 0x44, 0x24, 0xe4, 0xe8,
|
||||
0x39, 0xd4, 0x85, 0x30, 0x46, 0x65, 0xb5, 0x72, 0x75, 0x76, 0x7d, 0xbb, 0x93, 0x4a, 0xd3, 0x89,
|
||||
0xa5, 0x91, 0x1f, 0x3f, 0xb1, 0x7b, 0x9d, 0x83, 0xf5, 0x0e, 0xdb, 0xef, 0x77, 0x84, 0x34, 0x9d,
|
||||
0x8c, 0x34, 0x9d, 0x58, 0x9a, 0x8e, 0x95, 0x6c, 0xcb, 0x92, 0x54, 0x51, 0x1b, 0x9a, 0x01, 0x39,
|
||||
0xa0, 0x21, 0xf5, 0x3d, 0xa3, 0xba, 0x5a, 0xb9, 0xda, 0xb2, 0x92, 0x31, 0x32, 0x60, 0xc6, 0xf3,
|
||||
0x37, 0xb1, 0x3d, 0x20, 0x46, 0x6d, 0xb5, 0x72, 0xb5, 0x69, 0xc5, 0x43, 0xb4, 0x0a, 0xb3, 0x98,
|
||||
0xb1, 0x87, 0xb8, 0x4b, 0x9c, 0x07, 0xe4, 0xd0, 0xa8, 0xcb, 0x85, 0x59, 0x90, 0x58, 0x8b, 0x19,
|
||||
0x7b, 0x84, 0x5d, 0x62, 0x34, 0xe4, 0x6c, 0x3c, 0x44, 0x17, 0xa1, 0xe5, 0x61, 0x97, 0x84, 0x0c,
|
||||
0xdb, 0xc4, 0x68, 0xca, 0xb9, 0x14, 0x80, 0x7e, 0x06, 0x4b, 0x19, 0xc1, 0x9f, 0xf8, 0x51, 0x60,
|
||||
0x13, 0x03, 0xe4, 0xd6, 0x1f, 0x4f, 0xb6, 0xf5, 0x8d, 0x22, 0x59, 0x6b, 0x98, 0x13, 0xfa, 0x31,
|
||||
0x34, 0xa4, 0xe5, 0x8d, 0xd9, 0xd5, 0xda, 0xb1, 0x6a, 0x5b, 0x91, 0x45, 0x1e, 0xcc, 0x30, 0x27,
|
||||
0xea, 0x53, 0x2f, 0x34, 0x4e, 0x49, 0x0e, 0x4f, 0x27, 0xe3, 0xb0, 0xe9, 0x7b, 0x7b, 0xb4, 0xbf,
|
||||
0x83, 0x3d, 0xdc, 0x27, 0x2e, 0xf1, 0xf8, 0xae, 0x24, 0x6e, 0xc5, 0x4c, 0xd0, 0x4b, 0x58, 0xdc,
|
||||
0x8f, 0x42, 0xee, 0xbb, 0xf4, 0x25, 0x79, 0xcc, 0xc4, 0xda, 0xd0, 0x98, 0x93, 0xda, 0x7c, 0x34,
|
||||
0x19, 0xe3, 0x07, 0x05, 0xaa, 0xd6, 0x10, 0x1f, 0xe1, 0x24, 0xfb, 0x51, 0x97, 0x7c, 0x46, 0x02,
|
||||
0xe9, 0x5d, 0xf3, 0xca, 0x49, 0x32, 0x20, 0xe5, 0x46, 0x54, 0x8f, 0x42, 0x63, 0x61, 0xb5, 0xa6,
|
||||
0xdc, 0x28, 0x01, 0xa1, 0xab, 0xb0, 0x70, 0x40, 0x02, 0xba, 0x77, 0xf8, 0x84, 0xf6, 0x3d, 0xcc,
|
||||
0xa3, 0x80, 0x18, 0x8b, 0xd2, 0x15, 0x8b, 0x60, 0xe4, 0xc2, 0xdc, 0x80, 0x38, 0xae, 0x50, 0xf9,
|
||||
0x66, 0x40, 0x7a, 0xa1, 0xb1, 0x24, 0xf5, 0xbb, 0x35, 0xb9, 0x05, 0x25, 0x39, 0x2b, 0x4f, 0x5d,
|
||||
0x08, 0xe6, 0xf9, 0x96, 0x8e, 0x14, 0x15, 0x23, 0x48, 0x09, 0x56, 0x00, 0xa3, 0x2b, 0x30, 0xcf,
|
||||
0x03, 0x6c, 0xef, 0x53, 0xaf, 0xbf, 0x43, 0xf8, 0xc0, 0xef, 0x19, 0xa7, 0xa5, 0x26, 0x0a, 0x50,
|
||||
0x64, 0x03, 0x22, 0x1e, 0xee, 0x3a, 0xa4, 0xa7, 0x7c, 0xf1, 0xe9, 0x21, 0x23, 0xa1, 0x71, 0x46,
|
||||
0xee, 0xe2, 0x66, 0x27, 0x93, 0xa1, 0x0a, 0x09, 0xa2, 0x73, 0x77, 0x68, 0xd5, 0x5d, 0x8f, 0x07,
|
||||
0x87, 0x56, 0x09, 0x39, 0xb4, 0x0f, 0xb3, 0x62, 0x1f, 0xb1, 0x2b, 0x9c, 0x95, 0xae, 0x70, 0x7f,
|
||||
0x32, 0x1d, 0x6d, 0xa7, 0x04, 0xad, 0x2c, 0x75, 0xd4, 0x01, 0x34, 0xc0, 0xe1, 0x4e, 0xe4, 0x70,
|
||||
0xca, 0x1c, 0xa2, 0xc4, 0x08, 0x8d, 0x65, 0xa9, 0xa6, 0x92, 0x19, 0xf4, 0x00, 0x20, 0x20, 0x7b,
|
||||
0x31, 0xde, 0x39, 0xb9, 0xf3, 0xeb, 0xe3, 0x76, 0x6e, 0x25, 0xd8, 0x6a, 0xc7, 0x99, 0xe5, 0x82,
|
||||
0xb9, 0xd8, 0x06, 0xb1, 0xb9, 0x8e, 0x76, 0x19, 0xd6, 0x86, 0x74, 0xb1, 0x92, 0x19, 0xe1, 0x8b,
|
||||
0x1a, 0x2a, 0x93, 0xd6, 0x79, 0xe5, 0xad, 0x19, 0x90, 0x30, 0x24, 0xf5, 0x42, 0x8e, 0x1d, 0x47,
|
||||
0x2a, 0xe0, 0xfe, 0x1d, 0xe3, 0x82, 0x32, 0x64, 0x1e, 0xda, 0xbe, 0x0b, 0xe7, 0x46, 0x98, 0x04,
|
||||
0x2d, 0x42, 0x6d, 0x9f, 0x1c, 0xca, 0x54, 0xde, 0xb2, 0xc4, 0x27, 0x3a, 0x03, 0x8d, 0x03, 0xec,
|
||||
0x44, 0x44, 0x26, 0xdf, 0xa6, 0xa5, 0x06, 0xb7, 0xab, 0xdf, 0xaa, 0xb4, 0x7f, 0x55, 0x81, 0x85,
|
||||
0xc2, 0x06, 0x4b, 0xd6, 0xff, 0x28, 0xbb, 0xfe, 0x18, 0xdc, 0x7d, 0xef, 0x29, 0x0e, 0xfa, 0x84,
|
||||
0x67, 0x04, 0x31, 0xff, 0x51, 0x01, 0xa3, 0xa0, 0xf9, 0xef, 0x53, 0x3e, 0xb8, 0x47, 0x1d, 0x12,
|
||||
0xa2, 0x5b, 0x30, 0x13, 0x28, 0x98, 0x3e, 0xa0, 0x2e, 0x8c, 0x31, 0xd8, 0xf6, 0x94, 0x15, 0x63,
|
||||
0xa3, 0x8f, 0xa0, 0xe9, 0x12, 0x8e, 0x7b, 0x98, 0x63, 0x2d, 0xfb, 0x6a, 0xd9, 0x4a, 0xc1, 0x65,
|
||||
0x47, 0xe3, 0x6d, 0x4f, 0x59, 0xc9, 0x1a, 0xf4, 0x1e, 0x34, 0xec, 0x41, 0xe4, 0xed, 0xcb, 0xa3,
|
||||
0x69, 0x76, 0xfd, 0xd2, 0xa8, 0xc5, 0x9b, 0x02, 0x69, 0x7b, 0xca, 0x52, 0xd8, 0x1f, 0x4f, 0x43,
|
||||
0x9d, 0xe1, 0x80, 0x9b, 0xf7, 0xe0, 0x4c, 0x19, 0x0b, 0x71, 0x1e, 0xda, 0x03, 0x62, 0xef, 0x87,
|
||||
0x91, 0xab, 0xd5, 0x9c, 0x8c, 0x11, 0x82, 0x7a, 0x48, 0x5f, 0x2a, 0x55, 0xd7, 0x2c, 0xf9, 0x6d,
|
||||
0xbe, 0x0d, 0x4b, 0x43, 0xdc, 0x84, 0x51, 0x95, 0x6c, 0x82, 0xc2, 0x29, 0xcd, 0xda, 0x8c, 0xe0,
|
||||
0xec, 0x53, 0xa9, 0x8b, 0xe4, 0x50, 0x38, 0x89, 0x13, 0xde, 0xdc, 0x86, 0xe5, 0x22, 0xdb, 0x90,
|
||||
0xf9, 0x5e, 0x48, 0x44, 0x88, 0xc8, 0x2c, 0x4a, 0x49, 0x2f, 0x9d, 0x95, 0x52, 0x34, 0xad, 0x92,
|
||||
0x19, 0xf3, 0xf7, 0x55, 0x58, 0xb6, 0x48, 0xe8, 0x3b, 0x07, 0x24, 0x4e, 0x71, 0x27, 0x53, 0xa4,
|
||||
0xfc, 0x10, 0x6a, 0x98, 0x31, 0xed, 0x26, 0xf7, 0x8f, 0xad, 0x0c, 0xb0, 0x04, 0x55, 0xf4, 0x0e,
|
||||
0x2c, 0x61, 0xb7, 0x4b, 0xfb, 0x91, 0x1f, 0x85, 0xf1, 0xb6, 0xa4, 0x53, 0xb5, 0xac, 0xe1, 0x09,
|
||||
0x91, 0x26, 0x42, 0x19, 0x91, 0xf7, 0xbd, 0x1e, 0xf9, 0xa9, 0xac, 0x7c, 0x6a, 0x56, 0x16, 0x64,
|
||||
0xda, 0x70, 0x6e, 0x48, 0x49, 0x5a, 0xe1, 0xd9, 0x62, 0xab, 0x52, 0x28, 0xb6, 0x4a, 0xc5, 0xa8,
|
||||
0x8e, 0x10, 0xc3, 0x7c, 0x55, 0x81, 0xc5, 0x34, 0xb8, 0x34, 0xf9, 0x8b, 0xd0, 0x72, 0x35, 0x2c,
|
||||
0x34, 0x2a, 0x32, 0xd3, 0xa5, 0x80, 0x7c, 0xdd, 0x55, 0x2d, 0xd6, 0x5d, 0xcb, 0x30, 0xad, 0xca,
|
||||
0x62, 0xbd, 0x75, 0x3d, 0xca, 0x89, 0x5c, 0x2f, 0x88, 0xbc, 0x02, 0x10, 0x26, 0x19, 0xce, 0x98,
|
||||
0x96, 0xb3, 0x19, 0x08, 0x32, 0xe1, 0x94, 0x3a, 0xa5, 0x2d, 0x12, 0x46, 0x0e, 0x37, 0x66, 0x24,
|
||||
0x46, 0x0e, 0x26, 0xe3, 0xcd, 0x77, 0x5d, 0xec, 0xf5, 0x42, 0xa3, 0x29, 0x45, 0x4e, 0xc6, 0xa6,
|
||||
0x0f, 0x0b, 0x0f, 0xa9, 0xd8, 0xdf, 0x5e, 0x78, 0x32, 0xa1, 0xf2, 0x3e, 0xd4, 0x05, 0x33, 0x21,
|
||||
0x54, 0x37, 0xc0, 0x9e, 0x3d, 0x20, 0xb1, 0x1e, 0x93, 0xb1, 0x48, 0x02, 0x1c, 0xf7, 0x43, 0xa3,
|
||||
0x2a, 0xe1, 0xf2, 0xdb, 0xfc, 0x63, 0x55, 0x49, 0xba, 0xc1, 0x58, 0xf8, 0xd5, 0x97, 0xed, 0xe5,
|
||||
0x85, 0x44, 0x6d, 0xb8, 0x90, 0x28, 0x88, 0xfc, 0x65, 0x0a, 0x89, 0x63, 0x3a, 0xe4, 0xcc, 0x08,
|
||||
0x66, 0x36, 0x18, 0x13, 0x82, 0xa0, 0x1b, 0x50, 0xc7, 0x8c, 0x29, 0x85, 0x17, 0xf2, 0xb9, 0x46,
|
||||
0x11, 0xff, 0xb5, 0x48, 0x12, 0xb5, 0x7d, 0x0b, 0x5a, 0x09, 0xe8, 0x75, 0x6c, 0x5b, 0x59, 0xb6,
|
||||
0xab, 0x00, 0xaa, 0x52, 0xbe, 0xef, 0xed, 0xf9, 0xc2, 0xa4, 0x22, 0x10, 0xf4, 0x52, 0xf9, 0x6d,
|
||||
0xde, 0x8e, 0x31, 0xa4, 0x6c, 0xef, 0x40, 0x83, 0x72, 0xe2, 0xc6, 0xc2, 0x2d, 0x67, 0x85, 0x4b,
|
||||
0x09, 0x59, 0x0a, 0xc9, 0xfc, 0x6b, 0x13, 0xce, 0x0b, 0x8b, 0x3d, 0x91, 0x21, 0xb4, 0xc1, 0xd8,
|
||||
0x1d, 0xc2, 0x31, 0x75, 0xc2, 0xef, 0x45, 0x24, 0x38, 0x7c, 0xc3, 0x8e, 0xd1, 0x87, 0x69, 0x15,
|
||||
0x81, 0x3a, 0x5b, 0x1e, 0x7b, 0xd3, 0xa4, 0xc9, 0xa7, 0x9d, 0x52, 0xed, 0xcd, 0x74, 0x4a, 0x65,
|
||||
0x9d, 0x4b, 0xfd, 0x84, 0x3a, 0x97, 0xd1, 0xcd, 0x6b, 0xa6, 0x25, 0x9e, 0xce, 0xb7, 0xc4, 0x25,
|
||||
0x0d, 0xc1, 0xcc, 0x51, 0x1b, 0x82, 0x66, 0x69, 0x43, 0xe0, 0x96, 0xc6, 0x71, 0x4b, 0xaa, 0xfb,
|
||||
0x3b, 0x59, 0x0f, 0x1c, 0xe9, 0x6b, 0x93, 0xb4, 0x06, 0xf0, 0x46, 0x5b, 0x83, 0x4f, 0x73, 0xa5,
|
||||
0xbe, 0x6a, 0xb6, 0xdf, 0x3b, 0xda, 0x9e, 0xc6, 0x14, 0xfd, 0x5f, 0xbb, 0xd2, 0xfb, 0x97, 0xb2,
|
||||
0xe2, 0x62, 0x7e, 0xaa, 0x83, 0xe4, 0xb0, 0x17, 0xe7, 0x90, 0x38, 0x76, 0x75, 0xd2, 0x12, 0xdf,
|
||||
0xe8, 0x3a, 0xd4, 0x85, 0x92, 0x75, 0x49, 0x7c, 0x2e, 0xab, 0x4f, 0x61, 0x89, 0x0d, 0xc6, 0x9e,
|
||||
0x30, 0x62, 0x5b, 0x12, 0x09, 0xdd, 0x86, 0x56, 0xe2, 0xf8, 0x3a, 0xb2, 0x2e, 0x66, 0x57, 0x24,
|
||||
0x71, 0x12, 0x2f, 0x4b, 0xd1, 0xc5, 0xda, 0x1e, 0x0d, 0x88, 0x2d, 0x0b, 0xc6, 0xc6, 0xf0, 0xda,
|
||||
0x3b, 0xf1, 0x64, 0xb2, 0x36, 0x41, 0x47, 0x37, 0x60, 0x5a, 0xdd, 0x4e, 0xc8, 0x08, 0x9a, 0x5d,
|
||||
0x3f, 0x3f, 0x9c, 0x4c, 0xe3, 0x55, 0x1a, 0xd1, 0xfc, 0x4b, 0x05, 0xde, 0x4a, 0x1d, 0x22, 0x8e,
|
||||
0xa6, 0xb8, 0x66, 0xff, 0xea, 0x4f, 0xdc, 0x2b, 0x30, 0x2f, 0x9b, 0x84, 0xf4, 0x92, 0x42, 0xdd,
|
||||
0x97, 0x15, 0xa0, 0xe6, 0x1f, 0x2a, 0x70, 0x79, 0x78, 0x1f, 0x9b, 0x03, 0x1c, 0xf0, 0xc4, 0xbc,
|
||||
0x27, 0xb1, 0x97, 0xf8, 0xc0, 0xab, 0xa6, 0x07, 0x5e, 0x6e, 0x7f, 0xb5, 0xfc, 0xfe, 0xcc, 0x3f,
|
||||
0x55, 0x61, 0x36, 0xe3, 0x40, 0x65, 0x07, 0xa6, 0x28, 0x06, 0xa5, 0xdf, 0xca, 0xb6, 0x50, 0x1e,
|
||||
0x0a, 0x2d, 0x2b, 0x03, 0x41, 0xfb, 0x00, 0x0c, 0x07, 0xd8, 0x25, 0x9c, 0x04, 0x22, 0x93, 0x8b,
|
||||
0x88, 0x7f, 0x30, 0x79, 0x76, 0xd9, 0x8d, 0x69, 0x5a, 0x19, 0xf2, 0xa2, 0x9a, 0x95, 0xac, 0x43,
|
||||
0x9d, 0xbf, 0xf5, 0x08, 0x7d, 0x01, 0xf3, 0x7b, 0xd4, 0x21, 0xbb, 0xa9, 0x20, 0xd3, 0x52, 0x90,
|
||||
0xc7, 0x93, 0x0b, 0x72, 0x2f, 0x4b, 0xd7, 0x2a, 0xb0, 0x31, 0xaf, 0xc1, 0x62, 0x31, 0x9e, 0x84,
|
||||
0x90, 0xd4, 0xc5, 0xfd, 0x44, 0x5b, 0x7a, 0x64, 0x22, 0x58, 0x2c, 0xc6, 0x8f, 0xf9, 0xaf, 0x2a,
|
||||
0x9c, 0x4d, 0xc8, 0x6d, 0x78, 0x9e, 0x1f, 0x79, 0xb6, 0xbc, 0xf0, 0x2b, 0xb5, 0xc5, 0x19, 0x68,
|
||||
0x70, 0xca, 0x9d, 0xa4, 0xf0, 0x91, 0x03, 0x71, 0x76, 0x71, 0xdf, 0x77, 0x38, 0x65, 0xda, 0xc0,
|
||||
0xf1, 0x50, 0xd9, 0xfe, 0x45, 0x44, 0x03, 0xd2, 0x93, 0x99, 0xa0, 0x69, 0x25, 0x63, 0x31, 0x27,
|
||||
0xaa, 0x1a, 0x59, 0xe2, 0x2b, 0x65, 0x26, 0x63, 0xe9, 0xf7, 0xbe, 0xe3, 0x10, 0x5b, 0xa8, 0x23,
|
||||
0xd3, 0x04, 0x14, 0xa0, 0xb2, 0xb9, 0xe0, 0x01, 0xf5, 0xfa, 0xba, 0x05, 0xd0, 0x23, 0x21, 0x27,
|
||||
0x0e, 0x02, 0x7c, 0xa8, 0x2b, 0x7f, 0x35, 0x40, 0x1f, 0x42, 0xcd, 0xc5, 0x4c, 0x1f, 0x74, 0xd7,
|
||||
0x72, 0xd9, 0xa1, 0x4c, 0x03, 0x9d, 0x1d, 0xcc, 0xd4, 0x49, 0x20, 0x96, 0xb5, 0xdf, 0x87, 0x66,
|
||||
0x0c, 0xf8, 0x52, 0x25, 0xe1, 0xe7, 0x30, 0x97, 0x4b, 0x3e, 0xe8, 0x19, 0x2c, 0xa7, 0x1e, 0x95,
|
||||
0x65, 0xa8, 0x8b, 0xc0, 0xb7, 0x5e, 0x2b, 0x99, 0x35, 0x82, 0x80, 0xf9, 0x02, 0x96, 0x84, 0xcb,
|
||||
0xc8, 0xc0, 0x3f, 0xa1, 0xd6, 0xe6, 0x03, 0x68, 0x25, 0x2c, 0x4b, 0x7d, 0xa6, 0x0d, 0xcd, 0x83,
|
||||
0xf8, 0x22, 0x56, 0xf5, 0x36, 0xc9, 0xd8, 0xdc, 0x00, 0x94, 0x95, 0x57, 0x9f, 0x40, 0xd7, 0xf3,
|
||||
0x45, 0xf1, 0xd9, 0xe2, 0x71, 0x23, 0xd1, 0xe3, 0x9a, 0xf8, 0x9f, 0x55, 0x58, 0xd8, 0xa2, 0xf2,
|
||||
0x8e, 0xe4, 0x84, 0x92, 0xdc, 0x35, 0x58, 0x0c, 0xa3, 0xae, 0xeb, 0xf7, 0x22, 0x87, 0xe8, 0xa2,
|
||||
0x40, 0x9f, 0xf4, 0x43, 0xf0, 0x71, 0xc9, 0x4f, 0x28, 0x8b, 0x61, 0x3e, 0xd0, 0xdd, 0xaf, 0xfc,
|
||||
0x46, 0x1f, 0xc2, 0xf9, 0x47, 0xe4, 0x0b, 0xbd, 0x9f, 0x2d, 0xc7, 0xef, 0x76, 0xa9, 0xd7, 0x8f,
|
||||
0x99, 0x34, 0x24, 0x93, 0xd1, 0x08, 0x65, 0xa5, 0xe2, 0x74, 0x79, 0xa9, 0x98, 0x74, 0xd0, 0x9b,
|
||||
0xbe, 0xeb, 0x52, 0xae, 0x2b, 0xca, 0x1c, 0xcc, 0xfc, 0x45, 0x05, 0x16, 0x53, 0xcd, 0x6a, 0xdb,
|
||||
0xdc, 0x52, 0x31, 0xa4, 0x2c, 0x73, 0x39, 0x6b, 0x99, 0x22, 0xea, 0x7f, 0x1f, 0x3e, 0xa7, 0xb2,
|
||||
0xe1, 0xf3, 0xeb, 0x2a, 0x9c, 0xdd, 0xa2, 0x3c, 0x4e, 0x5c, 0xf4, 0xff, 0xcd, 0xca, 0x25, 0x36,
|
||||
0xa9, 0x1f, 0xcd, 0x26, 0x8d, 0x12, 0x9b, 0x74, 0x60, 0xb9, 0xa8, 0x0c, 0x6d, 0x98, 0x33, 0xd0,
|
||||
0x10, 0x1e, 0x14, 0xdf, 0x2b, 0xa8, 0x81, 0xf9, 0xf3, 0x19, 0xb8, 0xf4, 0x29, 0xeb, 0x61, 0x9e,
|
||||
0xdc, 0x19, 0xdd, 0xf3, 0x83, 0x5d, 0x31, 0x75, 0x32, 0x5a, 0x2c, 0xbc, 0xe7, 0x55, 0xc7, 0xbe,
|
||||
0xe7, 0xd5, 0xc6, 0xbc, 0xe7, 0xd5, 0x8f, 0xf4, 0x9e, 0xd7, 0x38, 0xb1, 0xf7, 0xbc, 0xe1, 0x5e,
|
||||
0x6b, 0xba, 0xb4, 0xd7, 0x7a, 0x96, 0xeb, 0x47, 0x66, 0x64, 0xd8, 0x7c, 0x3b, 0x1b, 0x36, 0x63,
|
||||
0xad, 0x33, 0xf6, 0x21, 0xa2, 0xf0, 0x0c, 0xd6, 0x7c, 0xed, 0x33, 0x58, 0x6b, 0xf8, 0x19, 0xac,
|
||||
0xfc, 0x25, 0x05, 0x46, 0xbe, 0xa4, 0x5c, 0x81, 0xf9, 0xf0, 0xd0, 0xb3, 0x49, 0x2f, 0xb9, 0x49,
|
||||
0x9c, 0x55, 0xdb, 0xce, 0x43, 0x73, 0x11, 0x71, 0xaa, 0x10, 0x11, 0x89, 0xa7, 0xce, 0x65, 0x3c,
|
||||
0xb5, 0x2c, 0x4e, 0xe6, 0x47, 0xb6, 0xb9, 0x85, 0xe7, 0x92, 0x85, 0xd2, 0xe7, 0x92, 0xff, 0x99,
|
||||
0x66, 0xeb, 0x33, 0x58, 0x19, 0x65, 0x65, 0x1d, 0xbc, 0x06, 0xcc, 0xd8, 0x03, 0xec, 0xf5, 0xe5,
|
||||
0xb5, 0xa0, 0xec, 0xfe, 0xf5, 0x70, 0x5c, 0x77, 0xb0, 0xfe, 0x67, 0x80, 0xa5, 0xb4, 0xea, 0x17,
|
||||
0x7f, 0xa9, 0x4d, 0xd0, 0x63, 0x58, 0xdc, 0xd2, 0x3f, 0x0d, 0x88, 0x2f, 0x72, 0xd1, 0xb8, 0xb7,
|
||||
0x93, 0xf6, 0xc5, 0xf2, 0x49, 0x25, 0x9a, 0x39, 0x85, 0x6c, 0x38, 0x5f, 0x24, 0x98, 0x3e, 0xd3,
|
||||
0x7c, 0x73, 0x0c, 0xe5, 0x04, 0xeb, 0x75, 0x2c, 0xae, 0x56, 0xd0, 0x33, 0x98, 0xcf, 0x3f, 0x26,
|
||||
0xa0, 0x5c, 0x19, 0x54, 0xfa, 0xbe, 0xd1, 0x36, 0xc7, 0xa1, 0x24, 0xf2, 0x3f, 0x17, 0x6e, 0x90,
|
||||
0xbb, 0x37, 0x47, 0x66, 0xfe, 0x46, 0xa0, 0xec, 0xe5, 0xa1, 0xfd, 0x8d, 0xb1, 0x38, 0x09, 0xf5,
|
||||
0x0f, 0xa0, 0x19, 0xdf, 0x25, 0xe7, 0xd5, 0x5c, 0xb8, 0x61, 0x6e, 0x2f, 0xe6, 0xe9, 0xed, 0x85,
|
||||
0xe6, 0x14, 0xfa, 0x48, 0x2d, 0xde, 0x60, 0xac, 0x64, 0x71, 0xe6, 0x06, 0xb5, 0x7d, 0xba, 0xe4,
|
||||
0xd6, 0xd2, 0x9c, 0x42, 0xdf, 0x85, 0x59, 0xf1, 0xb5, 0xab, 0x1f, 0xe5, 0x97, 0x3b, 0xea, 0x37,
|
||||
0x20, 0x9d, 0xf8, 0x37, 0x20, 0x9d, 0xbb, 0x2e, 0xe3, 0x87, 0xed, 0x92, 0x6b, 0x45, 0x4d, 0xe0,
|
||||
0x39, 0xcc, 0x6d, 0x11, 0x9e, 0xde, 0x02, 0xa0, 0xcb, 0x47, 0xba, 0x2b, 0x69, 0x9b, 0x45, 0xb4,
|
||||
0xe1, 0x8b, 0x04, 0x73, 0x0a, 0xfd, 0xb6, 0x02, 0xa7, 0xb7, 0x08, 0x2f, 0xf6, 0xd5, 0xe8, 0xdd,
|
||||
0x72, 0x26, 0x23, 0xfa, 0xef, 0xf6, 0xa3, 0x49, 0x63, 0x32, 0x4f, 0xd6, 0x9c, 0x42, 0xbf, 0xab,
|
||||
0xc0, 0xb9, 0x8c, 0x60, 0xd9, 0x46, 0x19, 0xdd, 0x18, 0x2f, 0x5c, 0x49, 0x53, 0xdd, 0xfe, 0x64,
|
||||
0xc2, 0xdf, 0x5a, 0x64, 0x48, 0x9a, 0x53, 0x68, 0x57, 0xda, 0x24, 0xad, 0x8b, 0xd1, 0xa5, 0xd2,
|
||||
0x02, 0x38, 0xe1, 0xbe, 0x32, 0x6a, 0x3a, 0xb1, 0xc3, 0x27, 0x30, 0xbb, 0x45, 0x78, 0x5c, 0xa0,
|
||||
0xe5, 0x3d, 0xad, 0x50, 0x3b, 0xe7, 0x43, 0xb5, 0x58, 0xd3, 0x49, 0x8f, 0x59, 0x52, 0xb4, 0x32,
|
||||
0x45, 0x48, 0x3e, 0x56, 0x4b, 0xab, 0xb5, 0xbc, 0xc7, 0x94, 0xd7, 0x30, 0xe6, 0x14, 0x7a, 0x01,
|
||||
0xcb, 0xe5, 0xa9, 0x12, 0xbd, 0x7d, 0xe4, 0x43, 0xb3, 0x7d, 0xed, 0x28, 0xa8, 0x31, 0xcb, 0x8f,
|
||||
0x37, 0xfe, 0xf6, 0x6a, 0xa5, 0xf2, 0xf7, 0x57, 0x2b, 0x95, 0x7f, 0xbf, 0x5a, 0xa9, 0xfc, 0xe0,
|
||||
0xe6, 0x6b, 0x7e, 0x93, 0x95, 0xf9, 0x99, 0x17, 0x66, 0xd4, 0x76, 0x28, 0xf1, 0x78, 0x77, 0x5a,
|
||||
0xc6, 0xdb, 0xcd, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0xdb, 0x7a, 0xf3, 0x7f, 0x05, 0x26, 0x00,
|
||||
0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
@@ -3233,6 +3252,15 @@ func (m *ManifestRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i -= len(m.XXX_unrecognized)
|
||||
copy(dAtA[i:], m.XXX_unrecognized)
|
||||
}
|
||||
if len(m.InstallationID) > 0 {
|
||||
i -= len(m.InstallationID)
|
||||
copy(dAtA[i:], m.InstallationID)
|
||||
i = encodeVarintRepository(dAtA, i, uint64(len(m.InstallationID)))
|
||||
i--
|
||||
dAtA[i] = 0x1
|
||||
i--
|
||||
dAtA[i] = 0xda
|
||||
}
|
||||
if len(m.ProjectName) > 0 {
|
||||
i -= len(m.ProjectName)
|
||||
copy(dAtA[i:], m.ProjectName)
|
||||
@@ -5257,6 +5285,13 @@ func (m *UpdateRevisionForPathsRequest) MarshalToSizedBuffer(dAtA []byte) (int,
|
||||
i -= len(m.XXX_unrecognized)
|
||||
copy(dAtA[i:], m.XXX_unrecognized)
|
||||
}
|
||||
if len(m.InstallationID) > 0 {
|
||||
i -= len(m.InstallationID)
|
||||
copy(dAtA[i:], m.InstallationID)
|
||||
i = encodeVarintRepository(dAtA, i, uint64(len(m.InstallationID)))
|
||||
i--
|
||||
dAtA[i] = 0x7a
|
||||
}
|
||||
if m.NoRevisionCache {
|
||||
i--
|
||||
if m.NoRevisionCache {
|
||||
@@ -5565,6 +5600,10 @@ func (m *ManifestRequest) Size() (n int) {
|
||||
if l > 0 {
|
||||
n += 2 + l + sovRepository(uint64(l))
|
||||
}
|
||||
l = len(m.InstallationID)
|
||||
if l > 0 {
|
||||
n += 2 + l + sovRepository(uint64(l))
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
}
|
||||
@@ -6424,6 +6463,10 @@ func (m *UpdateRevisionForPathsRequest) Size() (n int) {
|
||||
if m.NoRevisionCache {
|
||||
n += 2
|
||||
}
|
||||
l = len(m.InstallationID)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovRepository(uint64(l))
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
}
|
||||
@@ -7342,6 +7385,38 @@ func (m *ManifestRequest) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
m.ProjectName = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
case 27:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field InstallationID", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowRepository
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthRepository
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthRepository
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.InstallationID = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipRepository(dAtA[iNdEx:])
|
||||
@@ -12678,6 +12753,38 @@ func (m *UpdateRevisionForPathsRequest) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
}
|
||||
m.NoRevisionCache = bool(v != 0)
|
||||
case 15:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field InstallationID", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowRepository
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthRepository
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthRepository
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.InstallationID = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipRepository(dAtA[iNdEx:])
|
||||
|
||||
28
reposerver/cache/cache.go
vendored
28
reposerver/cache/cache.go
vendored
@@ -290,13 +290,17 @@ func (c *Cache) UnlockGitReferences(repo string, lockId string) error {
|
||||
|
||||
// refSourceCommitSHAs is a list of resolved revisions for each ref source. This allows us to invalidate the cache
|
||||
// when someone pushes a commit to a source which is referenced from the main source (the one referred to by `revision`).
|
||||
func manifestCacheKey(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, namespace string, trackingMethod string, appLabelKey string, appName string, info ClusterRuntimeInfo, refSourceCommitSHAs ResolvedRevisions) string {
|
||||
func manifestCacheKey(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, namespace string, trackingMethod string, appLabelKey string, appName string, info ClusterRuntimeInfo, refSourceCommitSHAs ResolvedRevisions, installationID string) string {
|
||||
// TODO: this function is getting unwieldy. We should probably consolidate some of this stuff into a struct. For
|
||||
// example, revision could be part of ResolvedRevisions. And srcRefs is probably redundant now that
|
||||
// refSourceCommitSHAs has been added. We don't need to know the _target_ revisions of the referenced sources
|
||||
// when the _resolved_ revisions are already part of the key.
|
||||
trackingKey := trackingKey(appLabelKey, trackingMethod)
|
||||
return fmt.Sprintf("mfst|%s|%s|%s|%s|%d", trackingKey, appName, revision, namespace, appSourceKey(appSrc, srcRefs, refSourceCommitSHAs)+clusterRuntimeInfoKey(info))
|
||||
key := fmt.Sprintf("mfst|%s|%s|%s|%s|%d", trackingKey, appName, revision, namespace, appSourceKey(appSrc, srcRefs, refSourceCommitSHAs)+clusterRuntimeInfoKey(info))
|
||||
if installationID != "" {
|
||||
key = fmt.Sprintf("%s|%s", key, installationID)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func trackingKey(appLabelKey string, trackingMethod string) string {
|
||||
@@ -323,14 +327,14 @@ func LogDebugManifestCacheKeyFields(message string, reason string, revision stri
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) SetNewRevisionManifests(newRevision string, revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace string, trackingMethod string, appLabelKey string, appName string, refSourceCommitSHAs ResolvedRevisions) error {
|
||||
oldKey := manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs)
|
||||
newKey := manifestCacheKey(newRevision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs)
|
||||
func (c *Cache) SetNewRevisionManifests(newRevision string, revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace string, trackingMethod string, appLabelKey string, appName string, refSourceCommitSHAs ResolvedRevisions, installationID string) error {
|
||||
oldKey := manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs, installationID)
|
||||
newKey := manifestCacheKey(newRevision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs, installationID)
|
||||
return c.cache.RenameItem(oldKey, newKey, c.repoCacheExpiration)
|
||||
}
|
||||
|
||||
func (c *Cache) GetManifests(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace string, trackingMethod string, appLabelKey string, appName string, res *CachedManifestResponse, refSourceCommitSHAs ResolvedRevisions) error {
|
||||
err := c.cache.GetItem(manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs), res)
|
||||
func (c *Cache) GetManifests(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace string, trackingMethod string, appLabelKey string, appName string, res *CachedManifestResponse, refSourceCommitSHAs ResolvedRevisions, installationID string) error {
|
||||
err := c.cache.GetItem(manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs, installationID), res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -346,7 +350,7 @@ func (c *Cache) GetManifests(revision string, appSrc *appv1.ApplicationSource, s
|
||||
|
||||
LogDebugManifestCacheKeyFields("deleting manifests cache", "manifest hash did not match or cached response is empty", revision, appSrc, srcRefs, clusterInfo, namespace, trackingMethod, appLabelKey, appName, refSourceCommitSHAs)
|
||||
|
||||
err = c.DeleteManifests(revision, appSrc, srcRefs, clusterInfo, namespace, trackingMethod, appLabelKey, appName, refSourceCommitSHAs)
|
||||
err = c.DeleteManifests(revision, appSrc, srcRefs, clusterInfo, namespace, trackingMethod, appLabelKey, appName, refSourceCommitSHAs, installationID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to delete manifest after hash mismatch, %w", err)
|
||||
}
|
||||
@@ -366,7 +370,7 @@ func (c *Cache) GetManifests(revision string, appSrc *appv1.ApplicationSource, s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) SetManifests(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace string, trackingMethod string, appLabelKey string, appName string, res *CachedManifestResponse, refSourceCommitSHAs ResolvedRevisions) error {
|
||||
func (c *Cache) SetManifests(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace string, trackingMethod string, appLabelKey string, appName string, res *CachedManifestResponse, refSourceCommitSHAs ResolvedRevisions, installationID string) error {
|
||||
// Generate and apply the cache entry hash, before writing
|
||||
if res != nil {
|
||||
res = res.shallowCopy()
|
||||
@@ -378,7 +382,7 @@ func (c *Cache) SetManifests(revision string, appSrc *appv1.ApplicationSource, s
|
||||
}
|
||||
|
||||
return c.cache.SetItem(
|
||||
manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs),
|
||||
manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs, installationID),
|
||||
res,
|
||||
&cacheutil.CacheActionOpts{
|
||||
Expiration: c.repoCacheExpiration,
|
||||
@@ -386,9 +390,9 @@ func (c *Cache) SetManifests(revision string, appSrc *appv1.ApplicationSource, s
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Cache) DeleteManifests(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace, trackingMethod, appLabelKey, appName string, refSourceCommitSHAs ResolvedRevisions) error {
|
||||
func (c *Cache) DeleteManifests(revision string, appSrc *appv1.ApplicationSource, srcRefs appv1.RefTargetRevisionMapping, clusterInfo ClusterRuntimeInfo, namespace, trackingMethod, appLabelKey, appName string, refSourceCommitSHAs ResolvedRevisions, installationID string) error {
|
||||
return c.cache.SetItem(
|
||||
manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs),
|
||||
manifestCacheKey(revision, appSrc, srcRefs, namespace, trackingMethod, appLabelKey, appName, clusterInfo, refSourceCommitSHAs, installationID),
|
||||
"",
|
||||
&cacheutil.CacheActionOpts{Delete: true})
|
||||
}
|
||||
|
||||
26
reposerver/cache/cache_test.go
vendored
26
reposerver/cache/cache_test.go
vendored
@@ -94,43 +94,43 @@ func TestCache_GetManifests(t *testing.T) {
|
||||
// cache miss
|
||||
q := &apiclient.ManifestRequest{}
|
||||
value := &CachedManifestResponse{}
|
||||
err := cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil)
|
||||
err := cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil, "")
|
||||
assert.Equal(t, ErrCacheMiss, err)
|
||||
// populate cache
|
||||
res := &CachedManifestResponse{ManifestResponse: &apiclient.ManifestResponse{SourceType: "my-source-type"}}
|
||||
err = cache.SetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", res, nil)
|
||||
err = cache.SetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", res, nil, "")
|
||||
require.NoError(t, err)
|
||||
t.Run("expect cache miss because of changed revision", func(t *testing.T) {
|
||||
err = cache.GetManifests("other-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil)
|
||||
err = cache.GetManifests("other-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil, "")
|
||||
assert.Equal(t, ErrCacheMiss, err)
|
||||
})
|
||||
t.Run("expect cache miss because of changed path", func(t *testing.T) {
|
||||
err = cache.GetManifests("my-revision", &ApplicationSource{Path: "other-path"}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil)
|
||||
err = cache.GetManifests("my-revision", &ApplicationSource{Path: "other-path"}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil, "")
|
||||
assert.Equal(t, ErrCacheMiss, err)
|
||||
})
|
||||
t.Run("expect cache miss because of changed namespace", func(t *testing.T) {
|
||||
err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "other-namespace", "", "my-app-label-key", "my-app-label-value", value, nil)
|
||||
err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "other-namespace", "", "my-app-label-key", "my-app-label-value", value, nil, "")
|
||||
assert.Equal(t, ErrCacheMiss, err)
|
||||
})
|
||||
t.Run("expect cache miss because of changed app label key", func(t *testing.T) {
|
||||
err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "other-app-label-key", "my-app-label-value", value, nil)
|
||||
err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "other-app-label-key", "my-app-label-value", value, nil, "")
|
||||
assert.Equal(t, ErrCacheMiss, err)
|
||||
})
|
||||
t.Run("expect cache miss because of changed app label value", func(t *testing.T) {
|
||||
err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "other-app-label-value", value, nil)
|
||||
err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "other-app-label-value", value, nil, "")
|
||||
assert.Equal(t, ErrCacheMiss, err)
|
||||
})
|
||||
t.Run("expect cache miss because of changed referenced source", func(t *testing.T) {
|
||||
err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "other-app-label-value", value, map[string]string{"my-referenced-source": "my-referenced-revision"})
|
||||
err = cache.GetManifests("my-revision", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "other-app-label-value", value, map[string]string{"my-referenced-source": "my-referenced-revision"}, "")
|
||||
assert.Equal(t, ErrCacheMiss, err)
|
||||
})
|
||||
t.Run("expect cache hit", func(t *testing.T) {
|
||||
err = cache.SetManifests(
|
||||
"my-revision1", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value",
|
||||
&CachedManifestResponse{ManifestResponse: &apiclient.ManifestResponse{SourceType: "my-source-type", Revision: "my-revision2"}}, nil)
|
||||
&CachedManifestResponse{ManifestResponse: &apiclient.ManifestResponse{SourceType: "my-source-type", Revision: "my-revision2"}}, nil, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = cache.GetManifests("my-revision1", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil)
|
||||
err = cache.GetManifests("my-revision1", &ApplicationSource{}, q.RefSources, q, "my-namespace", "", "my-app-label-key", "my-app-label-value", value, nil, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "my-source-type", value.ManifestResponse.SourceType)
|
||||
@@ -199,7 +199,7 @@ func TestCachedManifestResponse_HashBehavior(t *testing.T) {
|
||||
NumberOfConsecutiveFailures: 0,
|
||||
}
|
||||
q := &apiclient.ManifestRequest{}
|
||||
err := repoCache.SetManifests(response.Revision, appSrc, q.RefSources, q, response.Namespace, "", appKey, appValue, store, nil)
|
||||
err := repoCache.SetManifests(response.Revision, appSrc, q.RefSources, q, response.Namespace, "", appKey, appValue, store, nil, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -229,7 +229,7 @@ func TestCachedManifestResponse_HashBehavior(t *testing.T) {
|
||||
|
||||
// Retrieve the value using 'GetManifests' and confirm it works
|
||||
retrievedVal := &CachedManifestResponse{}
|
||||
err = repoCache.GetManifests(response.Revision, appSrc, q.RefSources, q, response.Namespace, "", appKey, appValue, retrievedVal, nil)
|
||||
err = repoCache.GetManifests(response.Revision, appSrc, q.RefSources, q, response.Namespace, "", appKey, appValue, retrievedVal, nil, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -251,7 +251,7 @@ func TestCachedManifestResponse_HashBehavior(t *testing.T) {
|
||||
|
||||
// Retrieve the value using GetManifests and confirm it returns a cache miss
|
||||
retrievedVal = &CachedManifestResponse{}
|
||||
err = repoCache.GetManifests(response.Revision, appSrc, q.RefSources, q, response.Namespace, "", appKey, appValue, retrievedVal, nil)
|
||||
err = repoCache.GetManifests(response.Revision, appSrc, q.RefSources, q, response.Namespace, "", appKey, appValue, retrievedVal, nil, "")
|
||||
|
||||
assert.Equal(t, err, cacheutil.ErrCacheMiss)
|
||||
|
||||
|
||||
@@ -826,7 +826,7 @@ func (s *Service) runManifestGenAsync(ctx context.Context, repoRoot, commitSHA,
|
||||
// Retrieve a new copy (if available) of the cached response: this ensures we are updating the latest copy of the cache,
|
||||
// rather than a copy of the cache that occurred before (a potentially lengthy) manifest generation.
|
||||
innerRes := &cache.CachedManifestResponse{}
|
||||
cacheErr := s.cache.GetManifests(cacheKey, appSourceCopy, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, innerRes, refSourceCommitSHAs)
|
||||
cacheErr := s.cache.GetManifests(cacheKey, appSourceCopy, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, innerRes, refSourceCommitSHAs, q.InstallationID)
|
||||
if cacheErr != nil && !errors.Is(cacheErr, cache.ErrCacheMiss) {
|
||||
logCtx.Warnf("manifest cache get error %s: %v", appSourceCopy.String(), cacheErr)
|
||||
ch.errCh <- cacheErr
|
||||
@@ -844,7 +844,7 @@ func (s *Service) runManifestGenAsync(ctx context.Context, repoRoot, commitSHA,
|
||||
// Update the cache to include failure information
|
||||
innerRes.NumberOfConsecutiveFailures++
|
||||
innerRes.MostRecentError = err.Error()
|
||||
cacheErr = s.cache.SetManifests(cacheKey, appSourceCopy, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, innerRes, refSourceCommitSHAs)
|
||||
cacheErr = s.cache.SetManifests(cacheKey, appSourceCopy, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, innerRes, refSourceCommitSHAs, q.InstallationID)
|
||||
|
||||
if cacheErr != nil {
|
||||
logCtx.Warnf("manifest cache set error %s: %v", appSourceCopy.String(), cacheErr)
|
||||
@@ -868,7 +868,7 @@ func (s *Service) runManifestGenAsync(ctx context.Context, repoRoot, commitSHA,
|
||||
}
|
||||
manifestGenResult.Revision = commitSHA
|
||||
manifestGenResult.VerifyResult = opContext.verificationResult
|
||||
err = s.cache.SetManifests(cacheKey, appSourceCopy, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, &manifestGenCacheEntry, refSourceCommitSHAs)
|
||||
err = s.cache.SetManifests(cacheKey, appSourceCopy, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, &manifestGenCacheEntry, refSourceCommitSHAs, q.InstallationID)
|
||||
if err != nil {
|
||||
log.Warnf("manifest cache set error %s/%s: %v", appSourceCopy.String(), cacheKey, err)
|
||||
}
|
||||
@@ -885,7 +885,7 @@ func (s *Service) getManifestCacheEntry(cacheKey string, q *apiclient.ManifestRe
|
||||
cache.LogDebugManifestCacheKeyFields("getting manifests cache", "GenerateManifest API call", cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, refSourceCommitSHAs)
|
||||
|
||||
res := cache.CachedManifestResponse{}
|
||||
err := s.cache.GetManifests(cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, &res, refSourceCommitSHAs)
|
||||
err := s.cache.GetManifests(cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, &res, refSourceCommitSHAs, q.InstallationID)
|
||||
if err == nil {
|
||||
// The cache contains an existing value
|
||||
|
||||
@@ -902,7 +902,7 @@ func (s *Service) getManifestCacheEntry(cacheKey string, q *apiclient.ManifestRe
|
||||
cache.LogDebugManifestCacheKeyFields("deleting manifests cache", "manifest hash did not match or cached response is empty", cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, refSourceCommitSHAs)
|
||||
|
||||
// We can now try again, so reset the cache state and run the operation below
|
||||
err = s.cache.DeleteManifests(cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, refSourceCommitSHAs)
|
||||
err = s.cache.DeleteManifests(cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, refSourceCommitSHAs, q.InstallationID)
|
||||
if err != nil {
|
||||
log.Warnf("manifest cache set error %s/%s: %v", q.ApplicationSource.String(), cacheKey, err)
|
||||
}
|
||||
@@ -917,7 +917,7 @@ func (s *Service) getManifestCacheEntry(cacheKey string, q *apiclient.ManifestRe
|
||||
cache.LogDebugManifestCacheKeyFields("deleting manifests cache", "reset after paused generation count", cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, refSourceCommitSHAs)
|
||||
|
||||
// We can now try again, so reset the error cache state and run the operation below
|
||||
err = s.cache.DeleteManifests(cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, refSourceCommitSHAs)
|
||||
err = s.cache.DeleteManifests(cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, refSourceCommitSHAs, q.InstallationID)
|
||||
if err != nil {
|
||||
log.Warnf("manifest cache set error %s/%s: %v", q.ApplicationSource.String(), cacheKey, err)
|
||||
}
|
||||
@@ -937,7 +937,7 @@ func (s *Service) getManifestCacheEntry(cacheKey string, q *apiclient.ManifestRe
|
||||
// Increment the number of returned cached responses and push that new value to the cache
|
||||
// (if we have not already done so previously in this function)
|
||||
res.NumberOfCachedResponsesReturned++
|
||||
err = s.cache.SetManifests(cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, &res, refSourceCommitSHAs)
|
||||
err = s.cache.SetManifests(cacheKey, q.ApplicationSource, q.RefSources, q, q.Namespace, q.TrackingMethod, q.AppLabelKey, q.AppName, &res, refSourceCommitSHAs, q.InstallationID)
|
||||
if err != nil {
|
||||
log.Warnf("manifest cache set error %s/%s: %v", q.ApplicationSource.String(), cacheKey, err)
|
||||
}
|
||||
@@ -1490,7 +1490,7 @@ func GenerateManifests(ctx context.Context, appPath, repoRoot, revision string,
|
||||
|
||||
for _, target := range targets {
|
||||
if q.AppLabelKey != "" && q.AppName != "" && !kube.IsCRD(target) {
|
||||
err = resourceTracking.SetAppInstance(target, q.AppLabelKey, q.AppName, q.Namespace, v1alpha1.TrackingMethod(q.TrackingMethod))
|
||||
err = resourceTracking.SetAppInstance(target, q.AppLabelKey, q.AppName, q.Namespace, v1alpha1.TrackingMethod(q.TrackingMethod), q.InstallationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to set app instance tracking info on manifest: %w", err)
|
||||
}
|
||||
@@ -2858,7 +2858,7 @@ func (s *Service) updateCachedRevision(logCtx *log.Entry, oldRev string, newRev
|
||||
}
|
||||
}
|
||||
|
||||
err := s.cache.SetNewRevisionManifests(newRev, oldRev, request.ApplicationSource, request.RefSources, request, request.Namespace, request.TrackingMethod, request.AppLabelKey, request.AppName, repoRefs)
|
||||
err := s.cache.SetNewRevisionManifests(newRev, oldRev, request.ApplicationSource, request.RefSources, request, request.Namespace, request.TrackingMethod, request.AppLabelKey, request.AppName, repoRefs, request.InstallationID)
|
||||
if err != nil {
|
||||
if errors.Is(err, cache.ErrCacheMiss) {
|
||||
logCtx.Debugf("manifest cache miss during comparison for application %s in repo %s from revision %s", request.AppName, request.GetRepo().Repo, oldRev)
|
||||
|
||||
@@ -38,6 +38,8 @@ message ManifestRequest {
|
||||
repeated string projectSourceRepos = 24;
|
||||
// This is used to surface "source not permitted" errors for Helm repositories
|
||||
string projectName = 25;
|
||||
// Holds instance installation id
|
||||
string installationID = 27;
|
||||
}
|
||||
|
||||
message ManifestRequestWithFiles {
|
||||
@@ -281,6 +283,7 @@ message UpdateRevisionForPathsRequest {
|
||||
repeated string paths = 13;
|
||||
|
||||
bool noRevisionCache = 14;
|
||||
string installationID = 15;
|
||||
}
|
||||
|
||||
message UpdateRevisionForPathsResponse {
|
||||
|
||||
@@ -313,7 +313,7 @@ func TestGenerateManifests_K8SAPIResetCache(t *testing.T) {
|
||||
|
||||
cachedFakeResponse := &apiclient.ManifestResponse{Manifests: []string{"Fake"}, Revision: mock.Anything}
|
||||
|
||||
err := service.cache.SetManifests(mock.Anything, &src, q.RefSources, &q, "", "", "", "", &cache.CachedManifestResponse{ManifestResponse: cachedFakeResponse}, nil)
|
||||
err := service.cache.SetManifests(mock.Anything, &src, q.RefSources, &q, "", "", "", "", &cache.CachedManifestResponse{ManifestResponse: cachedFakeResponse}, nil, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := service.GenerateManifest(context.Background(), &q)
|
||||
@@ -338,7 +338,7 @@ func TestGenerateManifests_EmptyCache(t *testing.T) {
|
||||
ProjectSourceRepos: []string{"*"},
|
||||
}
|
||||
|
||||
err := service.cache.SetManifests(mock.Anything, &src, q.RefSources, &q, "", "", "", "", &cache.CachedManifestResponse{ManifestResponse: nil}, nil)
|
||||
err := service.cache.SetManifests(mock.Anything, &src, q.RefSources, &q, "", "", "", "", &cache.CachedManifestResponse{ManifestResponse: nil}, nil, "")
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := service.GenerateManifest(context.Background(), &q)
|
||||
@@ -774,7 +774,7 @@ func TestManifestGenErrorCacheByNumRequests(t *testing.T) {
|
||||
assert.NotNil(t, manifestRequest)
|
||||
|
||||
cachedManifestResponse := &cache.CachedManifestResponse{}
|
||||
err := service.cache.GetManifests(mock.Anything, manifestRequest.ApplicationSource, manifestRequest.RefSources, manifestRequest, manifestRequest.Namespace, "", manifestRequest.AppLabelKey, manifestRequest.AppName, cachedManifestResponse, nil)
|
||||
err := service.cache.GetManifests(mock.Anything, manifestRequest.ApplicationSource, manifestRequest.RefSources, manifestRequest, manifestRequest.Namespace, "", manifestRequest.AppLabelKey, manifestRequest.AppName, cachedManifestResponse, nil, "")
|
||||
require.NoError(t, err)
|
||||
return cachedManifestResponse
|
||||
}
|
||||
@@ -2113,7 +2113,7 @@ func TestGenerateManifestsWithAppParameterFile(t *testing.T) {
|
||||
// Try to pull from the cache with a `source` that does not include any overrides. Overrides should not be
|
||||
// part of the cache key, because you can't get the overrides without a repo operation. And avoiding repo
|
||||
// operations is the point of the cache.
|
||||
err = service.cache.GetManifests(mock.Anything, source, argoappv1.RefTargetRevisionMapping{}, &argoappv1.ClusterInfo{}, "", "", "", "test", res, nil)
|
||||
err = service.cache.GetManifests(mock.Anything, source, argoappv1.RefTargetRevisionMapping{}, &argoappv1.ClusterInfo{}, "", "", "", "test", res, nil, "")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -14,10 +14,10 @@ if obj.status.status ~= nil then
|
||||
-- "root" policy
|
||||
for i, entry in ipairs(obj.status.status) do
|
||||
if entry.compliant ~= "Compliant" then
|
||||
noncompliants[i] = entry.clustername
|
||||
table.insert(noncompliants, entry.clustername)
|
||||
end
|
||||
end
|
||||
if table.getn(noncompliants) == 0 then
|
||||
if #noncompliants == 0 then
|
||||
hs.message = "All clusters are compliant"
|
||||
else
|
||||
hs.message = "NonCompliant clusters: " .. table.concat(noncompliants, ", ")
|
||||
@@ -26,10 +26,10 @@ elseif obj.status.details ~= nil then
|
||||
-- "replicated" policy
|
||||
for i, entry in ipairs(obj.status.details) do
|
||||
if entry.compliant ~= "Compliant" then
|
||||
noncompliants[i] = entry.templateMeta.name
|
||||
table.insert(noncompliants, entry.templateMeta.name)
|
||||
end
|
||||
end
|
||||
if table.getn(noncompliants) == 0 then
|
||||
if #noncompliants == 0 then
|
||||
hs.message = "All templates are compliant"
|
||||
else
|
||||
hs.message = "NonCompliant templates: " .. table.concat(noncompliants, ", ")
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
apiVersion: policy.open-cluster-management.io/v1
|
||||
kind: Policy
|
||||
metadata:
|
||||
name: open-cluster-management-global-set.argo-example
|
||||
namespace: local-cluster
|
||||
labels:
|
||||
policy.open-cluster-management.io/cluster-name: local-cluster
|
||||
policy.open-cluster-management.io/cluster-namespace: local-cluster
|
||||
policy.open-cluster-management.io/root-policy: open-cluster-management-global-set.argo-example
|
||||
spec:
|
||||
disabled: false
|
||||
policy-templates:
|
||||
- objectDefinition:
|
||||
apiVersion: policy.open-cluster-management.io/v1
|
||||
kind: ConfigurationPolicy
|
||||
metadata:
|
||||
name: example-namespace
|
||||
spec:
|
||||
object-templates:
|
||||
- complianceType: musthave
|
||||
objectDefinition:
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: example
|
||||
remediationAction: inform
|
||||
severity: low
|
||||
- objectDefinition:
|
||||
apiVersion: policy.open-cluster-management.io/v1
|
||||
kind: ConfigurationPolicy
|
||||
metadata:
|
||||
name: example-pod
|
||||
spec:
|
||||
namespaceSelector:
|
||||
exclude:
|
||||
- kube-*
|
||||
include:
|
||||
- default
|
||||
object-templates:
|
||||
- complianceType: musthave
|
||||
objectDefinition:
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: foobar
|
||||
spec:
|
||||
containers:
|
||||
- image: 'registry.redhat.io/rhel9/httpd-24:latest'
|
||||
name: httpd
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
privileged: false
|
||||
runAsNonRoot: true
|
||||
remediationAction: enforce
|
||||
severity: low
|
||||
status:
|
||||
compliant: NonCompliant
|
||||
details:
|
||||
- compliant: Compliant
|
||||
history:
|
||||
- eventName: open-cluster-management-global-set.argo-example.17e7034c879045a3
|
||||
lastTimestamp: '2024-07-30T14:16:49Z'
|
||||
message: 'Compliant; notification - pods [foobar] was created successfully in namespace default'
|
||||
templateMeta:
|
||||
creationTimestamp: null
|
||||
name: example-foo
|
||||
- compliant: NonCompliant
|
||||
history:
|
||||
- eventName: open-cluster-management-global-set.argo-example.17e701cc5101e3a4
|
||||
lastTimestamp: '2024-07-30T13:49:19Z'
|
||||
message: 'NonCompliant; violation - namespaces [example] not found'
|
||||
templateMeta:
|
||||
creationTimestamp: null
|
||||
name: example-namespace
|
||||
- compliant: Compliant
|
||||
history:
|
||||
- eventName: open-cluster-management-global-set.argo-example.17e7034c879045a3
|
||||
lastTimestamp: '2024-07-30T14:16:49Z'
|
||||
message: 'Compliant; notification - pods [foobar] was created successfully in namespace default'
|
||||
- eventName: open-cluster-management-global-set.argo-example.17e7020b47782ddc
|
||||
lastTimestamp: '2024-07-30T13:53:49Z'
|
||||
message: 'NonCompliant; violation - pods [foobar] not found in namespace default'
|
||||
templateMeta:
|
||||
creationTimestamp: null
|
||||
name: example-pod
|
||||
@@ -7,12 +7,13 @@ hs.message = "Waiting for status"
|
||||
if obj.status ~= nil then
|
||||
if obj.status.conditions ~= nil then
|
||||
for i, condition in ipairs(obj.status.conditions) do
|
||||
if condition.status == "False" then
|
||||
-- InsufficientPods can have valid use cases
|
||||
-- See a discussion in https://github.com/argoproj/argo-cd/issues/20171 for more details
|
||||
if condition.status == "False" and condition.reason ~= "InsufficientPods" then
|
||||
hs.status = "Degraded"
|
||||
hs.message = "PodDisruptionBudget has " .. condition.reason
|
||||
return hs
|
||||
end
|
||||
if condition.status == "True" then
|
||||
else
|
||||
hs.status = "Healthy"
|
||||
hs.message = "PodDisruptionBudget has " .. condition.reason
|
||||
end
|
||||
|
||||
@@ -9,5 +9,5 @@ tests:
|
||||
inputPath: testdata/progressing.yaml
|
||||
- healthStatus:
|
||||
status: Degraded
|
||||
message: 'PodDisruptionBudget has InsufficientPods'
|
||||
message: 'PodDisruptionBudget has SyncFailed'
|
||||
inputPath: testdata/degraded.yaml
|
||||
|
||||
@@ -16,6 +16,12 @@ status:
|
||||
reason: InsufficientPods
|
||||
status: "False"
|
||||
type: DisruptionAllowed
|
||||
- lastTransitionTime: "2024-09-06T18:29:06Z"
|
||||
message: ""
|
||||
observedGeneration: 2
|
||||
reason: SyncFailed
|
||||
status: "False"
|
||||
type: DisruptionAllowed
|
||||
currentHealthy: 2
|
||||
desiredHealthy: 3
|
||||
disruptionsAllowed: 0
|
||||
|
||||
@@ -1,50 +1,59 @@
|
||||
-- isInferenceServiceInRawDeploymentMode determines if the inference service deployed in RawDeployment mode
|
||||
-- KServe v12 and above supports Rawdeployment for Inference graphs. For Inference services, KServe has supported RawDeployment model since [v0.7.0](https://github.com/kserve/kserve/releases/tag/v0.7.0).
|
||||
function isInferenceServiceInRawDeploymentMode(obj)
|
||||
if obj.metadata.annotations == nil then
|
||||
return false
|
||||
end
|
||||
local deploymentMode = obj.metadata.annotations["serving.kserve.io/deploymentMode"]
|
||||
return deploymentMode ~= nil and deploymentMode == "RawDeployment"
|
||||
end
|
||||
|
||||
local health_status = {}
|
||||
|
||||
health_status.status = "Progressing"
|
||||
health_status.message = "Waiting for status update."
|
||||
if obj.status ~= nil and obj.status.conditions ~= nil then
|
||||
local status_true = 0
|
||||
health_status.message = "Waiting for InferenceService to report status..."
|
||||
|
||||
if obj.status ~= nil then
|
||||
|
||||
local progressing = false
|
||||
local degraded = false
|
||||
local status_false = 0
|
||||
local status_unknown = 0
|
||||
health_status.message = ""
|
||||
for i, condition in pairs(obj.status.conditions) do
|
||||
if condition.status == "True" and (condition.type == "IngressReady" or condition.type == "PredictorConfigurationReady" or condition.type == "PredictorReady" or condition.type == "PredictorRouteReady" or condition.type == "Ready") then
|
||||
status_true = status_true + 1
|
||||
elseif condition.status == "False" or condition.status == "Unknown" then
|
||||
msg = condition.type .. " is " .. condition.status
|
||||
if condition.reason ~= nil and condition.reason ~= "" then
|
||||
msg = msg .. ", since " .. condition.reason .. "."
|
||||
end
|
||||
if condition.message ~= nil and condition.message ~= "" then
|
||||
msg = msg .. " " .. condition.message
|
||||
end
|
||||
health_status.message = health_status.message .. msg .. "\n"
|
||||
if condition.status == "False" then
|
||||
status_false = status_false + 1
|
||||
local msg = ""
|
||||
|
||||
if obj.status.modelStatus ~= nil then
|
||||
if obj.status.modelStatus.transitionStatus ~= "UpToDate" then
|
||||
if obj.status.modelStatus.transitionStatus == "InProgress" then
|
||||
progressing = true
|
||||
else
|
||||
status_unknown = status_unknown + 1
|
||||
degraded = true
|
||||
end
|
||||
msg = msg .. "0: transitionStatus | " .. obj.status.modelStatus.transitionStatus
|
||||
end
|
||||
end
|
||||
if ((isInferenceServiceInRawDeploymentMode(obj) and status_true == 3) or status_true == 5) and status_false == 0 and status_unknown == 0 then
|
||||
health_status.message = "Inference Service is healthy."
|
||||
health_status.status = "Healthy"
|
||||
return health_status
|
||||
elseif status_false > 0 then
|
||||
health_status.status = "Degraded"
|
||||
return health_status
|
||||
else
|
||||
health_status.status = "Progressing"
|
||||
return health_status
|
||||
|
||||
if obj.status.conditions ~= nil then
|
||||
for i, condition in pairs(obj.status.conditions) do
|
||||
|
||||
if condition.status == "Unknown" then
|
||||
status_unknown = status_unknown + 1
|
||||
elseif condition.status == "False" then
|
||||
status_false = status_false + 1
|
||||
end
|
||||
|
||||
if condition.status ~= "True" then
|
||||
msg = msg .. " | " .. i .. ": " .. condition.type .. " | " .. condition.status
|
||||
if condition.reason ~= nil and condition.reason ~= "" then
|
||||
msg = msg .. " | " .. condition.reason
|
||||
end
|
||||
if condition.message ~= nil and condition.message ~= "" then
|
||||
msg = msg .. " | " .. condition.message
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if progressing == false and degraded == false and status_unknown == 0 and status_false == 0 then
|
||||
health_status.status = "Healthy"
|
||||
msg = "InferenceService is healthy."
|
||||
elseif degraded == false and status_unknown >= 0 then
|
||||
health_status.status = "Progressing"
|
||||
else
|
||||
health_status.status = "Degraded"
|
||||
end
|
||||
|
||||
health_status.message = msg
|
||||
end
|
||||
end
|
||||
return health_status
|
||||
|
||||
return health_status
|
||||
|
||||
@@ -1,17 +1,41 @@
|
||||
tests:
|
||||
- healthStatus:
|
||||
status: Progressing
|
||||
message: "PredictorConfigurationReady is Unknown\nPredictorReady is Unknown, since RevisionMissing. Configuration \"hello-world-predictor-default\" is waiting for a Revision to become ready.\nPredictorRouteReady is Unknown, since RevisionMissing. Configuration \"hello-world-predictor-default\" is waiting for a Revision to become ready.\nReady is Unknown, since RevisionMissing. Configuration \"hello-world-predictor-default\" is waiting for a Revision to become ready.\n"
|
||||
message: ' | 1: PredictorConfigurationReady | Unknown | 2: PredictorReady | Unknown | RevisionMissing | Configuration "hello-world-predictor-default" is waiting for a Revision to become ready. | 3: PredictorRouteReady | Unknown | RevisionMissing | Configuration "hello-world-predictor-default" is waiting for a Revision to become ready. | 4: Ready | Unknown | RevisionMissing | Configuration "hello-world-predictor-default" is waiting for a Revision to become ready.'
|
||||
inputPath: testdata/progressing.yaml
|
||||
- healthStatus:
|
||||
status: Progressing
|
||||
message: '0: transitionStatus | InProgress | 1: LatestDeploymentReady | Unknown | PredictorConfigurationReady not ready | 2: PredictorConfigurationReady | Unknown | 3: PredictorReady | Unknown | RevisionMissing | Configuration "helloworld-predictor" is waiting for a Revision to become ready. | 4: PredictorRouteReady | Unknown | RevisionMissing | Configuration "helloworld-predictor" is waiting for a Revision to become ready. | 5: Ready | Unknown | RevisionMissing | Configuration "helloworld-predictor" is waiting for a Revision to become ready. | 6: RoutesReady | Unknown | PredictorRouteReady not ready'
|
||||
inputPath: testdata/progressing_ocp.yaml
|
||||
- healthStatus:
|
||||
status: Progressing
|
||||
message: "0: transitionStatus | InProgress | 1: PredictorReady | False | 2: Ready | False"
|
||||
inputPath: testdata/progressing_modelmesh.yaml
|
||||
- healthStatus:
|
||||
status: Degraded
|
||||
message: "IngressReady is False, since Predictor ingress not created.\nPredictorConfigurationReady is False, since RevisionFailed. Revision \"helloworld-00002\" failed with message: Container failed with: container exited with no error.\nPredictorReady is False, since RevisionFailed. Revision \"helloworld-00002\" failed with message: Container failed with: container exited with no error.\nReady is False, since Predictor ingress not created.\n"
|
||||
message: '0: transitionStatus | BlockedByFailedLoad | 1: IngressReady | False | Predictor ingress not created | 2: PredictorConfigurationReady | False | RevisionFailed | Revision "helloworld-00002" failed with message: Container failed with: container exited with no error. | 3: PredictorReady | False | RevisionFailed | Revision "helloworld-00002" failed with message: Container failed with: container exited with no error. | 5: Ready | False | Predictor ingress not created'
|
||||
inputPath: testdata/degraded.yaml
|
||||
- healthStatus:
|
||||
status: Degraded
|
||||
message: '0: transitionStatus | BlockedByFailedLoad | 1: LatestDeploymentReady | False | PredictorConfigurationReady not ready | 2: PredictorConfigurationReady | False | RevisionFailed | Revision "helloworld-predictor-00002" failed with message: . | 3: PredictorReady | False | RevisionMissing | Configuration "helloworld-predictor" does not have any ready Revision. | 4: PredictorRouteReady | False | RevisionMissing | Configuration "helloworld-predictor" does not have any ready Revision. | 5: Ready | False | RevisionMissing | Configuration "helloworld-predictor" does not have any ready Revision. | 6: RoutesReady | False | PredictorRouteReady not ready'
|
||||
inputPath: testdata/degraded_ocp.yaml
|
||||
- healthStatus:
|
||||
status: Degraded
|
||||
message: "0: transitionStatus | BlockedByFailedLoad"
|
||||
inputPath: testdata/degraded_modelmesh.yaml
|
||||
- healthStatus:
|
||||
status: Healthy
|
||||
message: Inference Service is healthy.
|
||||
message: InferenceService is healthy.
|
||||
inputPath: testdata/healthy.yaml
|
||||
- healthStatus:
|
||||
status: Healthy
|
||||
message: Inference Service is healthy.
|
||||
message: InferenceService is healthy.
|
||||
inputPath: testdata/healthy_ocp.yaml
|
||||
- healthStatus:
|
||||
status: Healthy
|
||||
message: InferenceService is healthy.
|
||||
inputPath: testdata/healthy_modelmesh.yaml
|
||||
- healthStatus:
|
||||
status: Healthy
|
||||
message: InferenceService is healthy.
|
||||
inputPath: testdata/healthy_raw.yaml
|
||||
|
||||
@@ -28,3 +28,5 @@ status:
|
||||
reason: Predictor ingress not created
|
||||
status: "False"
|
||||
type: Ready
|
||||
modelStatus:
|
||||
transitionStatus: BlockedByFailedLoad
|
||||
16
resource_customizations/serving.kserve.io/InferenceService/testdata/degraded_modelmesh.yaml
vendored
Normal file
16
resource_customizations/serving.kserve.io/InferenceService/testdata/degraded_modelmesh.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: serving.kserve.io/v1beta1
|
||||
kind: InferenceService
|
||||
metadata:
|
||||
name: helloworld
|
||||
namespace: default
|
||||
spec: {}
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: '2024-05-30T22:43:16Z'
|
||||
status: 'True'
|
||||
type: PredictorReady
|
||||
- lastTransitionTime: '2024-05-30T22:43:16Z'
|
||||
status: 'True'
|
||||
type: Ready
|
||||
modelStatus:
|
||||
transitionStatus: BlockedByFailedLoad
|
||||
42
resource_customizations/serving.kserve.io/InferenceService/testdata/degraded_ocp.yaml
vendored
Normal file
42
resource_customizations/serving.kserve.io/InferenceService/testdata/degraded_ocp.yaml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
apiVersion: serving.kserve.io/v1beta1
|
||||
kind: InferenceService
|
||||
metadata:
|
||||
name: helloworld
|
||||
namespace: default
|
||||
spec: {}
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: '2024-05-30T23:03:45Z'
|
||||
reason: PredictorConfigurationReady not ready
|
||||
severity: Info
|
||||
status: 'False'
|
||||
type: LatestDeploymentReady
|
||||
- lastTransitionTime: '2024-05-30T23:03:45Z'
|
||||
message: 'Revision "helloworld-predictor-00002" failed with message: .'
|
||||
reason: RevisionFailed
|
||||
severity: Info
|
||||
status: 'False'
|
||||
type: PredictorConfigurationReady
|
||||
- lastTransitionTime: '2024-05-30T23:03:45Z'
|
||||
message: Configuration "helloworld-predictor" does not have any ready Revision.
|
||||
reason: RevisionMissing
|
||||
status: 'False'
|
||||
type: PredictorReady
|
||||
- lastTransitionTime: '2024-05-30T23:03:45Z'
|
||||
message: Configuration "helloworld-predictor" does not have any ready Revision.
|
||||
reason: RevisionMissing
|
||||
severity: Info
|
||||
status: 'False'
|
||||
type: PredictorRouteReady
|
||||
- lastTransitionTime: '2024-05-30T23:03:45Z'
|
||||
message: Configuration "helloworld-predictor" does not have any ready Revision.
|
||||
reason: RevisionMissing
|
||||
status: 'False'
|
||||
type: Ready
|
||||
- lastTransitionTime: '2024-05-30T23:03:45Z'
|
||||
reason: PredictorRouteReady not ready
|
||||
severity: Info
|
||||
status: 'False'
|
||||
type: RoutesReady
|
||||
modelStatus:
|
||||
transitionStatus: BlockedByFailedLoad
|
||||
16
resource_customizations/serving.kserve.io/InferenceService/testdata/healthy_modelmesh.yaml
vendored
Normal file
16
resource_customizations/serving.kserve.io/InferenceService/testdata/healthy_modelmesh.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: serving.kserve.io/v1beta1
|
||||
kind: InferenceService
|
||||
metadata:
|
||||
name: helloworld
|
||||
namespace: default
|
||||
spec: {}
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: '2024-05-30T22:43:16Z'
|
||||
status: 'True'
|
||||
type: PredictorReady
|
||||
- lastTransitionTime: '2024-05-30T22:43:16Z'
|
||||
status: 'True'
|
||||
type: Ready
|
||||
modelStatus:
|
||||
transitionStatus: UpToDate
|
||||
35
resource_customizations/serving.kserve.io/InferenceService/testdata/healthy_ocp.yaml
vendored
Normal file
35
resource_customizations/serving.kserve.io/InferenceService/testdata/healthy_ocp.yaml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
apiVersion: serving.kserve.io/v1beta1
|
||||
kind: InferenceService
|
||||
metadata:
|
||||
name: helloworld
|
||||
namespace: default
|
||||
spec: {}
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: '2024-05-30T22:14:31Z'
|
||||
status: 'True'
|
||||
type: IngressReady
|
||||
- lastTransitionTime: '2024-05-30T22:14:30Z'
|
||||
severity: Info
|
||||
status: 'True'
|
||||
type: LatestDeploymentReady
|
||||
- lastTransitionTime: '2024-05-30T22:14:30Z'
|
||||
severity: Info
|
||||
status: 'True'
|
||||
type: PredictorConfigurationReady
|
||||
- lastTransitionTime: '2024-05-30T22:14:31Z'
|
||||
status: 'True'
|
||||
type: PredictorReady
|
||||
- lastTransitionTime: '2024-05-30T22:14:31Z'
|
||||
severity: Info
|
||||
status: 'True'
|
||||
type: PredictorRouteReady
|
||||
- lastTransitionTime: '2024-05-30T22:14:31Z'
|
||||
status: 'True'
|
||||
type: Ready
|
||||
- lastTransitionTime: '2024-05-30T22:14:31Z'
|
||||
severity: Info
|
||||
status: 'True'
|
||||
type: RoutesReady
|
||||
modelStatus:
|
||||
transitionStatus: UpToDate
|
||||
16
resource_customizations/serving.kserve.io/InferenceService/testdata/progressing_modelmesh.yaml
vendored
Normal file
16
resource_customizations/serving.kserve.io/InferenceService/testdata/progressing_modelmesh.yaml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
apiVersion: serving.kserve.io/v1beta1
|
||||
kind: InferenceService
|
||||
metadata:
|
||||
name: helloworld
|
||||
namespace: default
|
||||
spec: {}
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: '2024-05-30T22:43:16Z'
|
||||
status: 'False'
|
||||
type: PredictorReady
|
||||
- lastTransitionTime: '2024-05-30T22:43:16Z'
|
||||
status: 'False'
|
||||
type: Ready
|
||||
modelStatus:
|
||||
transitionStatus: InProgress
|
||||
40
resource_customizations/serving.kserve.io/InferenceService/testdata/progressing_ocp.yaml
vendored
Normal file
40
resource_customizations/serving.kserve.io/InferenceService/testdata/progressing_ocp.yaml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
apiVersion: serving.kserve.io/v1beta1
|
||||
kind: InferenceService
|
||||
metadata:
|
||||
name: helloworld
|
||||
namespace: default
|
||||
spec: {}
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: '2024-05-30T22:29:46Z'
|
||||
reason: PredictorConfigurationReady not ready
|
||||
severity: Info
|
||||
status: Unknown
|
||||
type: LatestDeploymentReady
|
||||
- lastTransitionTime: '2024-05-30T22:29:46Z'
|
||||
severity: Info
|
||||
status: Unknown
|
||||
type: PredictorConfigurationReady
|
||||
- lastTransitionTime: '2024-05-30T22:29:46Z'
|
||||
message: Configuration "helloworld-predictor" is waiting for a Revision to become ready.
|
||||
reason: RevisionMissing
|
||||
status: Unknown
|
||||
type: PredictorReady
|
||||
- lastTransitionTime: '2024-05-30T22:29:46Z'
|
||||
message: Configuration "helloworld-predictor" is waiting for a Revision to become ready.
|
||||
reason: RevisionMissing
|
||||
severity: Info
|
||||
status: Unknown
|
||||
type: PredictorRouteReady
|
||||
- lastTransitionTime: '2024-05-30T22:29:46Z'
|
||||
message: Configuration "helloworld-predictor" is waiting for a Revision to become ready.
|
||||
reason: RevisionMissing
|
||||
status: Unknown
|
||||
type: Ready
|
||||
- lastTransitionTime: '2024-05-30T22:29:46Z'
|
||||
reason: PredictorRouteReady not ready
|
||||
severity: Info
|
||||
status: Unknown
|
||||
type: RoutesReady
|
||||
modelStatus:
|
||||
transitionStatus: InProgress
|
||||
@@ -112,6 +112,7 @@ func NewServer(
|
||||
settingsMgr *settings.SettingsManager,
|
||||
projInformer cache.SharedIndexInformer,
|
||||
enabledNamespaces []string,
|
||||
enableK8sEvent []string,
|
||||
) (application.ApplicationServiceServer, AppResourceTreeFn) {
|
||||
if appBroadcaster == nil {
|
||||
appBroadcaster = &broadcasterHandler{}
|
||||
@@ -133,7 +134,7 @@ func NewServer(
|
||||
kubectl: kubectl,
|
||||
enf: enf,
|
||||
projectLock: projectLock,
|
||||
auditLogger: argo.NewAuditLogger(namespace, kubeclientset, "argocd-server"),
|
||||
auditLogger: argo.NewAuditLogger(namespace, kubeclientset, "argocd-server", enableK8sEvent),
|
||||
settingsMgr: settingsMgr,
|
||||
projInformer: projInformer,
|
||||
enabledNamespaces: enabledNamespaces,
|
||||
@@ -509,6 +510,10 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting kustomize settings options: %w", err)
|
||||
}
|
||||
installationID, err := s.settingsMgr.GetInstallationID()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting installation ID: %w", err)
|
||||
}
|
||||
|
||||
manifestInfo, err := client.GenerateManifest(ctx, &apiclient.ManifestRequest{
|
||||
Repo: repo,
|
||||
@@ -529,6 +534,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
|
||||
ProjectSourceRepos: proj.Spec.SourceRepos,
|
||||
HasMultipleSources: a.Spec.HasMultipleSources(),
|
||||
RefSources: refSources,
|
||||
InstallationID: installationID,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating manifests: %w", err)
|
||||
@@ -1886,7 +1892,11 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
|
||||
|
||||
s.inferResourcesStatusHealth(a)
|
||||
|
||||
if !proj.Spec.SyncWindows.Matches(a).CanSync(true) {
|
||||
canSync, err := proj.Spec.SyncWindows.Matches(a).CanSync(true)
|
||||
if err != nil {
|
||||
return a, status.Errorf(codes.PermissionDenied, "cannot sync: invalid sync window: %v", err)
|
||||
}
|
||||
if !canSync {
|
||||
return a, status.Errorf(codes.PermissionDenied, "cannot sync: blocked by sync window")
|
||||
}
|
||||
|
||||
@@ -2201,7 +2211,7 @@ func getAmbiguousRevision(app *appv1.Application, syncReq *application.Applicati
|
||||
ambiguousRevision := ""
|
||||
if app.Spec.HasMultipleSources() {
|
||||
for i, pos := range syncReq.SourcePositions {
|
||||
if pos == int64(sourceIndex) {
|
||||
if pos == int64(sourceIndex+1) {
|
||||
ambiguousRevision = syncReq.Revisions[i]
|
||||
}
|
||||
}
|
||||
@@ -2603,10 +2613,17 @@ func (s *Server) GetApplicationSyncWindows(ctx context.Context, q *application.A
|
||||
}
|
||||
|
||||
windows := proj.Spec.SyncWindows.Matches(a)
|
||||
sync := windows.CanSync(true)
|
||||
sync, err := windows.CanSync(true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid sync windows: %w", err)
|
||||
}
|
||||
|
||||
activeWindows, err := windows.Active()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid sync windows: %w", err)
|
||||
}
|
||||
res := &application.ApplicationSyncWindowsResponse{
|
||||
ActiveWindows: convertSyncWindows(windows.Active()),
|
||||
ActiveWindows: convertSyncWindows(activeWindows),
|
||||
AssignedWindows: convertSyncWindows(windows),
|
||||
CanSync: &sync,
|
||||
}
|
||||
|
||||
@@ -69,6 +69,8 @@ const (
|
||||
fakeRepoURL = "https://git.com/repo.git"
|
||||
)
|
||||
|
||||
var testEnableEventList []string = argo.DefaultEnableEventList()
|
||||
|
||||
func fakeRepo() *appsv1.Repository {
|
||||
return &appsv1.Repository{
|
||||
Repo: fakeRepoURL,
|
||||
@@ -306,6 +308,7 @@ func newTestAppServerWithEnforcerConfigure(f func(*rbac.Enforcer), t *testing.T,
|
||||
settingsMgr,
|
||||
projInformer,
|
||||
[]string{},
|
||||
testEnableEventList,
|
||||
)
|
||||
return server.(*Server)
|
||||
}
|
||||
@@ -486,6 +489,7 @@ func newTestAppServerWithEnforcerConfigureWithBenchmark(f func(*rbac.Enforcer),
|
||||
settingsMgr,
|
||||
projInformer,
|
||||
[]string{},
|
||||
testEnableEventList,
|
||||
)
|
||||
return server.(*Server)
|
||||
}
|
||||
@@ -2934,7 +2938,7 @@ func TestGetAmbiguousRevision_MultiSource(t *testing.T) {
|
||||
},
|
||||
}
|
||||
syncReq := &application.ApplicationSyncRequest{
|
||||
SourcePositions: []int64{0, 1},
|
||||
SourcePositions: []int64{1, 2},
|
||||
Revisions: []string{"rev1", "rev2"},
|
||||
}
|
||||
|
||||
|
||||
@@ -144,8 +144,8 @@ func mergeLogStreams(streams []chan logEntry, bufferingDuration time.Duration) c
|
||||
|
||||
_ = send(true)
|
||||
|
||||
close(merged)
|
||||
ticker.Stop()
|
||||
close(merged)
|
||||
}()
|
||||
return merged
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ func NewServer(
|
||||
scmRootCAPath string,
|
||||
allowedScmProviders []string,
|
||||
enableScmProviders bool,
|
||||
enableK8sEvent []string,
|
||||
) applicationset.ApplicationSetServiceServer {
|
||||
s := &Server{
|
||||
ns: namespace,
|
||||
@@ -103,7 +104,7 @@ func NewServer(
|
||||
projLister: projLister,
|
||||
settings: settings,
|
||||
projectLock: projectLock,
|
||||
auditLogger: argo.NewAuditLogger(namespace, kubeclientset, "argocd-server"),
|
||||
auditLogger: argo.NewAuditLogger(namespace, kubeclientset, "argocd-server", enableK8sEvent),
|
||||
enabledNamespaces: enabledNamespaces,
|
||||
GitSubmoduleEnabled: gitSubmoduleEnabled,
|
||||
EnableNewGitFileGlobbing: enableNewGitFileGlobbing,
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
apps "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned/fake"
|
||||
appinformer "github.com/argoproj/argo-cd/v2/pkg/client/informers/externalversions"
|
||||
"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
"github.com/argoproj/argo-cd/v2/util/assets"
|
||||
"github.com/argoproj/argo-cd/v2/util/db"
|
||||
"github.com/argoproj/argo-cd/v2/util/errors"
|
||||
@@ -34,6 +35,8 @@ const (
|
||||
fakeRepoURL = "https://git.com/repo.git"
|
||||
)
|
||||
|
||||
var testEnableEventList []string = argo.DefaultEnableEventList()
|
||||
|
||||
func fakeRepo() *appsv1.Repository {
|
||||
return &appsv1.Repository{
|
||||
Repo: fakeRepoURL,
|
||||
@@ -162,6 +165,7 @@ func newTestAppSetServerWithEnforcerConfigure(f func(*rbac.Enforcer), namespace
|
||||
"",
|
||||
[]string{},
|
||||
true,
|
||||
testEnableEventList,
|
||||
)
|
||||
return server.(*Server)
|
||||
}
|
||||
|
||||
@@ -33,6 +33,12 @@ const (
|
||||
DefaultIdleConnectionTimeout = 60 * time.Second
|
||||
DefaultMaxIdleConnections = 30
|
||||
|
||||
// HeaderArgoCDNamespace defines the namespace of the
|
||||
// argo control plane to be passed to the extension handler.
|
||||
// Example:
|
||||
// Argocd-Namespace: "namespace"
|
||||
HeaderArgoCDNamespace = "Argocd-Namespace"
|
||||
|
||||
// HeaderArgoCDApplicationName defines the name of the
|
||||
// expected application header to be passed to the extension
|
||||
// handler. The header value must follow the format:
|
||||
@@ -333,6 +339,7 @@ type RbacEnforcer interface {
|
||||
// and handling proxy extensions.
|
||||
type Manager struct {
|
||||
log *log.Entry
|
||||
namespace string
|
||||
settings SettingsGetter
|
||||
application ApplicationGetter
|
||||
project ProjectGetter
|
||||
@@ -355,9 +362,10 @@ type ExtensionMetricsRegistry interface {
|
||||
}
|
||||
|
||||
// NewManager will initialize a new manager.
|
||||
func NewManager(log *log.Entry, sg SettingsGetter, ag ApplicationGetter, pg ProjectGetter, rbac RbacEnforcer, ug UserGetter) *Manager {
|
||||
func NewManager(log *log.Entry, namespace string, sg SettingsGetter, ag ApplicationGetter, pg ProjectGetter, rbac RbacEnforcer, ug UserGetter) *Manager {
|
||||
return &Manager{
|
||||
log: log,
|
||||
namespace: namespace,
|
||||
settings: sg,
|
||||
application: ag,
|
||||
project: pg,
|
||||
@@ -402,28 +410,46 @@ func proxyKey(extName, cName, cServer string) ProxyKey {
|
||||
}
|
||||
|
||||
func parseAndValidateConfig(s *settings.ArgoCDSettings) (*ExtensionConfigs, error) {
|
||||
if s.ExtensionConfig == "" {
|
||||
if len(s.ExtensionConfig) == 0 {
|
||||
return nil, fmt.Errorf("no extensions configurations found")
|
||||
}
|
||||
|
||||
extConfigMap := map[string]interface{}{}
|
||||
err := yaml.Unmarshal([]byte(s.ExtensionConfig), &extConfigMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid extension config: %w", err)
|
||||
}
|
||||
|
||||
parsedExtConfig := settings.ReplaceMapSecrets(extConfigMap, s.Secrets)
|
||||
parsedExtConfigBytes, err := yaml.Marshal(parsedExtConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling parsed extension config: %w", err)
|
||||
}
|
||||
|
||||
configs := ExtensionConfigs{}
|
||||
err = yaml.Unmarshal(parsedExtConfigBytes, &configs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid parsed extension config: %w", err)
|
||||
for extName, extConfig := range s.ExtensionConfig {
|
||||
extConfigMap := map[string]interface{}{}
|
||||
err := yaml.Unmarshal([]byte(extConfig), &extConfigMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid extension config: %w", err)
|
||||
}
|
||||
|
||||
parsedExtConfig := settings.ReplaceMapSecrets(extConfigMap, s.Secrets)
|
||||
parsedExtConfigBytes, err := yaml.Marshal(parsedExtConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error marshaling parsed extension config: %w", err)
|
||||
}
|
||||
// empty extName means that this is the main configuration defined by
|
||||
// the 'extension.config' configmap key
|
||||
if extName == "" {
|
||||
mainConfig := ExtensionConfigs{}
|
||||
err = yaml.Unmarshal(parsedExtConfigBytes, &mainConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid parsed extension config: %w", err)
|
||||
}
|
||||
configs.Extensions = append(configs.Extensions, mainConfig.Extensions...)
|
||||
} else {
|
||||
backendConfig := BackendConfig{}
|
||||
err = yaml.Unmarshal(parsedExtConfigBytes, &backendConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid parsed backend extension config for extension %s: %w", extName, err)
|
||||
}
|
||||
ext := ExtensionConfig{
|
||||
Name: extName,
|
||||
Backend: backendConfig,
|
||||
}
|
||||
configs.Extensions = append(configs.Extensions, ext)
|
||||
}
|
||||
}
|
||||
err = validateConfigs(&configs)
|
||||
err := validateConfigs(&configs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validation error: %w", err)
|
||||
}
|
||||
@@ -538,7 +564,7 @@ func (m *Manager) RegisterExtensions() error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting settings: %w", err)
|
||||
}
|
||||
if settings.ExtensionConfig == "" {
|
||||
if len(settings.ExtensionConfig) == 0 {
|
||||
m.log.Infof("No extensions configured.")
|
||||
return nil
|
||||
}
|
||||
@@ -740,7 +766,7 @@ func (m *Manager) CallExtension() func(http.ResponseWriter, *http.Request) {
|
||||
|
||||
user := m.userGetter.GetUser(r.Context())
|
||||
groups := m.userGetter.GetGroups(r.Context())
|
||||
prepareRequest(r, extName, app, user, groups)
|
||||
prepareRequest(r, m.namespace, extName, app, user, groups)
|
||||
m.log.Debugf("proxing request for extension %q", extName)
|
||||
// httpsnoop package is used to properly wrap the responseWriter
|
||||
// and avoid optional intefaces issue:
|
||||
@@ -763,11 +789,13 @@ func registerMetrics(extName string, metrics httpsnoop.Metrics, extensionMetrics
|
||||
// the Argo CD extension API section from it. It provides additional information to
|
||||
// the backend service appending them in the outgoing request headers. The appended
|
||||
// headers are:
|
||||
// - Control plane namespace
|
||||
// - Cluster destination name
|
||||
// - Cluster destination server
|
||||
// - Argo CD authenticated username
|
||||
func prepareRequest(r *http.Request, extName string, app *v1alpha1.Application, username string, groups []string) {
|
||||
func prepareRequest(r *http.Request, namespace string, extName string, app *v1alpha1.Application, username string, groups []string) {
|
||||
r.URL.Path = strings.TrimPrefix(r.URL.Path, fmt.Sprintf("%s/%s", URLPrefix, extName))
|
||||
r.Header.Set(HeaderArgoCDNamespace, namespace)
|
||||
if app.Spec.Destination.Name != "" {
|
||||
r.Header.Set(HeaderArgoCDTargetClusterName, app.Spec.Destination.Name)
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ func TestRegisterExtensions(t *testing.T) {
|
||||
|
||||
logger, _ := test.NewNullLogger()
|
||||
logEntry := logger.WithContext(context.Background())
|
||||
m := extension.NewManager(logEntry, settMock, nil, nil, nil, nil)
|
||||
m := extension.NewManager(logEntry, "", settMock, nil, nil, nil, nil)
|
||||
|
||||
return &fixture{
|
||||
settingsGetterMock: settMock,
|
||||
@@ -162,12 +162,16 @@ func TestRegisterExtensions(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := setup()
|
||||
settings := &settings.ArgoCDSettings{
|
||||
ExtensionConfig: getExtensionConfigString(),
|
||||
ExtensionConfig: map[string]string{
|
||||
"": getExtensionConfigString(),
|
||||
"another-ext": getSingleExtensionConfigString(),
|
||||
},
|
||||
}
|
||||
f.settingsGetterMock.On("Get", mock.Anything).Return(settings, nil)
|
||||
expectedProxyRegistries := []string{
|
||||
"external-backend",
|
||||
"some-backend",
|
||||
"another-ext",
|
||||
}
|
||||
|
||||
// when
|
||||
@@ -223,7 +227,9 @@ func TestRegisterExtensions(t *testing.T) {
|
||||
t.Parallel()
|
||||
f := setup()
|
||||
settings := &settings.ArgoCDSettings{
|
||||
ExtensionConfig: tc.configYaml,
|
||||
ExtensionConfig: map[string]string{
|
||||
"": tc.configYaml,
|
||||
},
|
||||
}
|
||||
f.settingsGetterMock.On("Get", mock.Anything).Return(settings, nil)
|
||||
|
||||
@@ -248,6 +254,7 @@ func TestCallExtension(t *testing.T) {
|
||||
userMock *mocks.UserGetter
|
||||
manager *extension.Manager
|
||||
}
|
||||
defaultServerNamespace := "control-plane-ns"
|
||||
defaultProjectName := "project-name"
|
||||
|
||||
setup := func() *fixture {
|
||||
@@ -260,7 +267,7 @@ func TestCallExtension(t *testing.T) {
|
||||
|
||||
logger, _ := test.NewNullLogger()
|
||||
logEntry := logger.WithContext(context.Background())
|
||||
m := extension.NewManager(logEntry, settMock, appMock, projMock, rbacMock, userMock)
|
||||
m := extension.NewManager(logEntry, defaultServerNamespace, settMock, appMock, projMock, rbacMock, userMock)
|
||||
m.AddMetricsRegistry(metricsMock)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
@@ -361,8 +368,10 @@ func TestCallExtension(t *testing.T) {
|
||||
secrets["extension.auth.header2"] = "Bearer another-bearer-token"
|
||||
|
||||
settings := &settings.ArgoCDSettings{
|
||||
ExtensionConfig: configYaml,
|
||||
Secrets: secrets,
|
||||
ExtensionConfig: map[string]string{
|
||||
"": configYaml,
|
||||
},
|
||||
Secrets: secrets,
|
||||
}
|
||||
f.settingsGetterMock.On("Get", mock.Anything).Return(settings, nil)
|
||||
}
|
||||
@@ -444,6 +453,7 @@ func TestCallExtension(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
actual := strings.TrimSuffix(string(body), "\n")
|
||||
assert.Equal(t, backendResponse, actual)
|
||||
assert.Equal(t, defaultServerNamespace, resp.Header.Get(extension.HeaderArgoCDNamespace))
|
||||
assert.Equal(t, clusterURL, resp.Header.Get(extension.HeaderArgoCDTargetClusterURL))
|
||||
assert.Equal(t, "Bearer some-bearer-token", resp.Header.Get("Authorization"))
|
||||
assert.Equal(t, "some-user", resp.Header.Get(extension.HeaderArgoCDUsername))
|
||||
@@ -794,6 +804,17 @@ extensions:
|
||||
`
|
||||
}
|
||||
|
||||
func getSingleExtensionConfigString() string {
|
||||
return `
|
||||
connectionTimeout: 10s
|
||||
keepAlive: 11s
|
||||
idleConnectionTimeout: 12s
|
||||
maxIdleConnections: 30
|
||||
services:
|
||||
- url: http://localhost:7777
|
||||
`
|
||||
}
|
||||
|
||||
func getExtensionConfigNoService() string {
|
||||
return `
|
||||
extensions:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user