2018-02-20 23:23:37 +00:00
package controller
import (
"context"
2018-11-17 01:10:04 +00:00
"crypto/tls"
2018-03-09 10:01:15 +00:00
"encoding/json"
"fmt"
2018-07-07 07:54:06 +00:00
"reflect"
2018-05-15 07:36:11 +00:00
"runtime/debug"
2018-11-27 21:38:00 +00:00
"strings"
2018-04-11 19:53:33 +00:00
"sync"
2018-05-05 00:01:57 +00:00
"time"
2018-04-11 19:53:33 +00:00
2018-11-30 21:50:27 +00:00
"github.com/grpc-ecosystem/go-grpc-middleware"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
log "github.com/sirupsen/logrus"
2018-11-17 01:10:04 +00:00
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc/status"
2018-07-24 15:48:13 +00:00
"k8s.io/api/core/v1"
2018-05-16 23:30:28 +00:00
"k8s.io/apimachinery/pkg/api/errors"
2018-02-26 15:43:35 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2018-03-07 06:05:07 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2018-05-03 22:55:01 +00:00
"k8s.io/apimachinery/pkg/types"
2018-02-20 23:23:37 +00:00
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
2018-07-07 07:54:06 +00:00
2018-11-28 21:38:02 +00:00
statecache "github.com/argoproj/argo-cd/controller/cache"
2018-11-17 01:10:04 +00:00
"github.com/argoproj/argo-cd/controller/services"
2018-07-07 07:54:06 +00:00
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
appinformers "github.com/argoproj/argo-cd/pkg/client/informers/externalversions"
2018-07-09 17:45:03 +00:00
"github.com/argoproj/argo-cd/reposerver"
2018-11-28 21:38:02 +00:00
"github.com/argoproj/argo-cd/util"
2018-07-09 17:45:03 +00:00
"github.com/argoproj/argo-cd/util/argo"
2018-07-07 07:54:06 +00:00
"github.com/argoproj/argo-cd/util/db"
2018-11-30 21:50:27 +00:00
"github.com/argoproj/argo-cd/util/diff"
2018-11-17 01:10:04 +00:00
grpc_util "github.com/argoproj/argo-cd/util/grpc"
2018-07-07 07:54:06 +00:00
"github.com/argoproj/argo-cd/util/kube"
2018-11-17 01:10:04 +00:00
settings_util "github.com/argoproj/argo-cd/util/settings"
tlsutil "github.com/argoproj/argo-cd/util/tls"
2018-02-20 23:23:37 +00:00
)
2018-04-11 19:53:33 +00:00
const (
2018-05-11 18:50:32 +00:00
updateOperationStateTimeout = 1 * time . Second
2018-04-11 19:53:33 +00:00
)
2018-02-20 23:23:37 +00:00
// ApplicationController is the controller for application resources.
type ApplicationController struct {
2018-11-30 18:32:31 +00:00
namespace string
kubeClientset kubernetes . Interface
kubectl kube . Kubectl
applicationClientset appclientset . Interface
auditLogger * argo . AuditLogger
appRefreshQueue workqueue . RateLimitingInterface
appOperationQueue workqueue . RateLimitingInterface
appInformer cache . SharedIndexInformer
appStateManager AppStateManager
stateCache statecache . LiveStateCache
statusRefreshTimeout time . Duration
repoClientset reposerver . Clientset
db db . ArgoDB
forceRefreshApps map [ string ] bool
forceRefreshAppsMutex * sync . Mutex
2018-12-03 18:27:43 +00:00
managedResources map [ string ] [ ] managedResource
2018-11-30 18:32:31 +00:00
managedResourcesMutex * sync . Mutex
2018-02-26 15:43:35 +00:00
}
type ApplicationControllerConfig struct {
InstanceID string
Namespace string
2018-02-20 23:23:37 +00:00
}
// NewApplicationController creates new instance of ApplicationController.
2018-02-22 18:56:14 +00:00
func NewApplicationController (
2018-04-11 19:53:33 +00:00
namespace string ,
2018-02-22 18:56:14 +00:00
kubeClientset kubernetes . Interface ,
applicationClientset appclientset . Interface ,
2018-07-09 17:45:03 +00:00
repoClientset reposerver . Clientset ,
2018-02-26 15:43:35 +00:00
appResyncPeriod time . Duration ,
2018-03-07 06:05:07 +00:00
) * ApplicationController {
2018-11-09 17:58:07 +00:00
settingsMgr := settings_util . NewSettingsManager ( kubeClientset , namespace )
db := db . NewDB ( namespace , settingsMgr , kubeClientset )
2018-09-11 21:28:53 +00:00
kubectlCmd := kube . KubectlCmd { }
ctrl := ApplicationController {
2018-11-30 18:32:31 +00:00
namespace : namespace ,
kubeClientset : kubeClientset ,
kubectl : kubectlCmd ,
applicationClientset : applicationClientset ,
repoClientset : repoClientset ,
appRefreshQueue : workqueue . NewRateLimitingQueue ( workqueue . DefaultControllerRateLimiter ( ) ) ,
appOperationQueue : workqueue . NewRateLimitingQueue ( workqueue . DefaultControllerRateLimiter ( ) ) ,
db : db ,
statusRefreshTimeout : appResyncPeriod ,
forceRefreshApps : make ( map [ string ] bool ) ,
forceRefreshAppsMutex : & sync . Mutex { } ,
auditLogger : argo . NewAuditLogger ( namespace , kubeClientset , "application-controller" ) ,
2018-12-03 18:27:43 +00:00
managedResources : make ( map [ string ] [ ] managedResource ) ,
2018-11-30 18:32:31 +00:00
managedResourcesMutex : & sync . Mutex { } ,
2018-11-28 21:38:02 +00:00
}
appInformer := ctrl . newApplicationInformer ( )
stateCache := statecache . NewLiveStateCache ( db , appInformer , kubectlCmd , func ( appName string ) {
ctrl . forceAppRefresh ( appName )
ctrl . appRefreshQueue . Add ( fmt . Sprintf ( "%s/%s" , ctrl . namespace , appName ) )
} )
appStateManager := NewAppStateManager ( db , applicationClientset , repoClientset , namespace , kubectlCmd , stateCache )
ctrl . appInformer = appInformer
ctrl . appStateManager = appStateManager
ctrl . stateCache = stateCache
2018-09-11 21:28:53 +00:00
return & ctrl
2018-02-20 23:23:37 +00:00
}
2018-11-28 21:38:02 +00:00
func ( ctrl * ApplicationController ) getApp ( name string ) ( * appv1 . Application , error ) {
obj , exists , err := ctrl . appInformer . GetStore ( ) . GetByKey ( fmt . Sprintf ( "%s/%s" , ctrl . namespace , name ) )
2018-11-17 01:10:04 +00:00
if err != nil {
2018-11-28 21:38:02 +00:00
return nil , err
}
if ! exists {
return nil , status . Error ( codes . NotFound , fmt . Sprintf ( "unable to find application with name %s" , name ) )
2018-11-17 01:10:04 +00:00
}
2018-11-28 21:38:02 +00:00
a , ok := ( obj ) . ( * appv1 . Application )
if ! ok {
return nil , status . Errorf ( codes . Internal , fmt . Sprintf ( "unexpected object type in app informer" ) )
}
return a , nil
}
2018-12-03 18:27:43 +00:00
func ( ctrl * ApplicationController ) setAppManagedResources ( appName string , resources [ ] managedResource ) {
2018-11-30 18:32:31 +00:00
ctrl . managedResourcesMutex . Lock ( )
defer ctrl . managedResourcesMutex . Unlock ( )
ctrl . managedResources [ appName ] = resources
2018-11-17 01:10:04 +00:00
}
2018-12-03 18:27:43 +00:00
func ( ctrl * ApplicationController ) getAppManagedResources ( appName string ) [ ] managedResource {
2018-11-30 18:32:31 +00:00
ctrl . managedResourcesMutex . Lock ( )
defer ctrl . managedResourcesMutex . Unlock ( )
return ctrl . managedResources [ appName ]
2018-11-28 21:38:02 +00:00
}
2018-11-30 18:32:31 +00:00
func ( ctrl * ApplicationController ) ResourceTree ( ctx context . Context , q * services . ResourcesQuery ) ( * services . ResourceTreeResponse , error ) {
2018-11-28 21:38:02 +00:00
a , err := ctrl . getApp ( q . ApplicationName )
if err != nil {
return nil , err
2018-11-17 01:10:04 +00:00
}
2018-11-30 18:32:31 +00:00
managedResources := ctrl . getAppManagedResources ( q . ApplicationName )
2018-11-28 21:38:02 +00:00
items := make ( [ ] * appv1 . ResourceNode , 0 )
2018-11-30 18:32:31 +00:00
for i := range managedResources {
managedResource := managedResources [ i ]
2018-11-28 21:38:02 +00:00
node := appv1 . ResourceNode {
2018-11-30 18:32:31 +00:00
Name : managedResource . Name ,
Version : managedResource . Version ,
Kind : managedResource . Kind ,
Group : managedResource . Group ,
Namespace : managedResource . Namespace ,
2018-11-28 21:38:02 +00:00
}
2018-11-30 18:32:31 +00:00
if managedResource . Live != nil {
node . ResourceVersion = managedResource . Live . GetResourceVersion ( )
children , err := ctrl . stateCache . GetChildren ( a . Spec . Destination . Server , managedResource . Live )
2018-11-17 01:10:04 +00:00
if err != nil {
return nil , err
}
2018-11-28 21:38:02 +00:00
node . Children = children
}
items = append ( items , & node )
}
2018-11-30 18:32:31 +00:00
return & services . ResourceTreeResponse { Items : items } , nil
2018-11-28 21:38:02 +00:00
}
2018-11-30 18:32:31 +00:00
func ( ctrl * ApplicationController ) ManagedResources ( ctx context . Context , q * services . ResourcesQuery ) ( * services . ManagedResourcesResponse , error ) {
resources := ctrl . getAppManagedResources ( q . ApplicationName )
2018-12-04 01:39:55 +00:00
items := make ( [ ] * appv1 . ResourceDiff , len ( resources ) )
2018-11-28 21:38:02 +00:00
for i := range resources {
res := resources [ i ]
2018-12-04 01:39:55 +00:00
item := appv1 . ResourceDiff {
2018-11-28 21:38:02 +00:00
Namespace : res . Namespace ,
Name : res . Name ,
Group : res . Group ,
Kind : res . Kind ,
}
live , data := hideSecretData ( res . Live , nil )
target , _ := hideSecretData ( res . Target , data )
if live != nil {
data , err := json . Marshal ( live )
if err != nil {
return nil , err
2018-11-17 01:10:04 +00:00
}
2018-11-28 21:38:02 +00:00
item . LiveState = string ( data )
} else {
item . LiveState = "null"
}
if target != nil {
data , err := json . Marshal ( target )
if err != nil {
return nil , err
2018-11-17 01:10:04 +00:00
}
2018-11-28 21:38:02 +00:00
item . TargetState = string ( data )
} else {
item . TargetState = "null"
2018-11-17 01:10:04 +00:00
}
2018-11-28 21:38:02 +00:00
jsonDiff , err := res . Diff . JSONFormat ( )
if err != nil {
return nil , err
}
item . Diff = jsonDiff
items [ i ] = & item
2018-11-17 01:10:04 +00:00
}
2018-11-30 18:32:31 +00:00
return & services . ManagedResourcesResponse { Items : items } , nil
2018-11-17 01:10:04 +00:00
}
func toString ( val interface { } ) string {
if val == nil {
return ""
}
return fmt . Sprintf ( "%s" , val )
}
// hideSecretData checks if given object kind is Secret, replaces data keys with stars and returns unchanged data map. The method additionally check if data key if different
// from corresponding key of optional parameter `otherData` and adds extra star to keep information about difference. So if secret data is out of sync user still can see which
// fields are different.
2018-11-28 21:38:02 +00:00
func hideSecretData ( state * unstructured . Unstructured , otherData map [ string ] interface { } ) ( * unstructured . Unstructured , map [ string ] interface { } ) {
obj := state . DeepCopy ( )
if obj != nil && obj . GroupVersionKind ( ) . Group == "" && obj . GetKind ( ) == kube . SecretKind {
if data , ok , err := unstructured . NestedMap ( obj . Object , "data" ) ; err == nil && ok {
unchangedData := make ( map [ string ] interface { } )
for k , v := range data {
unchangedData [ k ] = v
}
for k := range data {
replacement := "********"
if otherData != nil {
if val , ok := otherData [ k ] ; ok && toString ( val ) != toString ( data [ k ] ) {
replacement = replacement + "*"
2018-11-17 01:10:04 +00:00
}
}
2018-11-28 21:38:02 +00:00
data [ k ] = replacement
2018-11-17 01:10:04 +00:00
}
2018-11-28 21:38:02 +00:00
_ = unstructured . SetNestedMap ( obj . Object , data , "data" )
2018-11-17 01:10:04 +00:00
2018-11-28 21:38:02 +00:00
return obj , unchangedData
}
2018-11-17 01:10:04 +00:00
}
2018-11-28 21:38:02 +00:00
return obj , nil
2018-11-17 01:10:04 +00:00
}
2018-02-20 23:23:37 +00:00
// Run starts the Application CRD controller.
2018-05-11 18:50:32 +00:00
func ( ctrl * ApplicationController ) Run ( ctx context . Context , statusProcessors int , operationProcessors int ) {
2018-02-20 23:23:37 +00:00
defer runtime . HandleCrash ( )
2018-05-11 18:50:32 +00:00
defer ctrl . appRefreshQueue . ShutDown ( )
2018-02-20 23:23:37 +00:00
go ctrl . appInformer . Run ( ctx . Done ( ) )
if ! cache . WaitForCacheSync ( ctx . Done ( ) , ctrl . appInformer . HasSynced ) {
log . Error ( "Timed out waiting for caches to sync" )
return
}
2018-11-28 21:38:02 +00:00
go ctrl . stateCache . Run ( ctx )
2018-09-10 15:20:17 +00:00
2018-05-11 18:50:32 +00:00
for i := 0 ; i < statusProcessors ; i ++ {
go wait . Until ( func ( ) {
for ctrl . processAppRefreshQueueItem ( ) {
}
} , time . Second , ctx . Done ( ) )
}
for i := 0 ; i < operationProcessors ; i ++ {
go wait . Until ( func ( ) {
for ctrl . processAppOperationQueueItem ( ) {
}
} , time . Second , ctx . Done ( ) )
2018-02-20 23:23:37 +00:00
}
<- ctx . Done ( )
}
2018-11-17 01:10:04 +00:00
func ( ctrl * ApplicationController ) CreateGRPC ( tlsConfCustomizer tlsutil . ConfigCustomizer ) ( * grpc . Server , error ) {
// generate TLS cert
hosts := [ ] string {
"localhost" ,
"application-controller" ,
}
cert , err := tlsutil . GenerateX509KeyPair ( tlsutil . CertOptions {
Hosts : hosts ,
Organization : "Argo CD" ,
IsCA : true ,
} )
if err != nil {
return nil , err
}
tlsConfig := & tls . Config { Certificates : [ ] tls . Certificate { * cert } }
tlsConfCustomizer ( tlsConfig )
logEntry := log . NewEntry ( log . New ( ) )
server := grpc . NewServer (
grpc . Creds ( credentials . NewTLS ( tlsConfig ) ) ,
grpc . StreamInterceptor ( grpc_middleware . ChainStreamServer (
grpc_logrus . StreamServerInterceptor ( logEntry ) ,
grpc_util . PanicLoggerStreamServerInterceptor ( logEntry ) ,
) ) ,
grpc . UnaryInterceptor ( grpc_middleware . ChainUnaryServer (
grpc_logrus . UnaryServerInterceptor ( logEntry ) ,
grpc_util . PanicLoggerUnaryServerInterceptor ( logEntry ) ,
) ) ,
)
services . RegisterApplicationServiceServer ( server , ctrl )
reflection . Register ( server )
return server , nil
}
2018-04-11 19:53:33 +00:00
func ( ctrl * ApplicationController ) forceAppRefresh ( appName string ) {
ctrl . forceRefreshAppsMutex . Lock ( )
defer ctrl . forceRefreshAppsMutex . Unlock ( )
ctrl . forceRefreshApps [ appName ] = true
}
func ( ctrl * ApplicationController ) isRefreshForced ( appName string ) bool {
ctrl . forceRefreshAppsMutex . Lock ( )
defer ctrl . forceRefreshAppsMutex . Unlock ( )
_ , ok := ctrl . forceRefreshApps [ appName ]
if ok {
delete ( ctrl . forceRefreshApps , appName )
}
return ok
}
2018-05-16 23:30:28 +00:00
func ( ctrl * ApplicationController ) processAppOperationQueueItem ( ) ( processNext bool ) {
2018-05-11 18:50:32 +00:00
appKey , shutdown := ctrl . appOperationQueue . Get ( )
if shutdown {
2018-05-16 23:30:28 +00:00
processNext = false
return
2018-05-11 18:50:32 +00:00
}
2018-11-19 20:25:45 +00:00
processNext = true
2018-05-16 23:30:28 +00:00
defer func ( ) {
if r := recover ( ) ; r != nil {
log . Errorf ( "Recovered from panic: %+v\n%s" , r , debug . Stack ( ) )
}
ctrl . appOperationQueue . Done ( appKey )
} ( )
2018-05-11 18:50:32 +00:00
obj , exists , err := ctrl . appInformer . GetIndexer ( ) . GetByKey ( appKey . ( string ) )
if err != nil {
log . Errorf ( "Failed to get application '%s' from informer index: %+v" , appKey , err )
2018-05-16 23:30:28 +00:00
return
2018-05-11 18:50:32 +00:00
}
if ! exists {
// This happens after app was deleted, but the work queue still had an entry for it.
2018-05-16 23:30:28 +00:00
return
2018-05-11 18:50:32 +00:00
}
app , ok := obj . ( * appv1 . Application )
if ! ok {
log . Warnf ( "Key '%s' in index is not an application" , appKey )
2018-05-16 23:30:28 +00:00
return
}
if app . Operation != nil {
ctrl . processRequestedAppOperation ( app )
} else if app . DeletionTimestamp != nil && app . CascadedDeletion ( ) {
2018-11-19 20:25:45 +00:00
err = ctrl . finalizeApplicationDeletion ( app )
if err != nil {
ctrl . setAppCondition ( app , appv1 . ApplicationCondition {
Type : appv1 . ApplicationConditionDeletionError ,
Message : err . Error ( ) ,
} )
message := fmt . Sprintf ( "Unable to delete application resources: %v" , err . Error ( ) )
ctrl . auditLogger . LogAppEvent ( app , argo . EventInfo { Reason : argo . EventReasonStatusRefreshed , Type : v1 . EventTypeWarning } , message )
}
2018-05-11 18:50:32 +00:00
}
2018-05-16 23:30:28 +00:00
return
}
2018-11-19 20:25:45 +00:00
func ( ctrl * ApplicationController ) finalizeApplicationDeletion ( app * appv1 . Application ) error {
2018-09-24 15:52:43 +00:00
logCtx := log . WithField ( "application" , app . Name )
logCtx . Infof ( "Deleting resources" )
2018-05-16 23:30:28 +00:00
// Get refreshed application info, since informer app copy might be stale
app , err := ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( app . Namespace ) . Get ( app . Name , metav1 . GetOptions { } )
if err != nil {
if ! errors . IsNotFound ( err ) {
2018-09-24 15:52:43 +00:00
logCtx . Errorf ( "Unable to get refreshed application info prior deleting resources: %v" , err )
2018-05-16 23:30:28 +00:00
}
2018-11-19 20:25:45 +00:00
return nil
2018-05-15 07:36:11 +00:00
}
2018-11-30 19:29:12 +00:00
2018-11-30 18:32:31 +00:00
objsMap , err := ctrl . stateCache . GetManagedLiveObjs ( app , [ ] * unstructured . Unstructured { } )
2018-05-16 23:30:28 +00:00
if err != nil {
2018-11-19 20:25:45 +00:00
return err
}
2018-11-28 21:38:02 +00:00
objs := make ( [ ] * unstructured . Unstructured , 0 )
for k := range objsMap {
objs = append ( objs , objsMap [ k ] )
}
err = util . RunAllAsync ( len ( objs ) , func ( i int ) error {
obj := objs [ i ]
2018-11-30 19:29:12 +00:00
return ctrl . stateCache . Delete ( app . Spec . Destination . Server , obj )
2018-11-28 21:38:02 +00:00
} )
2018-11-19 20:25:45 +00:00
if err != nil {
return err
}
2018-11-28 21:38:02 +00:00
2018-11-30 18:32:31 +00:00
objsMap , err = ctrl . stateCache . GetManagedLiveObjs ( app , [ ] * unstructured . Unstructured { } )
2018-11-28 21:38:02 +00:00
if err != nil {
return err
}
if len ( objsMap ) > 0 {
logCtx . Infof ( "%d objects remaining for deletion" , len ( objsMap ) )
2018-11-19 20:25:45 +00:00
return nil
}
app . SetCascadedDeletion ( false )
var patch [ ] byte
patch , _ = json . Marshal ( map [ string ] interface { } {
"metadata" : map [ string ] interface { } {
"finalizers" : app . Finalizers ,
} ,
} )
_ , err = ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( app . Namespace ) . Patch ( app . Name , types . MergePatchType , patch )
if err != nil {
return err
}
2018-11-28 21:38:02 +00:00
2018-11-19 20:25:45 +00:00
logCtx . Info ( "Successfully deleted resources" )
return nil
2018-05-16 23:30:28 +00:00
}
func ( ctrl * ApplicationController ) setAppCondition ( app * appv1 . Application , condition appv1 . ApplicationCondition ) {
index := - 1
for i , exiting := range app . Status . Conditions {
if exiting . Type == condition . Type {
index = i
break
}
}
if index > - 1 {
app . Status . Conditions [ index ] = condition
} else {
app . Status . Conditions = append ( app . Status . Conditions , condition )
}
var patch [ ] byte
patch , err := json . Marshal ( map [ string ] interface { } {
"status" : map [ string ] interface { } {
"conditions" : app . Status . Conditions ,
} ,
} )
if err == nil {
_ , err = ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( app . Namespace ) . Patch ( app . Name , types . MergePatchType , patch )
}
if err != nil {
log . Errorf ( "Unable to set application condition: %v" , err )
}
}
func ( ctrl * ApplicationController ) processRequestedAppOperation ( app * appv1 . Application ) {
2018-09-11 21:28:53 +00:00
logCtx := log . WithField ( "application" , app . Name )
2018-07-07 07:54:06 +00:00
var state * appv1 . OperationState
2018-05-15 07:36:11 +00:00
// Recover from any unexpected panics and automatically set the status to be failed
defer func ( ) {
if r := recover ( ) ; r != nil {
2018-09-11 21:28:53 +00:00
logCtx . Errorf ( "Recovered from panic: %+v\n%s" , r , debug . Stack ( ) )
2018-05-15 18:35:10 +00:00
state . Phase = appv1 . OperationError
2018-05-15 07:36:11 +00:00
if rerr , ok := r . ( error ) ; ok {
state . Message = rerr . Error ( )
2018-05-11 18:50:32 +00:00
} else {
2018-05-15 07:36:11 +00:00
state . Message = fmt . Sprintf ( "%v" , r )
2018-05-11 18:50:32 +00:00
}
2018-07-14 00:13:31 +00:00
ctrl . setOperationState ( app , state )
2018-05-15 07:36:11 +00:00
}
} ( )
2018-07-14 00:13:31 +00:00
if isOperationInProgress ( app ) {
// If we get here, we are about process an operation but we notice it is already in progress.
// We need to detect if the app object we pulled off the informer is stale and doesn't
// reflect the fact that the operation is completed. We don't want to perform the operation
// again. To detect this, always retrieve the latest version to ensure it is not stale.
2018-05-15 07:36:11 +00:00
freshApp , err := ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( ctrl . namespace ) . Get ( app . ObjectMeta . Name , metav1 . GetOptions { } )
if err != nil {
2018-09-11 21:28:53 +00:00
logCtx . Errorf ( "Failed to retrieve latest application state: %v" , err )
2018-05-16 23:30:28 +00:00
return
2018-05-15 07:36:11 +00:00
}
2018-07-14 00:13:31 +00:00
if ! isOperationInProgress ( freshApp ) {
2018-09-11 21:28:53 +00:00
logCtx . Infof ( "Skipping operation on stale application state" )
2018-05-16 23:30:28 +00:00
return
2018-05-15 07:36:11 +00:00
}
2018-07-07 07:54:06 +00:00
app = freshApp
state = app . Status . OperationState . DeepCopy ( )
2018-09-11 21:28:53 +00:00
logCtx . Infof ( "Resuming in-progress operation. phase: %s, message: %s" , state . Phase , state . Message )
2018-05-15 07:36:11 +00:00
} else {
2018-07-07 07:54:06 +00:00
state = & appv1 . OperationState { Phase : appv1 . OperationRunning , Operation : * app . Operation , StartedAt : metav1 . Now ( ) }
2018-07-14 00:13:31 +00:00
ctrl . setOperationState ( app , state )
2018-09-11 21:28:53 +00:00
logCtx . Infof ( "Initialized new operation: %v" , * app . Operation )
2018-05-15 07:36:11 +00:00
}
2018-07-07 07:54:06 +00:00
ctrl . appStateManager . SyncAppState ( app , state )
2018-07-14 00:13:31 +00:00
if state . Phase == appv1 . OperationRunning {
// It's possible for an app to be terminated while we were operating on it. We do not want
// to clobber the Terminated state with Running. Get the latest app state to check for this.
freshApp , err := ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( ctrl . namespace ) . Get ( app . ObjectMeta . Name , metav1 . GetOptions { } )
if err == nil {
if freshApp . Status . OperationState != nil && freshApp . Status . OperationState . Phase == appv1 . OperationTerminating {
state . Phase = appv1 . OperationTerminating
state . Message = "operation is terminating"
// after this, we will get requeued to the workqueue, but next time the
// SyncAppState will operate in a Terminating phase, allowing the worker to perform
// cleanup (e.g. delete jobs, workflows, etc...)
}
}
}
ctrl . setOperationState ( app , state )
2018-07-12 19:39:46 +00:00
if state . Phase . Completed ( ) {
// if we just completed an operation, force a refresh so that UI will report up-to-date
// sync/health information
ctrl . forceAppRefresh ( app . ObjectMeta . Name )
}
2018-05-11 18:50:32 +00:00
}
2018-07-14 00:13:31 +00:00
func ( ctrl * ApplicationController ) setOperationState ( app * appv1 . Application , state * appv1 . OperationState ) {
2018-11-28 21:38:02 +00:00
util . RetryUntilSucceed ( func ( ) error {
2018-05-15 07:36:11 +00:00
if state . Phase == "" {
// expose any bugs where we neglect to set phase
panic ( "no phase was set" )
}
2018-07-14 00:13:31 +00:00
if state . Phase . Completed ( ) {
now := metav1 . Now ( )
state . FinishedAt = & now
2018-05-15 07:36:11 +00:00
}
2018-07-14 00:13:31 +00:00
patch := map [ string ] interface { } {
2018-05-11 18:50:32 +00:00
"status" : map [ string ] interface { } {
"operationState" : state ,
} ,
2018-07-14 00:13:31 +00:00
}
if state . Phase . Completed ( ) {
// If operation is completed, clear the operation field to indicate no operation is
// in progress.
patch [ "operation" ] = nil
}
if reflect . DeepEqual ( app . Status . OperationState , state ) {
log . Infof ( "No operation updates necessary to '%s'. Skipping patch" , app . Name )
return nil
}
patchJSON , err := json . Marshal ( patch )
2018-05-15 07:36:11 +00:00
if err != nil {
return err
}
appClient := ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( ctrl . namespace )
2018-07-14 00:13:31 +00:00
_ , err = appClient . Patch ( app . Name , types . MergePatchType , patchJSON )
2018-05-15 07:36:11 +00:00
if err != nil {
return err
2018-05-11 18:50:32 +00:00
}
2018-07-07 07:54:06 +00:00
log . Infof ( "updated '%s' operation (phase: %s)" , app . Name , state . Phase )
2018-09-24 15:52:43 +00:00
if state . Phase . Completed ( ) {
eventInfo := argo . EventInfo { Reason : argo . EventReasonOperationCompleted }
2018-11-27 21:38:00 +00:00
var messages [ ] string
if state . Operation . Sync != nil && len ( state . Operation . Sync . Resources ) > 0 {
messages = [ ] string { "Partial sync operation" }
} else {
messages = [ ] string { "Sync operation" }
}
if state . SyncResult != nil {
messages = append ( messages , "to" , state . SyncResult . Revision )
}
2018-09-24 15:52:43 +00:00
if state . Phase . Successful ( ) {
eventInfo . Type = v1 . EventTypeNormal
2018-11-27 21:38:00 +00:00
messages = append ( messages , "succeeded" )
2018-09-24 15:52:43 +00:00
} else {
eventInfo . Type = v1 . EventTypeWarning
2018-11-27 21:38:00 +00:00
messages = append ( messages , "failed:" , state . Message )
2018-09-24 15:52:43 +00:00
}
2018-11-27 21:38:00 +00:00
ctrl . auditLogger . LogAppEvent ( app , eventInfo , strings . Join ( messages , " " ) )
2018-09-24 15:52:43 +00:00
}
2018-05-15 07:36:11 +00:00
return nil
2018-05-11 18:50:32 +00:00
} , "Update application operation state" , context . Background ( ) , updateOperationStateTimeout )
}
2018-05-16 23:30:28 +00:00
func ( ctrl * ApplicationController ) processAppRefreshQueueItem ( ) ( processNext bool ) {
2018-05-11 18:50:32 +00:00
appKey , shutdown := ctrl . appRefreshQueue . Get ( )
2018-02-20 23:23:37 +00:00
if shutdown {
2018-05-16 23:30:28 +00:00
processNext = false
return
2018-02-20 23:23:37 +00:00
}
2018-07-12 19:39:46 +00:00
processNext = true
2018-05-16 23:30:28 +00:00
defer func ( ) {
if r := recover ( ) ; r != nil {
log . Errorf ( "Recovered from panic: %+v\n%s" , r , debug . Stack ( ) )
}
ctrl . appRefreshQueue . Done ( appKey )
} ( )
2018-02-22 18:56:14 +00:00
obj , exists , err := ctrl . appInformer . GetIndexer ( ) . GetByKey ( appKey . ( string ) )
if err != nil {
log . Errorf ( "Failed to get application '%s' from informer index: %+v" , appKey , err )
2018-05-16 23:30:28 +00:00
return
2018-02-22 18:56:14 +00:00
}
if ! exists {
// This happens after app was deleted, but the work queue still had an entry for it.
2018-05-16 23:30:28 +00:00
return
2018-02-22 18:56:14 +00:00
}
2018-12-04 10:52:57 +00:00
origApp , ok := obj . ( * appv1 . Application )
2018-02-22 18:56:14 +00:00
if ! ok {
log . Warnf ( "Key '%s' in index is not an application" , appKey )
2018-05-16 23:30:28 +00:00
return
2018-02-22 18:56:14 +00:00
}
2018-12-04 10:52:57 +00:00
if ! ctrl . needRefreshAppStatus ( origApp , ctrl . statusRefreshTimeout ) {
2018-07-12 02:12:30 +00:00
return
}
2018-12-04 10:52:57 +00:00
// NOTE: normalization returns a copy
app := ctrl . normalizeApplication ( origApp )
2018-07-10 21:45:18 +00:00
2018-07-12 02:12:30 +00:00
conditions , hasErrors := ctrl . refreshAppConditions ( app )
if hasErrors {
2018-12-04 10:52:57 +00:00
app . Status . Sync . Status = appv1 . SyncStatusCodeUnknown
app . Status . Health . Status = appv1 . HealthStatusUnknown
app . Status . Conditions = conditions
ctrl . persistAppStatus ( origApp , & app . Status )
2018-07-12 02:12:30 +00:00
return
}
2018-07-10 21:45:18 +00:00
2018-12-04 10:52:57 +00:00
compareResult , err := ctrl . appStateManager . CompareAppState ( app , "" , nil )
2018-07-12 02:12:30 +00:00
if err != nil {
conditions = append ( conditions , appv1 . ApplicationCondition { Type : appv1 . ApplicationConditionComparisonError , Message : err . Error ( ) } )
} else {
2018-12-04 10:52:57 +00:00
conditions = append ( conditions , compareResult . conditions ... )
2018-02-22 18:56:14 +00:00
}
2018-12-04 10:52:57 +00:00
ctrl . setAppManagedResources ( app . Name , compareResult . managedResources )
2018-09-11 21:28:53 +00:00
2018-12-04 10:52:57 +00:00
syncErrCond := ctrl . autoSync ( app , compareResult . syncStatus )
2018-09-11 21:28:53 +00:00
if syncErrCond != nil {
conditions = append ( conditions , * syncErrCond )
}
2018-12-04 10:52:57 +00:00
app . Status . ObservedAt = compareResult . observedAt
app . Status . Sync = * compareResult . syncStatus
app . Status . Health = * compareResult . healthStatus
app . Status . Resources = compareResult . resources
app . Status . Conditions = conditions
ctrl . persistAppStatus ( origApp , & app . Status )
2018-05-16 23:30:28 +00:00
return
2018-02-20 23:23:37 +00:00
}
2018-07-12 19:39:46 +00:00
// needRefreshAppStatus answers if application status needs to be refreshed.
// Returns true if application never been compared, has changed or comparison result has expired.
func ( ctrl * ApplicationController ) needRefreshAppStatus ( app * appv1 . Application , statusRefreshTimeout time . Duration ) bool {
2018-09-24 15:52:43 +00:00
logCtx := log . WithFields ( log . Fields { "application" : app . Name } )
2018-07-12 19:39:46 +00:00
var reason string
2018-12-04 10:52:57 +00:00
expired := app . Status . ObservedAt . Add ( statusRefreshTimeout ) . Before ( time . Now ( ) . UTC ( ) )
2018-07-12 19:39:46 +00:00
if ctrl . isRefreshForced ( app . Name ) {
reason = "force refresh"
2018-12-04 10:52:57 +00:00
} else if app . Status . Sync . Status == appv1 . SyncStatusCodeUnknown && expired {
2018-07-12 19:39:46 +00:00
reason = "comparison status unknown"
2018-12-04 10:52:57 +00:00
} else if ! app . Spec . Source . Equals ( app . Status . Sync . ComparedTo ) {
2018-07-12 19:39:46 +00:00
reason = "spec.source differs"
2018-09-10 17:58:13 +00:00
} else if expired {
2018-12-04 10:52:57 +00:00
reason = fmt . Sprintf ( "comparison expired. observedAt: %v, expiry: %v" , app . Status . ObservedAt , statusRefreshTimeout )
2018-07-12 19:39:46 +00:00
}
if reason != "" {
2018-09-24 15:52:43 +00:00
logCtx . Infof ( "Refreshing app status (%s)" , reason )
2018-07-12 19:39:46 +00:00
return true
}
return false
}
2018-07-09 17:45:03 +00:00
func ( ctrl * ApplicationController ) refreshAppConditions ( app * appv1 . Application ) ( [ ] appv1 . ApplicationCondition , bool ) {
2018-07-10 21:45:18 +00:00
conditions := make ( [ ] appv1 . ApplicationCondition , 0 )
2018-07-09 17:45:03 +00:00
proj , err := argo . GetAppProject ( & app . Spec , ctrl . applicationClientset , ctrl . namespace )
if err != nil {
2018-07-10 21:45:18 +00:00
if errors . IsNotFound ( err ) {
conditions = append ( conditions , appv1 . ApplicationCondition {
Type : appv1 . ApplicationConditionInvalidSpecError ,
Message : fmt . Sprintf ( "Application referencing project %s which does not exist" , app . Spec . Project ) ,
} )
} else {
conditions = append ( conditions , appv1 . ApplicationCondition {
Type : appv1 . ApplicationConditionUnknownError ,
Message : err . Error ( ) ,
} )
}
2018-07-09 17:45:03 +00:00
} else {
2018-07-10 21:45:18 +00:00
specConditions , err := argo . GetSpecErrors ( context . Background ( ) , & app . Spec , proj , ctrl . repoClientset , ctrl . db )
2018-07-09 17:45:03 +00:00
if err != nil {
2018-07-10 21:45:18 +00:00
conditions = append ( conditions , appv1 . ApplicationCondition {
Type : appv1 . ApplicationConditionUnknownError ,
Message : err . Error ( ) ,
} )
2018-07-09 17:45:03 +00:00
} else {
2018-07-10 21:45:18 +00:00
conditions = append ( conditions , specConditions ... )
2018-07-09 17:45:03 +00:00
}
}
2018-07-10 21:45:18 +00:00
// List of condition types which have to be reevaluated by controller; all remaining conditions should stay as is.
reevaluateTypes := map [ appv1 . ApplicationConditionType ] bool {
2018-07-11 20:00:48 +00:00
appv1 . ApplicationConditionInvalidSpecError : true ,
appv1 . ApplicationConditionUnknownError : true ,
appv1 . ApplicationConditionComparisonError : true ,
appv1 . ApplicationConditionSharedResourceWarning : true ,
2018-09-11 21:28:53 +00:00
appv1 . ApplicationConditionSyncError : true ,
2018-07-10 21:45:18 +00:00
}
appConditions := make ( [ ] appv1 . ApplicationCondition , 0 )
2018-07-09 17:45:03 +00:00
for i := 0 ; i < len ( app . Status . Conditions ) ; i ++ {
condition := app . Status . Conditions [ i ]
2018-07-10 21:45:18 +00:00
if _ , ok := reevaluateTypes [ condition . Type ] ; ! ok {
appConditions = append ( appConditions , condition )
2018-07-09 17:45:03 +00:00
}
}
2018-07-10 21:45:18 +00:00
hasErrors := false
for i := range conditions {
condition := conditions [ i ]
appConditions = append ( appConditions , condition )
if condition . IsError ( ) {
hasErrors = true
2018-07-09 17:45:03 +00:00
}
2018-05-03 22:55:01 +00:00
2018-05-07 15:38:25 +00:00
}
2018-07-10 21:45:18 +00:00
return appConditions , hasErrors
2018-05-07 15:38:25 +00:00
}
2018-11-30 21:50:27 +00:00
// normalizeApplication normalizes an application.spec and additionally persists updates if it changed
// Always returns a copy of the application
func ( ctrl * ApplicationController ) normalizeApplication ( app * appv1 . Application ) * appv1 . Application {
logCtx := log . WithFields ( log . Fields { "application" : app . Name } )
appClient := ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( app . Namespace )
modifiedApp := app . DeepCopy ( )
modifiedApp . Spec = * argo . NormalizeApplicationSpec ( & app . Spec )
patch , modified , err := diff . CreateTwoWayMergePatch ( app , modifiedApp , appv1 . Application { } )
if err != nil {
logCtx . Errorf ( "error constructing app spec patch: %v" , err )
return modifiedApp
} else if modified {
_ , err = appClient . Patch ( app . Name , types . MergePatchType , patch )
if err != nil {
logCtx . Errorf ( "Error persisting normalized application spec: %v" , err )
} else {
logCtx . Infof ( "Normalized app spec: %s" , string ( patch ) )
}
}
return modifiedApp
}
// persistAppStatus persists updates to application status. If no changes were made, it is a no-op
2018-12-04 10:52:57 +00:00
func ( ctrl * ApplicationController ) persistAppStatus ( orig * appv1 . Application , newStatus * appv1 . ApplicationStatus ) {
logCtx := log . WithFields ( log . Fields { "application" : orig . Name } )
if orig . Status . Sync . Status != newStatus . Sync . Status {
message := fmt . Sprintf ( "Updated sync status: %s -> %s" , orig . Status . Sync . Status , newStatus . Sync . Status )
ctrl . auditLogger . LogAppEvent ( orig , argo . EventInfo { Reason : argo . EventReasonResourceUpdated , Type : v1 . EventTypeNormal } , message )
2018-07-12 19:39:46 +00:00
}
2018-12-04 10:52:57 +00:00
if orig . Status . Health . Status != newStatus . Health . Status {
message := fmt . Sprintf ( "Updated health status: %s -> %s" , orig . Status . Health . Status , newStatus . Health . Status )
ctrl . auditLogger . LogAppEvent ( orig , argo . EventInfo { Reason : argo . EventReasonResourceUpdated , Type : v1 . EventTypeNormal } , message )
2018-07-12 19:39:46 +00:00
}
2018-12-04 10:52:57 +00:00
patch , modified , err := diff . CreateTwoWayMergePatch ( & appv1 . Application { Status : orig . Status } , & appv1 . Application { Status : * newStatus } , appv1 . Application { } )
2018-07-12 19:39:46 +00:00
if err != nil {
2018-11-30 21:50:27 +00:00
logCtx . Errorf ( "Error constructing app status patch: %v" , err )
2018-07-12 19:39:46 +00:00
return
}
2018-11-30 21:50:27 +00:00
if ! modified {
2018-09-24 15:52:43 +00:00
logCtx . Infof ( "No status changes. Skipping patch" )
2018-07-12 19:39:46 +00:00
return
2018-05-03 22:55:01 +00:00
}
2018-12-04 10:52:57 +00:00
appClient := ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( orig . Namespace )
_ , err = appClient . Patch ( orig . Name , types . MergePatchType , patch )
2018-02-22 18:56:14 +00:00
if err != nil {
2018-09-24 15:52:43 +00:00
logCtx . Warnf ( "Error updating application: %v" , err )
2018-04-11 19:53:33 +00:00
} else {
2018-09-24 15:52:43 +00:00
logCtx . Infof ( "Update successful" )
2018-02-22 18:56:14 +00:00
}
}
2018-09-11 21:28:53 +00:00
// autoSync will initiate a sync operation for an application configured with automated sync
2018-12-04 10:52:57 +00:00
func ( ctrl * ApplicationController ) autoSync ( app * appv1 . Application , syncStatus * appv1 . SyncStatus ) * appv1 . ApplicationCondition {
2018-09-11 21:28:53 +00:00
if app . Spec . SyncPolicy == nil || app . Spec . SyncPolicy . Automated == nil {
return nil
}
logCtx := log . WithFields ( log . Fields { "application" : app . Name } )
if app . Operation != nil {
logCtx . Infof ( "Skipping auto-sync: another operation is in progress" )
return nil
}
// Only perform auto-sync if we detect OutOfSync status. This is to prevent us from attempting
// a sync when application is already in a Synced or Unknown state
2018-12-04 10:52:57 +00:00
if syncStatus . Status != appv1 . SyncStatusCodeOutOfSync {
logCtx . Infof ( "Skipping auto-sync: application status is %s" , syncStatus . Status )
2018-09-11 21:28:53 +00:00
return nil
}
2018-12-04 10:52:57 +00:00
desiredCommitSHA := syncStatus . Revision
2018-09-24 15:52:43 +00:00
2018-09-11 21:28:53 +00:00
// It is possible for manifests to remain OutOfSync even after a sync/kubectl apply (e.g.
// auto-sync with pruning disabled). We need to ensure that we do not keep Syncing an
// application in an infinite loop. To detect this, we only attempt the Sync if the revision
2018-09-24 15:52:43 +00:00
// and parameter overrides are different from our most recent sync operation.
if alreadyAttemptedSync ( app , desiredCommitSHA ) {
if app . Status . OperationState . Phase != appv1 . OperationSucceeded {
2018-09-11 21:28:53 +00:00
logCtx . Warnf ( "Skipping auto-sync: failed previous sync attempt to %s" , desiredCommitSHA )
message := fmt . Sprintf ( "Failed sync attempt to %s: %s" , desiredCommitSHA , app . Status . OperationState . Message )
return & appv1 . ApplicationCondition { Type : appv1 . ApplicationConditionSyncError , Message : message }
}
2018-09-24 15:52:43 +00:00
logCtx . Infof ( "Skipping auto-sync: most recent sync already to %s" , desiredCommitSHA )
return nil
2018-09-11 21:28:53 +00:00
}
2018-02-26 15:43:35 +00:00
2018-09-11 21:28:53 +00:00
op := appv1 . Operation {
Sync : & appv1 . SyncOperation {
2018-09-24 15:52:43 +00:00
Revision : desiredCommitSHA ,
Prune : app . Spec . SyncPolicy . Automated . Prune ,
ParameterOverrides : app . Spec . Source . ComponentParameterOverrides ,
2018-02-26 15:43:35 +00:00
} ,
2018-09-11 21:28:53 +00:00
}
appIf := ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( app . Namespace )
2018-11-17 01:10:04 +00:00
_ , err := argo . SetAppOperation ( appIf , app . Name , & op )
2018-09-11 21:28:53 +00:00
if err != nil {
logCtx . Errorf ( "Failed to initiate auto-sync to %s: %v" , desiredCommitSHA , err )
return & appv1 . ApplicationCondition { Type : appv1 . ApplicationConditionSyncError , Message : err . Error ( ) }
}
2018-09-24 15:52:43 +00:00
message := fmt . Sprintf ( "Initiated automated sync to '%s'" , desiredCommitSHA )
ctrl . auditLogger . LogAppEvent ( app , argo . EventInfo { Reason : argo . EventReasonOperationStarted , Type : v1 . EventTypeNormal } , message )
logCtx . Info ( message )
2018-09-11 21:28:53 +00:00
return nil
}
2018-09-24 15:52:43 +00:00
// alreadyAttemptedSync returns whether or not the most recent sync was performed against the
// commitSHA and with the same parameter overrides which are currently set in the app
func alreadyAttemptedSync ( app * appv1 . Application , commitSHA string ) bool {
if app . Status . OperationState == nil || app . Status . OperationState . Operation . Sync == nil || app . Status . OperationState . SyncResult == nil {
return false
}
if app . Status . OperationState . SyncResult . Revision != commitSHA {
return false
}
if ! reflect . DeepEqual ( appv1 . ParameterOverrides ( app . Spec . Source . ComponentParameterOverrides ) , app . Status . OperationState . Operation . Sync . ParameterOverrides ) {
return false
}
return true
}
2018-09-11 21:28:53 +00:00
func ( ctrl * ApplicationController ) newApplicationInformer ( ) cache . SharedIndexInformer {
appInformerFactory := appinformers . NewFilteredSharedInformerFactory (
ctrl . applicationClientset ,
ctrl . statusRefreshTimeout ,
ctrl . namespace ,
func ( options * metav1 . ListOptions ) { } ,
2018-02-20 23:23:37 +00:00
)
informer := appInformerFactory . Argoproj ( ) . V1alpha1 ( ) . Applications ( ) . Informer ( )
informer . AddEventHandler (
cache . ResourceEventHandlerFuncs {
AddFunc : func ( obj interface { } ) {
key , err := cache . MetaNamespaceKeyFunc ( obj )
if err == nil {
2018-09-11 21:28:53 +00:00
ctrl . appRefreshQueue . Add ( key )
ctrl . appOperationQueue . Add ( key )
2018-02-20 23:23:37 +00:00
}
} ,
UpdateFunc : func ( old , new interface { } ) {
key , err := cache . MetaNamespaceKeyFunc ( new )
2018-09-11 21:28:53 +00:00
if err != nil {
return
}
oldApp , oldOK := old . ( * appv1 . Application )
newApp , newOK := new . ( * appv1 . Application )
if oldOK && newOK {
if toggledAutomatedSync ( oldApp , newApp ) {
log . WithField ( "application" , newApp . Name ) . Info ( "Enabled automated sync" )
ctrl . forceAppRefresh ( newApp . Name )
}
2018-02-20 23:23:37 +00:00
}
2018-09-11 21:28:53 +00:00
ctrl . appRefreshQueue . Add ( key )
ctrl . appOperationQueue . Add ( key )
2018-02-20 23:23:37 +00:00
} ,
DeleteFunc : func ( obj interface { } ) {
// IndexerInformer uses a delta queue, therefore for deletes we have to use this
// key function.
key , err := cache . DeletionHandlingMetaNamespaceKeyFunc ( obj )
if err == nil {
2018-09-11 21:28:53 +00:00
ctrl . appRefreshQueue . Add ( key )
2018-02-20 23:23:37 +00:00
}
} ,
} ,
)
return informer
}
2018-07-07 07:54:06 +00:00
2018-07-14 00:13:31 +00:00
func isOperationInProgress ( app * appv1 . Application ) bool {
2018-07-07 07:54:06 +00:00
return app . Status . OperationState != nil && ! app . Status . OperationState . Phase . Completed ( )
}
2018-09-11 21:28:53 +00:00
// toggledAutomatedSync tests if an app went from auto-sync disabled to enabled.
// if it was toggled to be enabled, the informer handler will force a refresh
func toggledAutomatedSync ( old * appv1 . Application , new * appv1 . Application ) bool {
if new . Spec . SyncPolicy == nil || new . Spec . SyncPolicy . Automated == nil {
return false
}
// auto-sync is enabled. check if it was previously disabled
if old . Spec . SyncPolicy == nil || old . Spec . SyncPolicy . Automated == nil {
return true
}
// nothing changed
return false
}