feat: AppSet Progressive Rollouts with RollingSync (#9437) (#10048)

Signed-off-by: Matt Groot <mgroot@indeed.com>

Signed-off-by: Matt Groot <mgroot@indeed.com>
Co-authored-by: Matt Groot <mgroot@indeed.com>
This commit is contained in:
wmgroot 2022-12-16 16:11:58 -06:00 committed by GitHub
parent c6fa942e94
commit ec4e2f26d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 5857 additions and 726 deletions

View file

@ -105,6 +105,7 @@ Currently, the following organizations are **officially** using Argo CD:
1. [Ibotta](https://home.ibotta.com)
1. [IITS-Consulting](https://iits-consulting.de)
1. [imaware](https://imaware.health)
1. [Indeed](https://indeed.com)
1. [Index Exchange](https://www.indexexchange.com/)
1. [InsideBoard](https://www.insideboard.com)
1. [Intuit](https://www.intuit.com/)

View file

@ -25,6 +25,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
@ -69,6 +70,8 @@ type ApplicationSetReconciler struct {
KubeClientset kubernetes.Interface
utils.Policy
utils.Renderer
EnableProgressiveRollouts bool
}
// +kubebuilder:rbac:groups=argoproj.io,resources=applicationsets,verbs=get;list;watch;create;update;patch;delete
@ -134,6 +137,27 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
return ctrl.Result{RequeueAfter: ReconcileRequeueOnValidationError}, nil
}
// appMap is a name->app collection of Applications in this ApplicationSet.
appMap := map[string]argov1alpha1.Application{}
// appSyncMap tracks which apps will be synced during this reconciliation.
appSyncMap := map[string]bool{}
if r.EnableProgressiveRollouts && applicationSetInfo.Spec.Strategy != nil {
applications, err := r.getCurrentApplications(ctx, applicationSetInfo)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to get current applications for application set: %w", err)
}
for _, app := range applications {
appMap[app.Name] = app
}
appSyncMap, err = r.performProgressiveRollouts(ctx, applicationSetInfo, applications, desiredApplications, appMap)
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to perform progressive rollouts reconciliation for application set: %w", err)
}
}
var validApps []argov1alpha1.Application
for i := range desiredApplications {
if validateErrors[i] == nil {
@ -162,6 +186,26 @@ func (r *ApplicationSetReconciler) Reconcile(ctx context.Context, req ctrl.Reque
)
}
if r.EnableProgressiveRollouts {
// trigger appropriate application syncs if RollingSync strategy is enabled
if progressiveRolloutStrategyEnabled(&applicationSetInfo, "RollingSync") {
validApps, err = r.syncValidApplications(ctx, &applicationSetInfo, appSyncMap, appMap, validApps)
if err != nil {
_ = r.setApplicationSetStatusCondition(ctx,
&applicationSetInfo,
argov1alpha1.ApplicationSetCondition{
Type: argov1alpha1.ApplicationSetConditionErrorOccurred,
Message: err.Error(),
Reason: argov1alpha1.ApplicationSetReasonSyncApplicationError,
Status: argov1alpha1.ApplicationSetConditionStatusTrue,
}, parametersGenerated,
)
return ctrl.Result{}, err
}
}
}
if r.Policy.Update() {
err = r.createOrUpdateInCluster(ctx, applicationSetInfo, validApps)
if err != nil {
@ -528,6 +572,11 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
// Copy only the Application/ObjectMeta fields that are significant, from the generatedApp
found.Spec = generatedApp.Spec
// allow setting the Operation field to trigger a sync operation on an Application
if generatedApp.Operation != nil {
found.Operation = generatedApp.Operation
}
// Preserve specially treated argo cd annotations:
// * https://github.com/argoproj/applicationset/issues/180
// * https://github.com/argoproj/argo-cd/issues/10500
@ -726,4 +775,523 @@ func (r *ApplicationSetReconciler) removeFinalizerOnInvalidDestination(ctx conte
return nil
}
func (r *ApplicationSetReconciler) performProgressiveRollouts(ctx context.Context, appset argov1alpha1.ApplicationSet, applications []argov1alpha1.Application, desiredApplications []argov1alpha1.Application, appMap map[string]argov1alpha1.Application) (map[string]bool, error) {
_, err := r.updateApplicationSetApplicationStatus(ctx, &appset, applications)
if err != nil {
return nil, fmt.Errorf("failed to update applicationset app status: %w", err)
}
appDependencyList, appStepMap, err := r.buildAppDependencyList(ctx, appset, desiredApplications)
if err != nil {
return nil, fmt.Errorf("failed to build app dependency list: %w", err)
}
appSyncMap, err := r.buildAppSyncMap(ctx, appset, appDependencyList, appMap)
if err != nil {
return nil, fmt.Errorf("failed to build app sync map: %w", err)
}
log.Infof("appSyncMap: %+v", appSyncMap)
_, err = r.updateApplicationSetApplicationStatusProgress(ctx, &appset, appSyncMap, appStepMap, appMap)
if err != nil {
return nil, fmt.Errorf("failed to update applicationset application status progress: %w", err)
}
_, err = r.updateApplicationSetApplicationStatusConditions(ctx, &appset)
if err != nil {
return nil, fmt.Errorf("failed to update applicationset application status conditions: %w", err)
}
return appSyncMap, nil
}
// this list tracks which Applications belong to each RollingUpdate step
func (r *ApplicationSetReconciler) buildAppDependencyList(ctx context.Context, applicationSet argov1alpha1.ApplicationSet, applications []argov1alpha1.Application) ([][]string, map[string]int, error) {
if applicationSet.Spec.Strategy == nil || applicationSet.Spec.Strategy.Type == "" || applicationSet.Spec.Strategy.Type == "AllAtOnce" {
return [][]string{}, map[string]int{}, nil
}
steps := []argov1alpha1.ApplicationSetRolloutStep{}
if progressiveRolloutStrategyEnabled(&applicationSet, "RollingSync") {
steps = applicationSet.Spec.Strategy.RollingSync.Steps
}
appDependencyList := make([][]string, 0)
for range steps {
appDependencyList = append(appDependencyList, make([]string, 0))
}
appStepMap := map[string]int{}
// use applicationLabelSelectors to filter generated Applications into steps and status by name
for _, app := range applications {
for i, step := range steps {
selected := true // default to true, assuming the current Application is a match for the given step matchExpression
allNotInMatched := true // needed to support correct AND behavior between multiple NotIn MatchExpressions
notInUsed := false // since we default to allNotInMatched == true, track whether a NotIn expression was actually used
for _, matchExpression := range step.MatchExpressions {
if matchExpression.Operator == "In" {
if val, ok := app.Labels[matchExpression.Key]; ok {
valueMatched := labelMatchedExpression(val, matchExpression)
if !valueMatched { // none of the matchExpression values was a match with the Application'ss labels
selected = false
break
}
} else {
selected = false // no matching label key with In means this Application will not be included in the current step
break
}
} else if matchExpression.Operator == "NotIn" {
notInUsed = true // a NotIn selector was used in this matchExpression
if val, ok := app.Labels[matchExpression.Key]; ok {
valueMatched := labelMatchedExpression(val, matchExpression)
if !valueMatched { // none of the matchExpression values was a match with the Application's labels
allNotInMatched = false
}
} else {
allNotInMatched = false // no matching label key with NotIn means this Application may still be included in the current step
}
} else { // handle invalid operator selection
log.Warnf("skipping AppSet rollingUpdate step Application selection for %q, invalid matchExpression operator provided: %q ", applicationSet.Name, matchExpression.Operator)
selected = false
break
}
}
if notInUsed && allNotInMatched { // check if all NotIn Expressions matched, if so exclude this Application
selected = false
}
if selected {
appDependencyList[i] = append(appDependencyList[i], app.Name)
if val, ok := appStepMap[app.Name]; ok {
log.Warnf("AppSet '%v' has a invalid matchExpression that selects Application '%v' label twice, in steps %v and %v", applicationSet.Name, app.Name, val+1, i+1)
} else {
appStepMap[app.Name] = i
}
}
}
}
return appDependencyList, appStepMap, nil
}
func labelMatchedExpression(val string, matchExpression argov1alpha1.ApplicationMatchExpression) bool {
valueMatched := false
for _, value := range matchExpression.Values {
if val == value {
valueMatched = true
break
}
}
return valueMatched
}
// this map is used to determine which stage of Applications are ready to be updated in the reconciler loop
func (r *ApplicationSetReconciler) buildAppSyncMap(ctx context.Context, applicationSet argov1alpha1.ApplicationSet, appDependencyList [][]string, appMap map[string]argov1alpha1.Application) (map[string]bool, error) {
appSyncMap := map[string]bool{}
syncEnabled := true
// healthy stages and the first non-healthy stage should have sync enabled
// every stage after should have sync disabled
for i := range appDependencyList {
// set the syncEnabled boolean for every Application in the current step
for _, appName := range appDependencyList[i] {
appSyncMap[appName] = syncEnabled
}
// detect if we need to halt before progressing to the next step
for _, appName := range appDependencyList[i] {
idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, appName)
if idx == -1 {
// no Application status found, likely because the Application is being newly created
syncEnabled = false
break
}
appStatus := applicationSet.Status.ApplicationStatus[idx]
if app, ok := appMap[appName]; ok {
syncEnabled = appSyncEnabledForNextStep(&applicationSet, app, appStatus)
if !syncEnabled {
break
}
} else {
// application name not found in the list of applications managed by this ApplicationSet, maybe because it's being deleted
syncEnabled = false
break
}
}
}
return appSyncMap, nil
}
func appSyncEnabledForNextStep(appset *argov1alpha1.ApplicationSet, app argov1alpha1.Application, appStatus argov1alpha1.ApplicationSetApplicationStatus) bool {
if progressiveRolloutStrategyEnabled(appset, "RollingSync") {
// we still need to complete the current step if the Application is not yet Healthy or there are still pending Application changes
return isApplicationHealthy(app) && appStatus.Status == "Healthy"
}
return true
}
func progressiveRolloutStrategyEnabled(appset *argov1alpha1.ApplicationSet, strategyType string) bool {
if appset.Spec.Strategy == nil || appset.Spec.Strategy.Type != strategyType {
return false
}
if strategyType == "RollingSync" && appset.Spec.Strategy.RollingSync == nil {
return false
}
return true
}
func isApplicationHealthy(app argov1alpha1.Application) bool {
healthStatusString, syncStatusString, operationPhaseString := statusStrings(app)
if healthStatusString == "Healthy" && syncStatusString != "OutOfSync" && (operationPhaseString == "Succeeded" || operationPhaseString == "") {
return true
}
return false
}
func statusStrings(app argov1alpha1.Application) (string, string, string) {
healthStatusString := string(app.Status.Health.Status)
syncStatusString := string(app.Status.Sync.Status)
operationPhaseString := ""
if app.Status.OperationState != nil {
operationPhaseString = string(app.Status.OperationState.Phase)
}
return healthStatusString, syncStatusString, operationPhaseString
}
// check the status of each Application's status and promote Applications to the next status if needed
func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatus(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet, applications []argov1alpha1.Application) ([]argov1alpha1.ApplicationSetApplicationStatus, error) {
now := metav1.Now()
appStatuses := make([]argov1alpha1.ApplicationSetApplicationStatus, 0, len(applications))
for _, app := range applications {
healthStatusString, syncStatusString, operationPhaseString := statusStrings(app)
idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, app.Name)
if idx == -1 {
// AppStatus not found, set default status of "Waiting"
appStatuses = append(appStatuses, argov1alpha1.ApplicationSetApplicationStatus{
Application: app.Name,
LastTransitionTime: &now,
Message: "No Application status found, defaulting status to Waiting.",
Status: "Waiting",
})
break
}
// we have an existing AppStatus
currentAppStatus := applicationSet.Status.ApplicationStatus[idx]
appOutdated := false
if progressiveRolloutStrategyEnabled(applicationSet, "RollingSync") {
appOutdated = syncStatusString == "OutOfSync"
}
if appOutdated && currentAppStatus.Status != "Waiting" && currentAppStatus.Status != "Pending" {
log.Infof("Application %v is outdated, updating its ApplicationSet status to Waiting", app.Name)
currentAppStatus.LastTransitionTime = &now
currentAppStatus.Status = "Waiting"
currentAppStatus.Message = "Application has pending changes, setting status to Waiting."
}
if currentAppStatus.Status == "Pending" {
if healthStatusString == "Progressing" || operationPhaseString == "Running" {
log.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."
}
}
if currentAppStatus.Status == "Waiting" && isApplicationHealthy(app) {
log.Infof("Application %v is already synced and healthy, updating its ApplicationSet status to Healthy", app.Name)
currentAppStatus.LastTransitionTime = &now
currentAppStatus.Status = healthStatusString
currentAppStatus.Message = "Application resource is already Healthy, updating status from Waiting to Healthy."
}
if currentAppStatus.Status == "Progressing" && isApplicationHealthy(app) {
log.Infof("Application %v has completed Progressing status, updating its ApplicationSet status to Healthy", app.Name)
currentAppStatus.LastTransitionTime = &now
currentAppStatus.Status = healthStatusString
currentAppStatus.Message = "Application resource became Healthy, updating status from Progressing to Healthy."
}
appStatuses = append(appStatuses, currentAppStatus)
}
err := r.setAppSetApplicationStatus(ctx, applicationSet, appStatuses)
if err != nil {
return nil, fmt.Errorf("failed to set AppSet application statuses: %w", err)
}
return appStatuses, nil
}
// check Applications that are in Waiting status and promote them to Pending if needed
func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusProgress(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appStepMap map[string]int, appMap map[string]argov1alpha1.Application) ([]argov1alpha1.ApplicationSetApplicationStatus, error) {
now := metav1.Now()
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" {
updateCountMap := []int{}
totalCountMap := []int{}
length := 0
if progressiveRolloutStrategyEnabled(applicationSet, "RollingSync") {
length = len(applicationSet.Spec.Strategy.RollingSync.Steps)
}
for s := 0; s < length; s++ {
updateCountMap = append(updateCountMap, 0)
totalCountMap = append(totalCountMap, 0)
}
// populate updateCountMap with counts of existing Pending and Progressing Applications
for _, appStatus := range applicationSet.Status.ApplicationStatus {
totalCountMap[appStepMap[appStatus.Application]] += 1
if progressiveRolloutStrategyEnabled(applicationSet, "RollingSync") {
if appStatus.Status == "Pending" || appStatus.Status == "Progressing" {
updateCountMap[appStepMap[appStatus.Application]] += 1
}
}
}
for _, appStatus := range applicationSet.Status.ApplicationStatus {
maxUpdateAllowed := true
maxUpdate := &intstr.IntOrString{}
if progressiveRolloutStrategyEnabled(applicationSet, "RollingSync") {
maxUpdate = applicationSet.Spec.Strategy.RollingSync.Steps[appStepMap[appStatus.Application]].MaxUpdate
}
// by default allow all applications to update if maxUpdate is unset
if maxUpdate != nil {
maxUpdateVal, err := intstr.GetScaledValueFromIntOrPercent(maxUpdate, totalCountMap[appStepMap[appStatus.Application]], false)
if err != nil {
log.Warnf("AppSet '%v' has a invalid maxUpdate value '%+v', ignoring maxUpdate logic for this step: %v", applicationSet.Name, maxUpdate, err)
}
// ensure that percentage values greater than 0% always result in at least 1 Application being selected
if maxUpdate.Type == intstr.String && maxUpdate.StrVal != "0%" && maxUpdateVal < 1 {
maxUpdateVal = 1
}
if updateCountMap[appStepMap[appStatus.Application]] >= maxUpdateVal {
maxUpdateAllowed = false
log.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)
}
}
if appStatus.Status == "Waiting" && appSyncMap[appStatus.Application] && maxUpdateAllowed {
log.Infof("Application %v moved to Pending status, watching for the Application to start Progressing", appStatus.Application)
appStatus.LastTransitionTime = &now
appStatus.Status = "Pending"
appStatus.Message = "Application moved to Pending status, watching for the Application resource to start Progressing."
updateCountMap[appStepMap[appStatus.Application]] += 1
}
appStatuses = append(appStatuses, appStatus)
}
}
err := r.setAppSetApplicationStatus(ctx, applicationSet, appStatuses)
if err != nil {
return nil, fmt.Errorf("failed to set AppSet app status: %w", err)
}
return appStatuses, nil
}
func (r *ApplicationSetReconciler) updateApplicationSetApplicationStatusConditions(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet) ([]argov1alpha1.ApplicationSetCondition, error) {
appSetProgressing := false
for _, appStatus := range applicationSet.Status.ApplicationStatus {
if appStatus.Status != "Healthy" {
appSetProgressing = true
break
}
}
appSetConditionProgressing := false
for _, appSetCondition := range applicationSet.Status.Conditions {
if appSetCondition.Type == argov1alpha1.ApplicationSetConditionRolloutProgressing && appSetCondition.Status == argov1alpha1.ApplicationSetConditionStatusTrue {
appSetConditionProgressing = true
break
}
}
if appSetProgressing && !appSetConditionProgressing {
_ = r.setApplicationSetStatusCondition(ctx,
applicationSet,
argov1alpha1.ApplicationSetCondition{
Type: argov1alpha1.ApplicationSetConditionRolloutProgressing,
Message: "ApplicationSet Rollout Rollout started",
Reason: argov1alpha1.ApplicationSetReasonApplicationSetModified,
Status: argov1alpha1.ApplicationSetConditionStatusTrue,
}, false,
)
} else if !appSetProgressing && appSetConditionProgressing {
_ = r.setApplicationSetStatusCondition(ctx,
applicationSet,
argov1alpha1.ApplicationSetCondition{
Type: argov1alpha1.ApplicationSetConditionRolloutProgressing,
Message: "ApplicationSet Rollout Rollout complete",
Reason: argov1alpha1.ApplicationSetReasonApplicationSetRolloutComplete,
Status: argov1alpha1.ApplicationSetConditionStatusFalse,
}, false,
)
}
return applicationSet.Status.Conditions, nil
}
func findApplicationStatusIndex(appStatuses []argov1alpha1.ApplicationSetApplicationStatus, application string) int {
for i := range appStatuses {
if appStatuses[i].Application == application {
return i
}
}
return -1
}
// setApplicationSetApplicationStatus updates the ApplicatonSet's status field
// with any new/changed Application statuses.
func (r *ApplicationSetReconciler) setAppSetApplicationStatus(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet, applicationStatuses []argov1alpha1.ApplicationSetApplicationStatus) error {
needToUpdateStatus := false
for i := range applicationStatuses {
appStatus := applicationStatuses[i]
idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, appStatus.Application)
if idx == -1 {
needToUpdateStatus = true
break
}
currentStatus := applicationSet.Status.ApplicationStatus[idx]
if currentStatus.Message != appStatus.Message || currentStatus.Status != appStatus.Status {
needToUpdateStatus = true
break
}
}
if needToUpdateStatus {
// fetch updated Application Set object before updating it
namespacedName := types.NamespacedName{Namespace: applicationSet.Namespace, Name: applicationSet.Name}
if err := r.Get(ctx, namespacedName, applicationSet); err != nil {
if client.IgnoreNotFound(err) != nil {
return nil
}
return fmt.Errorf("error fetching updated application set: %v", err)
}
for i := range applicationStatuses {
applicationSet.Status.SetApplicationStatus(applicationStatuses[i])
}
// Update the newly fetched object with new set of ApplicationStatus
err := r.Client.Status().Update(ctx, applicationSet)
if err != nil {
log.Errorf("unable to set application set status: %v", err)
return fmt.Errorf("unable to set application set status: %v", err)
}
if err := r.Get(ctx, namespacedName, applicationSet); err != nil {
if client.IgnoreNotFound(err) != nil {
return nil
}
return fmt.Errorf("error fetching updated application set: %v", err)
}
}
return nil
}
func (r *ApplicationSetReconciler) syncValidApplications(ctx context.Context, applicationSet *argov1alpha1.ApplicationSet, appSyncMap map[string]bool, appMap map[string]argov1alpha1.Application, validApps []argov1alpha1.Application) ([]argov1alpha1.Application, error) {
rolloutApps := []argov1alpha1.Application{}
for i := range validApps {
pruneEnabled := false
// ensure that Applications generated with RollingSync do not have an automated sync policy, since the AppSet controller will handle triggering the sync operation instead
if validApps[i].Spec.SyncPolicy != nil && validApps[i].Spec.SyncPolicy.Automated != nil {
pruneEnabled = validApps[i].Spec.SyncPolicy.Automated.Prune
validApps[i].Spec.SyncPolicy.Automated = nil
}
appSetStatusPending := false
idx := findApplicationStatusIndex(applicationSet.Status.ApplicationStatus, validApps[i].Name)
if idx > -1 && applicationSet.Status.ApplicationStatus[idx].Status == "Pending" {
// only trigger a sync for Applications that are in Pending status, since this is governed by maxUpdate
appSetStatusPending = true
}
// check appSyncMap to determine which Applications are ready to be updated and which should be skipped
if appSyncMap[validApps[i].Name] && appMap[validApps[i].Name].Status.Sync.Status == "OutOfSync" && appSetStatusPending {
log.Infof("triggering sync for application: %v, prune enabled: %v", validApps[i].Name, pruneEnabled)
validApps[i], _ = syncApplication(validApps[i], pruneEnabled)
}
rolloutApps = append(rolloutApps, validApps[i])
}
return rolloutApps, nil
}
// used by the RollingSync Progressive Rollout strategy to trigger a sync of a particular Application resource
func syncApplication(application argov1alpha1.Application, prune bool) (argov1alpha1.Application, error) {
operation := argov1alpha1.Operation{
InitiatedBy: argov1alpha1.OperationInitiator{
Username: "applicationset-controller",
Automated: true,
},
Info: []*argov1alpha1.Info{
{
Name: "Reason",
Value: "ApplicationSet RollingSync triggered a sync of this Application resource.",
},
},
Sync: &argov1alpha1.SyncOperation{},
}
if application.Spec.SyncPolicy != nil {
if application.Spec.SyncPolicy.Retry != nil {
operation.Retry = *application.Spec.SyncPolicy.Retry
}
if application.Spec.SyncPolicy.SyncOptions != nil {
operation.Sync.SyncOptions = application.Spec.SyncPolicy.SyncOptions
}
operation.Sync.Prune = prune
}
application.Operation = &operation
return application, nil
}
var _ handler.EventHandler = &clusterSecretEventHandler{}

File diff suppressed because it is too large Load diff

View file

@ -4362,6 +4362,24 @@
"type": "object",
"title": "Generic (empty) response for GPG public key CRUD requests"
},
"intstrIntOrString": {
"description": "+protobuf=true\n+protobuf.options.(gogoproto.goproto_stringer)=false\n+k8s:openapi-gen=true",
"type": "object",
"title": "IntOrString is a type that can hold an int32 or a string. When used in\nJSON or YAML marshalling and unmarshalling, it produces or consumes the\ninner type. This allows you to have, for example, a JSON field that can\naccept a name or number.\nTODO: Rename to Int32OrString",
"properties": {
"intVal": {
"type": "integer",
"format": "int32"
},
"strVal": {
"type": "string"
},
"type": {
"type": "string",
"format": "int64"
}
}
},
"notificationService": {
"type": "object",
"properties": {
@ -5600,6 +5618,23 @@
}
}
},
"v1alpha1ApplicationMatchExpression": {
"type": "object",
"properties": {
"key": {
"type": "string"
},
"operator": {
"type": "string"
},
"values": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"v1alpha1ApplicationSet": {
"type": "object",
"title": "ApplicationSet is a set of Application resources\n+genclient\n+genclient:noStatus\n+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n+kubebuilder:resource:path=applicationsets,shortName=appset;appsets\n+kubebuilder:subresource:status",
@ -5615,6 +5650,27 @@
}
}
},
"v1alpha1ApplicationSetApplicationStatus": {
"type": "object",
"title": "ApplicationSetApplicationStatus contains details about each Application managed by the ApplicationSet",
"properties": {
"application": {
"type": "string",
"title": "Application contains the name of the Application resource"
},
"lastTransitionTime": {
"$ref": "#/definitions/v1Time"
},
"message": {
"type": "string",
"title": "Message contains human-readable message indicating details about the status"
},
"status": {
"type": "string",
"title": "Status contains the AppSet's perceived status of the managed Application resource: (Waiting, Pending, Progressing, Healthy)"
}
}
},
"v1alpha1ApplicationSetCondition": {
"type": "object",
"title": "ApplicationSetCondition contains details about an applicationset condition, which is usally an error or warning",
@ -5721,6 +5777,31 @@
}
}
},
"v1alpha1ApplicationSetRolloutStep": {
"type": "object",
"properties": {
"matchExpressions": {
"type": "array",
"items": {
"$ref": "#/definitions/v1alpha1ApplicationMatchExpression"
}
},
"maxUpdate": {
"$ref": "#/definitions/intstrIntOrString"
}
}
},
"v1alpha1ApplicationSetRolloutStrategy": {
"type": "object",
"properties": {
"steps": {
"type": "array",
"items": {
"$ref": "#/definitions/v1alpha1ApplicationSetRolloutStep"
}
}
}
},
"v1alpha1ApplicationSetSpec": {
"description": "ApplicationSetSpec represents a class of application set state.",
"type": "object",
@ -5734,6 +5815,9 @@
"goTemplate": {
"type": "boolean"
},
"strategy": {
"$ref": "#/definitions/v1alpha1ApplicationSetStrategy"
},
"syncPolicy": {
"$ref": "#/definitions/v1alpha1ApplicationSetSyncPolicy"
},
@ -5746,6 +5830,12 @@
"type": "object",
"title": "ApplicationSetStatus defines the observed state of ApplicationSet",
"properties": {
"applicationStatus": {
"type": "array",
"items": {
"$ref": "#/definitions/v1alpha1ApplicationSetApplicationStatus"
}
},
"conditions": {
"type": "array",
"title": "INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\nImportant: Run \"make\" to regenerate code after modifying this file",
@ -5755,6 +5845,18 @@
}
}
},
"v1alpha1ApplicationSetStrategy": {
"description": "ApplicationSetStrategy configures how generated Applications are updated in sequence.",
"type": "object",
"properties": {
"rollingSync": {
"$ref": "#/definitions/v1alpha1ApplicationSetRolloutStrategy"
},
"type": {
"type": "string"
}
}
},
"v1alpha1ApplicationSetSyncPolicy": {
"description": "ApplicationSetSyncPolicy configures how generated Applications will relate to their\nApplicationSet.",
"type": "object",

View file

@ -46,16 +46,17 @@ func getSubmoduleEnabled() bool {
func NewCommand() *cobra.Command {
var (
clientConfig clientcmd.ClientConfig
metricsAddr string
probeBindAddr string
webhookAddr string
enableLeaderElection bool
namespace string
argocdRepoServer string
policy string
debugLog bool
dryRun bool
clientConfig clientcmd.ClientConfig
metricsAddr string
probeBindAddr string
webhookAddr string
enableLeaderElection bool
namespace string
argocdRepoServer string
policy string
debugLog bool
dryRun bool
enableProgressiveRollouts bool
)
scheme := runtime.NewScheme()
_ = clientgoscheme.AddToScheme(scheme)
@ -168,15 +169,16 @@ func NewCommand() *cobra.Command {
go func() { errors.CheckError(askPassServer.Run(askpass.SocketPath)) }()
if err = (&controllers.ApplicationSetReconciler{
Generators: topLevelGenerators,
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Recorder: mgr.GetEventRecorderFor("applicationset-controller"),
Renderer: &utils.Render{},
Policy: policyObj,
ArgoAppClientset: appSetConfig,
KubeClientset: k8sClient,
ArgoDB: argoCDDB,
Generators: topLevelGenerators,
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Recorder: mgr.GetEventRecorderFor("applicationset-controller"),
Renderer: &utils.Render{},
Policy: policyObj,
ArgoAppClientset: appSetConfig,
KubeClientset: k8sClient,
ArgoDB: argoCDDB,
EnableProgressiveRollouts: enableProgressiveRollouts,
}).SetupWithManager(mgr); err != nil {
log.Error(err, "unable to create controller", "controller", "ApplicationSet")
os.Exit(1)
@ -205,6 +207,7 @@ func NewCommand() *cobra.Command {
command.Flags().StringVar(&cmdutil.LogFormat, "logformat", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_LOGFORMAT", "text"), "Set the logging format. One of: text|json")
command.Flags().StringVar(&cmdutil.LogLevel, "loglevel", env.StringFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_LOGLEVEL", "info"), "Set the logging level. One of: debug|info|warn|error")
command.Flags().BoolVar(&dryRun, "dry-run", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_CONTROLLER_DRY_RUN", false), "Enable dry run mode")
command.Flags().BoolVar(&enableProgressiveRollouts, "enable-progressive-rollouts", env.ParseBoolFromEnv("ARGOCD_APPLICATIONSET_ENABLE_PROGRESSIVE_ROLLOUTS", false), "Enable use of the experimental progressive rollouts feature.")
return &command
}

View file

@ -0,0 +1,109 @@
# Progressive Rollouts
!!! warning "Alpha Feature"
This is an experimental, alpha-quality feature that allows you to control the order in which the ApplicationSet controller will create or update the Applications owned by an ApplicationSet resource. It may be removed in future releases or modified in backwards-incompatible ways.
## Use Cases
The Progressive Rollouts feature set is intended to be light and flexible. The feature only interacts with the health of managed Applications. It is not intended to support direct integrations with other Rollout controllers (such as the native ReplicaSet controller or Argo Rollouts).
* Progressive Rollouts watch for the managed Application resources to become "Healthy" before proceeding to the next stage.
* Deployments, DaemonSets, StatefulSets, and [Argo Rollouts](https://argoproj.github.io/argo-rollouts/) are all supported, because the Application enters a "Progressing" state while pods are being rolled out. In fact, any resource with a health check that can report a "Progressing" status is supported.
* [Argo CD Resource Hooks](../../user-guide/resource_hooks.md) are supported. We recommend this approach for users that need advanced functionality when an Argo Rollout cannot be used, such as smoke testing after a DaemonSet change.
## Enabling Progressive Rollouts
As an experimental feature, progressive rollouts must be explicitly enabled, in one of these ways.
1. Pass `--enable-progressive-rollouts` to the ApplicationSet controller args.
1. Set `ARGOCD_APPLICATIONSET_ENABLE_PROGRESSIVE_ROLLOUTS=true` in the ApplicationSet controller environment variables.
1. Set `applicationsetcontroller.enable.progressive.rollouts: true` in the ArgoCD ConfigMap.
## Strategies
* AllAtOnce (default)
* RollingSync
### AllAtOnce
This default Application update behavior is unchanged from the original ApplicationSet implementation.
All Applications managed by the ApplicationSet resource are updated simultaneously when the ApplicationSet is updated.
### RollingSync
This update strategy allows you to group Applications by labels present on the generated Application resources.
When the ApplicationSet changes, the changes will be applied to each group of Application resources sequentially.
* Application groups are selected by `matchExpressions`.
* All `matchExpressions` must be true for an Application to be selected (multiple expressions match with AND behavior).
* The `In` and `NotIn` operators must match at least one value to be considered true (OR behavior).
* The `NotIn` operatorn has priority in the event that both a `NotIn` and `In` operator produce a match.
* All Applications in each group must become Healthy before the ApplicationSet controller will proceed to update the next group of Applications.
* The number of simultaneous Application updates in a group will not exceed its `maxUpdate` parameter (default is 100%, unbounded).
* RollingSync will capture external changes outside the ApplicationSet resource, since it relies on watching the OutOfSync status of the managed Applications.
* RollingSync will force all generated Applications to have autosync disabled. Warnings are printed in the applicationset-controller logs for any Application specs with an automated syncPolicy enabled.
* 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).
#### Example
The following example illustrates how to stage a progressive rollout over Applications with explicitly configured environment labels.
Once a change is pushed, the following will happen in order.
* All `env-dev` Applications will be updated simultaneously.
* The rollout will wait for all `env-qa` Applications to be manually synced via the `argocd` CLI or by clicking the Sync button in the UI.
* 10% of all `env-prod` Applications will be updated at a time until all `env-prod` Applications have been updated.
```
---
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: guestbook
spec:
generators:
- list:
elements:
- cluster: engineering-dev
url: https://1.2.3.4
env: env-dev
- cluster: engineering-qa
url: https://2.4.6.8
env: env-qa
- cluster: engineering-prod
url: https://9.8.7.6/
env: env-prod
strategy:
type: RollingSync
rollingSync:
steps:
- matchExpressions:
- key: env
operator: In
values:
- env-dev
#maxUpdate: 100% # if undefined, all applications matched are updated together (default is 100%)
- matchExpressions:
- key: env
operator: In
values:
- env-qa
maxUpdate: 0 # if 0, no matched applications will be updated
- matchExpressions:
- key: env
operator: In
values:
- env-prod
maxUpdate: 10% # maxUpdate supports both integer and percentage string values (rounds down, but floored at 1 Application for >0%)
goTemplate: true
template:
metadata:
name: '{{.cluster}}-guestbook'
labels:
env: '{{.env}}'
spec:
project: my-project
source:
repoURL: https://github.com/infra-team/cluster-deployments.git
targetRevision: HEAD
path: guestbook/{{.cluster}}
destination:
server: '{{.url}}'
namespace: guestbook
```

View file

@ -164,3 +164,5 @@ data:
applicationsetcontroller.dryrun: "false"
# Enable git submodule support
applicationsetcontroller.enable.git.submodule: "true"
# Enables use of the Progressive Rollouts capability
applicationsetcontroller.enable.progressive.rollouts: "false"

View file

@ -14245,6 +14245,37 @@ spec:
type: array
goTemplate:
type: boolean
strategy:
properties:
rollingSync:
properties:
steps:
items:
properties:
matchExpressions:
items:
properties:
key:
type: string
operator:
type: string
values:
items:
type: string
type: array
type: object
type: array
maxUpdate:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
type: object
type: array
type: object
type:
type: string
type: object
syncPolicy:
properties:
preserveResourcesOnDeletion:
@ -14699,6 +14730,24 @@ spec:
type: object
status:
properties:
applicationStatus:
items:
properties:
application:
type: string
lastTransitionTime:
format: date-time
type: string
message:
type: string
status:
type: string
required:
- application
- message
- status
type: object
type: array
conditions:
items:
properties:

View file

@ -10231,6 +10231,37 @@ spec:
type: array
goTemplate:
type: boolean
strategy:
properties:
rollingSync:
properties:
steps:
items:
properties:
matchExpressions:
items:
properties:
key:
type: string
operator:
type: string
values:
items:
type: string
type: array
type: object
type: array
maxUpdate:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
type: object
type: array
type: object
type:
type: string
type: object
syncPolicy:
properties:
preserveResourcesOnDeletion:
@ -10685,6 +10716,24 @@ spec:
type: object
status:
properties:
applicationStatus:
items:
properties:
application:
type: string
lastTransitionTime:
format: date-time
type: string
message:
type: string
status:
type: string
required:
- application
- message
- status
type: object
type: array
conditions:
items:
properties:

View file

@ -14245,6 +14245,37 @@ spec:
type: array
goTemplate:
type: boolean
strategy:
properties:
rollingSync:
properties:
steps:
items:
properties:
matchExpressions:
items:
properties:
key:
type: string
operator:
type: string
values:
items:
type: string
type: array
type: object
type: array
maxUpdate:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
type: object
type: array
type: object
type:
type: string
type: object
syncPolicy:
properties:
preserveResourcesOnDeletion:
@ -14699,6 +14730,24 @@ spec:
type: object
status:
properties:
applicationStatus:
items:
properties:
application:
type: string
lastTransitionTime:
format: date-time
type: string
message:
type: string
status:
type: string
required:
- application
- message
- status
type: object
type: array
conditions:
items:
properties:

View file

@ -14245,6 +14245,37 @@ spec:
type: array
goTemplate:
type: boolean
strategy:
properties:
rollingSync:
properties:
steps:
items:
properties:
matchExpressions:
items:
properties:
key:
type: string
operator:
type: string
values:
items:
type: string
type: array
type: object
type: array
maxUpdate:
anyOf:
- type: integer
- type: string
x-kubernetes-int-or-string: true
type: object
type: array
type: object
type:
type: string
type: object
syncPolicy:
properties:
preserveResourcesOnDeletion:
@ -14699,6 +14730,24 @@ spec:
type: object
status:
properties:
applicationStatus:
items:
properties:
application:
type: string
lastTransitionTime:
format: date-time
type: string
message:
type: string
status:
type: string
required:
- application
- message
- status
type: object
type: array
conditions:
items:
properties:

View file

@ -101,6 +101,7 @@ nav:
- operator-manual/applicationset/GoTemplate.md
- Controlling Resource Modification: operator-manual/applicationset/Controlling-Resource-Modification.md
- Application Pruning & Resource Deletion: operator-manual/applicationset/Application-Deletion.md
- Progressive Rollouts: operator-manual/applicationset/Progressive-Rollouts.md
- Server Configuration Parameters:
- operator-manual/server-commands/argocd-server.md
- operator-manual/server-commands/argocd-application-controller.md

View file

@ -7,7 +7,11 @@ API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/ap
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,AppProjectSpec,SignatureKeys
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,AppProjectSpec,SourceNamespaces
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,AppProjectSpec,SourceRepos
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationMatchExpression,Values
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationSetRolloutStep,MatchExpressions
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationSetRolloutStrategy,Steps
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationSetSpec,Generators
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationSetStatus,ApplicationStatus
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationSetStatus,Conditions
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationSetTemplateMeta,Finalizers
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationSourceHelm,FileParameters

View file

@ -25,6 +25,7 @@ import (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
// Utility struct for a reference to a secret key.
@ -52,6 +53,28 @@ type ApplicationSetSpec struct {
Generators []ApplicationSetGenerator `json:"generators" protobuf:"bytes,2,name=generators"`
Template ApplicationSetTemplate `json:"template" protobuf:"bytes,3,name=template"`
SyncPolicy *ApplicationSetSyncPolicy `json:"syncPolicy,omitempty" protobuf:"bytes,4,name=syncPolicy"`
Strategy *ApplicationSetStrategy `json:"strategy,omitempty" protobuf:"bytes,5,opt,name=strategy"`
}
// ApplicationSetStrategy configures how generated Applications are updated in sequence.
type ApplicationSetStrategy struct {
Type string `json:"type,omitempty" protobuf:"bytes,1,opt,name=type"`
RollingSync *ApplicationSetRolloutStrategy `json:"rollingSync,omitempty" protobuf:"bytes,2,opt,name=rollingSync"`
// RollingUpdate *ApplicationSetRolloutStrategy `json:"rollingUpdate,omitempty" protobuf:"bytes,3,opt,name=rollingUpdate"`
}
type ApplicationSetRolloutStrategy struct {
Steps []ApplicationSetRolloutStep `json:"steps,omitempty" protobuf:"bytes,1,opt,name=steps"`
}
type ApplicationSetRolloutStep struct {
MatchExpressions []ApplicationMatchExpression `json:"matchExpressions,omitempty" protobuf:"bytes,1,opt,name=matchExpressions"`
MaxUpdate *intstr.IntOrString `json:"maxUpdate,omitempty" protobuf:"bytes,2,opt,name=maxUpdate"`
}
type ApplicationMatchExpression struct {
Key string `json:"key,omitempty" protobuf:"bytes,1,opt,name=key"`
Operator string `json:"operator,omitempty" protobuf:"bytes,2,opt,name=operator"`
Values []string `json:"values,omitempty" protobuf:"bytes,3,opt,name=values"`
}
// ApplicationSetSyncPolicy configures how generated Applications will relate to their
@ -501,7 +524,8 @@ type PullRequestGeneratorFilter struct {
type ApplicationSetStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
Conditions []ApplicationSetCondition `json:"conditions,omitempty" protobuf:"bytes,1,name=conditions"`
Conditions []ApplicationSetCondition `json:"conditions,omitempty" protobuf:"bytes,1,name=conditions"`
ApplicationStatus []ApplicationSetApplicationStatus `json:"applicationStatus,omitempty" protobuf:"bytes,2,name=applicationStatus"`
}
// ApplicationSetCondition contains details about an applicationset condition, which is usally an error or warning
@ -542,6 +566,7 @@ const (
ApplicationSetConditionErrorOccurred ApplicationSetConditionType = "ErrorOccurred"
ApplicationSetConditionParametersGenerated ApplicationSetConditionType = "ParametersGenerated"
ApplicationSetConditionResourcesUpToDate ApplicationSetConditionType = "ResourcesUpToDate"
ApplicationSetConditionRolloutProgressing ApplicationSetConditionType = "RolloutProgressing"
)
type ApplicationSetReasonType string
@ -558,8 +583,23 @@ const (
ApplicationSetReasonDeleteApplicationError = "DeleteApplicationError"
ApplicationSetReasonRefreshApplicationError = "RefreshApplicationError"
ApplicationSetReasonApplicationValidationError = "ApplicationValidationError"
ApplicationSetReasonApplicationSetModified = "ApplicationSetModified"
ApplicationSetReasonApplicationSetRolloutComplete = "ApplicationSetRolloutComplete"
ApplicationSetReasonSyncApplicationError = "SyncApplicationError"
)
// ApplicationSetApplicationStatus contains details about each Application managed by the ApplicationSet
type ApplicationSetApplicationStatus struct {
// Application contains the name of the Application resource
Application string `json:"application" protobuf:"bytes,1,opt,name=application"`
// LastTransitionTime is the time the status was last updated
LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,2,opt,name=lastTransitionTime"`
// Message contains human-readable message indicating details about the status
Message string `json:"message" protobuf:"bytes,3,opt,name=message"`
// Status contains the AppSet's perceived status of the managed Application resource: (Waiting, Pending, Progressing, Healthy)
Status string `json:"status" protobuf:"bytes,5,opt,name=status"`
}
// ApplicationSetList contains a list of ApplicationSet
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:object:root=true
@ -617,3 +657,14 @@ func findConditionIndex(conditions []ApplicationSetCondition, t ApplicationSetCo
}
return -1
}
func (status *ApplicationSetStatus) SetApplicationStatus(newStatus ApplicationSetApplicationStatus) {
for i := range status.ApplicationStatus {
appStatus := status.ApplicationStatus[i]
if appStatus.Application == newStatus.Application {
status.ApplicationStatus[i] = newStatus
return
}
}
status.ApplicationStatus = append(status.ApplicationStatus, newStatus)
}

File diff suppressed because it is too large Load diff

View file

@ -10,6 +10,7 @@ import "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/generated.proto
import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto";
import "k8s.io/apimachinery/pkg/runtime/generated.proto";
import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
import "k8s.io/apimachinery/pkg/util/intstr/generated.proto";
// Package-wide variables from generator "generated".
option go_package = "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1";
@ -148,6 +149,14 @@ message ApplicationList {
repeated Application items = 2;
}
message ApplicationMatchExpression {
optional string key = 1;
optional string operator = 2;
repeated string values = 3;
}
// ApplicationSet is a set of Application resources
// +genclient
// +genclient:noStatus
@ -162,6 +171,21 @@ message ApplicationSet {
optional ApplicationSetStatus status = 3;
}
// ApplicationSetApplicationStatus contains details about each Application managed by the ApplicationSet
message ApplicationSetApplicationStatus {
// Application contains the name of the Application resource
optional string application = 1;
// LastTransitionTime is the time the status was last updated
optional k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 2;
// Message contains human-readable message indicating details about the status
optional string message = 3;
// Status contains the AppSet's perceived status of the managed Application resource: (Waiting, Pending, Progressing, Healthy)
optional string status = 5;
}
// ApplicationSetCondition contains details about an applicationset condition, which is usally an error or warning
message ApplicationSetCondition {
// Type is an applicationset condition type
@ -236,6 +260,16 @@ message ApplicationSetNestedGenerator {
optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 9;
}
message ApplicationSetRolloutStep {
repeated ApplicationMatchExpression matchExpressions = 1;
optional k8s.io.apimachinery.pkg.util.intstr.IntOrString maxUpdate = 2;
}
message ApplicationSetRolloutStrategy {
repeated ApplicationSetRolloutStep steps = 1;
}
// ApplicationSetSpec represents a class of application set state.
message ApplicationSetSpec {
optional bool goTemplate = 1;
@ -245,6 +279,8 @@ message ApplicationSetSpec {
optional ApplicationSetTemplate template = 3;
optional ApplicationSetSyncPolicy syncPolicy = 4;
optional ApplicationSetStrategy strategy = 5;
}
// ApplicationSetStatus defines the observed state of ApplicationSet
@ -252,6 +288,15 @@ message ApplicationSetStatus {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
repeated ApplicationSetCondition conditions = 1;
repeated ApplicationSetApplicationStatus applicationStatus = 2;
}
// ApplicationSetStrategy configures how generated Applications are updated in sequence.
message ApplicationSetStrategy {
optional string type = 1;
optional ApplicationSetRolloutStrategy rollingSync = 2;
}
// ApplicationSetSyncPolicy configures how generated Applications will relate to their

View file

@ -23,13 +23,18 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationCondition": schema_pkg_apis_application_v1alpha1_ApplicationCondition(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationDestination": schema_pkg_apis_application_v1alpha1_ApplicationDestination(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationList": schema_pkg_apis_application_v1alpha1_ApplicationList(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationMatchExpression": schema_pkg_apis_application_v1alpha1_ApplicationMatchExpression(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSet": schema_pkg_apis_application_v1alpha1_ApplicationSet(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetApplicationStatus": schema_pkg_apis_application_v1alpha1_ApplicationSetApplicationStatus(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetCondition": schema_pkg_apis_application_v1alpha1_ApplicationSetCondition(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetGenerator": schema_pkg_apis_application_v1alpha1_ApplicationSetGenerator(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetList": schema_pkg_apis_application_v1alpha1_ApplicationSetList(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetNestedGenerator": schema_pkg_apis_application_v1alpha1_ApplicationSetNestedGenerator(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetRolloutStep": schema_pkg_apis_application_v1alpha1_ApplicationSetRolloutStep(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetRolloutStrategy": schema_pkg_apis_application_v1alpha1_ApplicationSetRolloutStrategy(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetSpec": schema_pkg_apis_application_v1alpha1_ApplicationSetSpec(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetStatus": schema_pkg_apis_application_v1alpha1_ApplicationSetStatus(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetStrategy": schema_pkg_apis_application_v1alpha1_ApplicationSetStrategy(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetSyncPolicy": schema_pkg_apis_application_v1alpha1_ApplicationSetSyncPolicy(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetTemplate": schema_pkg_apis_application_v1alpha1_ApplicationSetTemplate(ref),
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetTemplateMeta": schema_pkg_apis_application_v1alpha1_ApplicationSetTemplateMeta(ref),
@ -651,6 +656,44 @@ func schema_pkg_apis_application_v1alpha1_ApplicationList(ref common.ReferenceCa
}
}
func schema_pkg_apis_application_v1alpha1_ApplicationMatchExpression(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"key": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"operator": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"values": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
},
},
}
}
func schema_pkg_apis_application_v1alpha1_ApplicationSet(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@ -699,6 +742,52 @@ func schema_pkg_apis_application_v1alpha1_ApplicationSet(ref common.ReferenceCal
}
}
func schema_pkg_apis_application_v1alpha1_ApplicationSetApplicationStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "ApplicationSetApplicationStatus contains details about each Application managed by the ApplicationSet",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"application": {
SchemaProps: spec.SchemaProps{
Description: "Application contains the name of the Application resource",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"lastTransitionTime": {
SchemaProps: spec.SchemaProps{
Description: "LastTransitionTime is the time the status was last updated",
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
},
},
"message": {
SchemaProps: spec.SchemaProps{
Description: "Message contains human-readable message indicating details about the status",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"status": {
SchemaProps: spec.SchemaProps{
Description: "Status contains the AppSet's perceived status of the managed Application resource: (Waiting, Pending, Progressing, Healthy)",
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"application", "message", "status"},
},
},
Dependencies: []string{
"k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
}
}
func schema_pkg_apis_application_v1alpha1_ApplicationSetCondition(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@ -926,6 +1015,65 @@ func schema_pkg_apis_application_v1alpha1_ApplicationSetNestedGenerator(ref comm
}
}
func schema_pkg_apis_application_v1alpha1_ApplicationSetRolloutStep(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"matchExpressions": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationMatchExpression"),
},
},
},
},
},
"maxUpdate": {
SchemaProps: spec.SchemaProps{
Ref: ref("k8s.io/apimachinery/pkg/util/intstr.IntOrString"),
},
},
},
},
},
Dependencies: []string{
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationMatchExpression", "k8s.io/apimachinery/pkg/util/intstr.IntOrString"},
}
}
func schema_pkg_apis_application_v1alpha1_ApplicationSetRolloutStrategy(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"steps": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetRolloutStep"),
},
},
},
},
},
},
},
},
Dependencies: []string{
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetRolloutStep"},
}
}
func schema_pkg_apis_application_v1alpha1_ApplicationSetSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@ -963,12 +1111,17 @@ func schema_pkg_apis_application_v1alpha1_ApplicationSetSpec(ref common.Referenc
Ref: ref("github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetSyncPolicy"),
},
},
"strategy": {
SchemaProps: spec.SchemaProps{
Ref: ref("github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetStrategy"),
},
},
},
Required: []string{"generators", "template"},
},
},
Dependencies: []string{
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetGenerator", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetSyncPolicy", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetTemplate"},
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetGenerator", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetStrategy", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetSyncPolicy", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetTemplate"},
}
}
@ -993,11 +1146,50 @@ func schema_pkg_apis_application_v1alpha1_ApplicationSetStatus(ref common.Refere
},
},
},
"applicationStatus": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetApplicationStatus"),
},
},
},
},
},
},
},
},
Dependencies: []string{
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetCondition"},
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetApplicationStatus", "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetCondition"},
}
}
func schema_pkg_apis_application_v1alpha1_ApplicationSetStrategy(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "ApplicationSetStrategy configures how generated Applications are updated in sequence.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"type": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"rollingSync": {
SchemaProps: spec.SchemaProps{
Ref: ref("github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetRolloutStrategy"),
},
},
},
},
},
Dependencies: []string{
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ApplicationSetRolloutStrategy"},
}
}

