Added --resource flag to argocd app wait (#1453)

This commit is contained in:
Simon Behar 2019-04-19 09:59:06 -07:00 committed by Alex Collins
parent ad9ed33f8d
commit 8027882c1c

View file

@ -954,6 +954,31 @@ func formatConditionsSummary(app argoappv1.Application) string {
return summary
}
const (
resourceFieldDelimiter = ":"
resourceFieldCount = 3
)
func parseSelectedResources(resources []string) []argoappv1.SyncOperationResource {
var selectedResources []argoappv1.SyncOperationResource
if resources != nil {
selectedResources = []argoappv1.SyncOperationResource{}
for _, r := range resources {
fields := strings.Split(r, resourceFieldDelimiter)
if len(fields) != resourceFieldCount {
log.Fatalf("Resource should have GROUP%sKIND%sNAME, but instead got: %s", resourceFieldDelimiter, resourceFieldDelimiter, r)
}
rsrc := argoappv1.SyncOperationResource{
Group: fields[0],
Kind: fields[1],
Name: fields[2],
}
selectedResources = append(selectedResources, rsrc)
}
}
return selectedResources
}
// NewApplicationWaitCommand returns a new instance of an `argocd app wait` command
func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
@ -962,6 +987,7 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
watchSuspended bool
watchOperations bool
timeout uint
resources []string
)
var command = &cobra.Command{
Use: "wait APPNAME",
@ -977,16 +1003,17 @@ func NewApplicationWaitCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
watchOperations = true
watchSuspended = false
}
selectedResources := parseSelectedResources(resources)
appName := args[0]
acdClient := argocdclient.NewClientOrDie(clientOpts)
_, err := waitOnApplicationStatus(acdClient, appName, timeout, watchSync, watchHealth, watchOperations, watchSuspended, nil)
_, err := waitOnApplicationStatus(acdClient, appName, timeout, watchSync, watchHealth, watchOperations, watchSuspended, selectedResources)
errors.CheckError(err)
},
}
command.Flags().BoolVar(&watchSync, "sync", false, "Wait for sync")
command.Flags().BoolVar(&watchHealth, "health", false, "Wait for health")
command.Flags().BoolVar(&watchSuspended, "suspended", false, "Wait for suspended")
command.Flags().StringArrayVar(&resources, "resource", []string{}, fmt.Sprintf("Sync only specific resources as GROUP%sKIND%sNAME. Fields may be blank. This option may be specified repeatedly", resourceFieldDelimiter, resourceFieldDelimiter))
command.Flags().BoolVar(&watchOperations, "operation", false, "Wait for pending operations")
command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds")
return command
@ -1045,17 +1072,13 @@ func printAppResources(w io.Writer, app *argoappv1.Application, showOperation bo
func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
var (
revision string
resources *[]string
resources []string
prune bool
dryRun bool
timeout uint
strategy string
force bool
)
const (
resourceFieldDelimiter = ":"
resourceFieldCount = 3
)
var command = &cobra.Command{
Use: "sync APPNAME",
Short: "Sync an application to its target state",
@ -1067,28 +1090,14 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
acdClient := argocdclient.NewClientOrDie(clientOpts)
conn, appIf := acdClient.NewApplicationClientOrDie()
defer util.Close(conn)
selectedResources := parseSelectedResources(resources)
appName := args[0]
var syncResources []argoappv1.SyncOperationResource
if resources != nil {
syncResources = []argoappv1.SyncOperationResource{}
for _, r := range *resources {
fields := strings.Split(r, resourceFieldDelimiter)
if len(fields) != resourceFieldCount {
log.Fatalf("Resource should have GROUP%sKIND%sNAME, but instead got: %s", resourceFieldDelimiter, resourceFieldDelimiter, r)
}
rsrc := argoappv1.SyncOperationResource{
Group: fields[0],
Kind: fields[1],
Name: fields[2],
}
syncResources = append(syncResources, rsrc)
}
}
syncReq := application.ApplicationSyncRequest{
Name: &appName,
DryRun: dryRun,
Revision: revision,
Resources: syncResources,
Resources: selectedResources,
Prune: prune,
}
switch strategy {
@ -1105,7 +1114,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
_, err := appIf.Sync(ctx, &syncReq)
errors.CheckError(err)
app, err := waitOnApplicationStatus(acdClient, appName, timeout, false, false, true, false, syncResources)
app, err := waitOnApplicationStatus(acdClient, appName, timeout, false, false, true, false, selectedResources)
errors.CheckError(err)
pruningRequired := 0
@ -1126,7 +1135,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
command.Flags().BoolVar(&dryRun, "dry-run", false, "Preview apply without affecting cluster")
command.Flags().BoolVar(&prune, "prune", false, "Allow deleting unexpected resources")
command.Flags().StringVar(&revision, "revision", "", "Sync to a specific revision. Preserves parameter overrides")
resources = command.Flags().StringArray("resource", nil, fmt.Sprintf("Sync only specific resources as GROUP%sKIND%sNAME. Fields may be blank. This option may be specified repeatedly", resourceFieldDelimiter, resourceFieldDelimiter))
command.Flags().StringArrayVar(&resources, "resource", []string{}, fmt.Sprintf("Sync only specific resources as GROUP%sKIND%sNAME. Fields may be blank. This option may be specified repeatedly", resourceFieldDelimiter, resourceFieldDelimiter))
command.Flags().UintVar(&timeout, "timeout", defaultCheckTimeoutSeconds, "Time out after this many seconds")
command.Flags().StringVar(&strategy, "strategy", "", "Sync strategy (one of: apply|hook)")
command.Flags().BoolVar(&force, "force", false, "Use a force apply")
@ -1200,20 +1209,8 @@ func (rs *resourceState) Merge(newState *resourceState) bool {
return updated
}
func calculateResourceStates(app *argoappv1.Application, syncResources []argoappv1.SyncOperationResource) map[string]*resourceState {
resStates := make(map[string]*resourceState)
for _, res := range app.Status.Resources {
if len(syncResources) > 0 && !argo.ContainsSyncResource(res.Name, res.GroupVersionKind(), syncResources) {
continue
}
newState := newResourceStateFromStatus(&res)
key := newState.Key()
if prev, ok := resStates[key]; ok {
prev.Merge(newState)
} else {
resStates[key] = newState
}
}
func calculateResourceStates(app *argoappv1.Application, selectedResources []argoappv1.SyncOperationResource) map[string]*resourceState {
resStates := getResourceStates(app, selectedResources)
var opResult *argoappv1.SyncOperationResult
if app.Status.OperationState != nil {
@ -1238,9 +1235,42 @@ func calculateResourceStates(app *argoappv1.Application, syncResources []argoapp
return resStates
}
func getResourceStates(app *argoappv1.Application, selectedResources []argoappv1.SyncOperationResource) map[string]*resourceState {
resStates := make(map[string]*resourceState)
for _, res := range app.Status.Resources {
if len(selectedResources) > 0 && !argo.ContainsSyncResource(res.Name, res.GroupVersionKind(), selectedResources) {
continue
}
newState := newResourceStateFromStatus(&res)
key := newState.Key()
if prev, ok := resStates[key]; ok {
prev.Merge(newState)
} else {
resStates[key] = newState
}
}
return resStates
}
func checkResourceStatus(watchSync bool, watchHealth bool, watchOperation bool, watchSuspended bool, healthStatus string, syncStatus string, operationStatus *argoappv1.Operation) bool {
healthCheckPassed := true
if watchSuspended && watchHealth {
healthCheckPassed = healthStatus == argoappv1.HealthStatusHealthy ||
healthStatus == argoappv1.HealthStatusSuspended
} else if watchSuspended {
healthCheckPassed = healthStatus == argoappv1.HealthStatusSuspended
} else if watchHealth {
healthCheckPassed = healthStatus == argoappv1.HealthStatusHealthy
}
synced := !watchSync || syncStatus == string(argoappv1.SyncStatusCodeSynced)
operational := !watchOperation || operationStatus == nil
return synced && healthCheckPassed && operational
}
const waitFormatString = "%s\t%5s\t%10s\t%10s\t%20s\t%8s\t%7s\t%10s\t%s\n"
func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout uint, watchSync bool, watchHealth bool, watchOperation bool, watchSuspened bool, syncResources []argoappv1.SyncOperationResource) (*argoappv1.Application, error) {
func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout uint, watchSync bool, watchHealth bool, watchOperation bool, watchSuspended bool, selectedResources []argoappv1.SyncOperationResource) (*argoappv1.Application, error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -1296,25 +1326,29 @@ func waitOnApplicationStatus(acdClient apiclient.Client, appName string, timeout
refresh = true
}
healthStatus := true
if watchSuspened && watchHealth {
healthStatus = app.Status.Health.Status == argoappv1.HealthStatusHealthy ||
app.Status.Health.Status == argoappv1.HealthStatusSuspended
} else if watchSuspened {
healthStatus = app.Status.Health.Status == argoappv1.HealthStatusSuspended
} else if watchHealth {
healthStatus = app.Status.Health.Status == argoappv1.HealthStatusHealthy
var selectedResourcesAreReady bool
// If selected resources are included, wait only on those resources, otherwise wait on the application as a whole.
if len(selectedResources) > 0 {
selectedResourcesAreReady = true
for _, state := range getResourceStates(app, selectedResources) {
resourceIsReady := checkResourceStatus(watchSync, watchHealth, false, watchSuspended, state.Health, state.Status, nil)
if !resourceIsReady {
selectedResourcesAreReady = false
break
}
}
} else {
// Wait on the application as a whole
selectedResourcesAreReady = checkResourceStatus(watchSync, watchHealth, watchOperation, watchSuspended, app.Status.Health.Status, string(app.Status.Sync.Status), appEvent.Application.Operation)
}
// consider skipped checks successful
synced := !watchSync || app.Status.Sync.Status == argoappv1.SyncStatusCodeSynced
operational := !watchOperation || appEvent.Application.Operation == nil
if len(app.Status.GetErrorConditions()) == 0 && synced && healthStatus && operational {
if len(app.Status.GetErrorConditions()) == 0 && selectedResourcesAreReady {
printFinalStatus(app)
return app, nil
}
newStates := calculateResourceStates(app, syncResources)
newStates := calculateResourceStates(app, selectedResources)
for _, newState := range newStates {
var doPrint bool
stateKey := newState.Key()