View file

@ -10,6 +10,7 @@ import (
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
intstr "k8s.io/apimachinery/pkg/util/intstr"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@ -293,6 +294,27 @@ func (in *ApplicationList) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationMatchExpression) DeepCopyInto(out *ApplicationMatchExpression) {
*out = *in
if in.Values != nil {
in, out := &in.Values, &out.Values
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationMatchExpression.
func (in *ApplicationMatchExpression) DeepCopy() *ApplicationMatchExpression {
if in == nil {
return nil
}
out := new(ApplicationMatchExpression)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationSet) DeepCopyInto(out *ApplicationSet) {
*out = *in
@ -321,6 +343,26 @@ func (in *ApplicationSet) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationSetApplicationStatus) DeepCopyInto(out *ApplicationSetApplicationStatus) {
*out = *in
if in.LastTransitionTime != nil {
in, out := &in.LastTransitionTime, &out.LastTransitionTime
*out = (*in).DeepCopy()
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationSetApplicationStatus.
func (in *ApplicationSetApplicationStatus) DeepCopy() *ApplicationSetApplicationStatus {
if in == nil {
return nil
}
out := new(ApplicationSetApplicationStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationSetCondition) DeepCopyInto(out *ApplicationSetCondition) {
*out = *in
@ -518,6 +560,57 @@ func (in ApplicationSetNestedGenerators) DeepCopy() ApplicationSetNestedGenerato
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationSetRolloutStep) DeepCopyInto(out *ApplicationSetRolloutStep) {
*out = *in
if in.MatchExpressions != nil {
in, out := &in.MatchExpressions, &out.MatchExpressions
*out = make([]ApplicationMatchExpression, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.MaxUpdate != nil {
in, out := &in.MaxUpdate, &out.MaxUpdate
*out = new(intstr.IntOrString)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationSetRolloutStep.
func (in *ApplicationSetRolloutStep) DeepCopy() *ApplicationSetRolloutStep {
if in == nil {
return nil
}
out := new(ApplicationSetRolloutStep)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationSetRolloutStrategy) DeepCopyInto(out *ApplicationSetRolloutStrategy) {
*out = *in
if in.Steps != nil {
in, out := &in.Steps, &out.Steps
*out = make([]ApplicationSetRolloutStep, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationSetRolloutStrategy.
func (in *ApplicationSetRolloutStrategy) DeepCopy() *ApplicationSetRolloutStrategy {
if in == nil {
return nil
}
out := new(ApplicationSetRolloutStrategy)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationSetSpec) DeepCopyInto(out *ApplicationSetSpec) {
*out = *in
@ -534,6 +627,11 @@ func (in *ApplicationSetSpec) DeepCopyInto(out *ApplicationSetSpec) {
*out = new(ApplicationSetSyncPolicy)
**out = **in
}
if in.Strategy != nil {
in, out := &in.Strategy, &out.Strategy
*out = new(ApplicationSetStrategy)
(*in).DeepCopyInto(*out)
}
return
}
@ -557,6 +655,13 @@ func (in *ApplicationSetStatus) DeepCopyInto(out *ApplicationSetStatus) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.ApplicationStatus != nil {
in, out := &in.ApplicationStatus, &out.ApplicationStatus
*out = make([]ApplicationSetApplicationStatus, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
@ -570,6 +675,27 @@ func (in *ApplicationSetStatus) DeepCopy() *ApplicationSetStatus {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationSetStrategy) DeepCopyInto(out *ApplicationSetStrategy) {
*out = *in
if in.RollingSync != nil {
in, out := &in.RollingSync, &out.RollingSync
*out = new(ApplicationSetRolloutStrategy)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationSetStrategy.
func (in *ApplicationSetStrategy) DeepCopy() *ApplicationSetStrategy {
if in == nil {
return nil
}
out := new(ApplicationSetStrategy)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ApplicationSetSyncPolicy) DeepCopyInto(out *ApplicationSetSyncPolicy) {
*out = *in