2018-02-19 11:39:03 +00:00
package application
import (
2022-07-20 19:32:03 +00:00
"context"
2018-05-01 18:05:56 +00:00
"encoding/json"
2019-10-10 22:17:13 +00:00
"errors"
2018-06-13 20:05:39 +00:00
"fmt"
2026-02-09 12:37:34 +00:00
"maps"
2020-09-17 23:21:25 +00:00
"math"
2018-05-07 20:20:38 +00:00
"reflect"
2025-06-06 11:27:02 +00:00
"slices"
2020-03-18 00:10:54 +00:00
"sort"
2020-03-16 18:51:59 +00:00
"strconv"
2018-04-06 20:08:29 +00:00
"strings"
2018-04-20 08:19:53 +00:00
"time"
2018-04-06 20:08:29 +00:00
2025-02-11 01:38:20 +00:00
cacheutil "github.com/argoproj/argo-cd/v3/util/cache"
2026-02-12 14:29:40 +00:00
kubecache "github.com/argoproj/argo-cd/gitops-engine/pkg/cache"
"github.com/argoproj/argo-cd/gitops-engine/pkg/diff"
"github.com/argoproj/argo-cd/gitops-engine/pkg/health"
"github.com/argoproj/argo-cd/gitops-engine/pkg/sync/common"
"github.com/argoproj/argo-cd/gitops-engine/pkg/utils/kube"
"github.com/argoproj/argo-cd/gitops-engine/pkg/utils/text"
2025-04-16 20:15:42 +00:00
"github.com/argoproj/pkg/v2/sync"
2019-02-28 21:11:47 +00:00
jsonpatch "github.com/evanphx/json-patch"
2018-02-28 05:37:23 +00:00
log "github.com/sirupsen/logrus"
2018-04-20 08:19:53 +00:00
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
2025-01-03 16:10:00 +00:00
corev1 "k8s.io/api/core/v1"
2025-01-03 17:09:37 +00:00
apierrors "k8s.io/apimachinery/pkg/api/errors"
2018-02-19 11:39:03 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2019-01-10 19:37:32 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2018-05-30 22:30:58 +00:00
"k8s.io/apimachinery/pkg/fields"
2020-03-16 18:51:59 +00:00
"k8s.io/apimachinery/pkg/labels"
2023-06-23 18:45:53 +00:00
"k8s.io/apimachinery/pkg/runtime/schema"
2018-05-01 18:05:56 +00:00
"k8s.io/apimachinery/pkg/types"
2019-05-13 21:58:22 +00:00
"k8s.io/apimachinery/pkg/watch"
2018-02-19 11:39:03 +00:00
"k8s.io/client-go/kubernetes"
2018-04-09 17:39:46 +00:00
"k8s.io/client-go/rest"
2020-06-29 22:32:18 +00:00
"k8s.io/client-go/tools/cache"
2018-06-06 04:44:14 +00:00
2025-01-10 21:14:00 +00:00
argocommon "github.com/argoproj/argo-cd/v3/common"
"github.com/argoproj/argo-cd/v3/pkg/apiclient/application"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
2025-08-12 17:02:21 +00:00
2025-01-10 21:14:00 +00:00
appclientset "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned"
applisters "github.com/argoproj/argo-cd/v3/pkg/client/listers/application/v1alpha1"
"github.com/argoproj/argo-cd/v3/reposerver/apiclient"
2026-02-26 15:07:00 +00:00
"github.com/argoproj/argo-cd/v3/server/broadcast"
2025-01-10 21:14:00 +00:00
servercache "github.com/argoproj/argo-cd/v3/server/cache"
"github.com/argoproj/argo-cd/v3/server/deeplinks"
2025-05-20 19:48:09 +00:00
applog "github.com/argoproj/argo-cd/v3/util/app/log"
2025-01-10 21:14:00 +00:00
"github.com/argoproj/argo-cd/v3/util/argo"
"github.com/argoproj/argo-cd/v3/util/collections"
"github.com/argoproj/argo-cd/v3/util/db"
"github.com/argoproj/argo-cd/v3/util/env"
"github.com/argoproj/argo-cd/v3/util/git"
2025-05-21 16:25:32 +00:00
utilio "github.com/argoproj/argo-cd/v3/util/io"
2025-01-10 21:14:00 +00:00
"github.com/argoproj/argo-cd/v3/util/lua"
"github.com/argoproj/argo-cd/v3/util/manifeststream"
"github.com/argoproj/argo-cd/v3/util/rbac"
"github.com/argoproj/argo-cd/v3/util/security"
"github.com/argoproj/argo-cd/v3/util/session"
"github.com/argoproj/argo-cd/v3/util/settings"
2023-03-24 14:43:43 +00:00
2026-02-12 14:29:40 +00:00
resourceutil "github.com/argoproj/argo-cd/gitops-engine/pkg/sync/resource"
2025-08-12 17:02:21 +00:00
2025-01-10 21:14:00 +00:00
applicationType "github.com/argoproj/argo-cd/v3/pkg/apis/application"
2025-08-12 17:02:21 +00:00
argodiff "github.com/argoproj/argo-cd/v3/util/argo/diff"
"github.com/argoproj/argo-cd/v3/util/argo/normalizers"
kubeutil "github.com/argoproj/argo-cd/v3/util/kube"
2018-02-19 11:39:03 +00:00
)
2025-01-06 16:34:32 +00:00
type AppResourceTreeFn func ( ctx context . Context , app * v1alpha1 . Application ) ( * v1alpha1 . ApplicationTree , error )
2022-04-19 18:36:05 +00:00
2021-03-15 16:27:41 +00:00
const (
backgroundPropagationPolicy string = "background"
foregroundPropagationPolicy string = "foreground"
)
2021-02-08 17:27:24 +00:00
2025-02-11 01:38:20 +00:00
var (
ErrCacheMiss = cacheutil . ErrCacheMiss
watchAPIBufferSize = env . ParseNumFromEnv ( argocommon . EnvWatchAPIBufferSize , 1000 , 0 , math . MaxInt32 )
)
2020-09-17 23:21:25 +00:00
2022-05-17 02:10:27 +00:00
// Server provides an Application service
2018-02-19 11:39:03 +00:00
type Server struct {
2025-02-28 14:43:53 +00:00
ns string
kubeclientset kubernetes . Interface
appclientset appclientset . Interface
appLister applisters . ApplicationLister
appInformer cache . SharedIndexInformer
2026-02-26 15:07:00 +00:00
appBroadcaster broadcast . Broadcaster [ v1alpha1 . ApplicationWatchEvent ]
2025-02-28 14:43:53 +00:00
repoClientset apiclient . Clientset
kubectl kube . Kubectl
db db . ArgoDB
enf * rbac . Enforcer
projectLock sync . KeyLock
auditLogger * argo . AuditLogger
settingsMgr * settings . SettingsManager
cache * servercache . Cache
projInformer cache . SharedIndexInformer
enabledNamespaces [ ] string
syncWithReplaceAllowed bool
2018-02-19 11:39:03 +00:00
}
2018-02-28 11:07:56 +00:00
// NewServer returns a new instance of the Application service
2018-03-07 22:34:43 +00:00
func NewServer (
namespace string ,
kubeclientset kubernetes . Interface ,
appclientset appclientset . Interface ,
2022-08-10 09:39:10 +00:00
appLister applisters . ApplicationLister ,
2020-06-29 22:32:18 +00:00
appInformer cache . SharedIndexInformer ,
2026-02-26 15:07:00 +00:00
appBroadcaster broadcast . Broadcaster [ v1alpha1 . ApplicationWatchEvent ] ,
2019-07-13 00:17:23 +00:00
repoClientset apiclient . Clientset ,
2019-10-16 22:46:45 +00:00
cache * servercache . Cache ,
2018-09-10 17:14:14 +00:00
kubectl kube . Kubectl ,
2018-05-14 18:36:08 +00:00
db db . ArgoDB ,
2018-06-06 04:44:14 +00:00
enf * rbac . Enforcer ,
2020-09-17 21:05:27 +00:00
projectLock sync . KeyLock ,
2018-12-24 06:25:04 +00:00
settingsMgr * settings . SettingsManager ,
2020-10-13 22:12:05 +00:00
projInformer cache . SharedIndexInformer ,
2022-08-10 09:39:10 +00:00
enabledNamespaces [ ] string ,
2024-09-23 03:02:17 +00:00
enableK8sEvent [ ] string ,
2025-02-28 14:43:53 +00:00
syncWithReplaceAllowed bool ,
2022-04-19 18:36:05 +00:00
) ( application . ApplicationServiceServer , AppResourceTreeFn ) {
2023-03-23 13:22:05 +00:00
if appBroadcaster == nil {
2026-02-26 15:07:00 +00:00
appBroadcaster = broadcast . NewHandler [ v1alpha1 . Application , v1alpha1 . ApplicationWatchEvent ] (
func ( app * v1alpha1 . Application , eventType watch . EventType ) * v1alpha1 . ApplicationWatchEvent {
return & v1alpha1 . ApplicationWatchEvent { Application : * app , Type : eventType }
} ,
applog . GetAppLogFields ,
)
2023-03-23 13:22:05 +00:00
}
2026-02-26 15:07:00 +00:00
// Register Application-level broadcaster to receive create/update/delete events
// and handle general application event processing.
2023-10-18 15:17:00 +00:00
_ , err := appInformer . AddEventHandler ( appBroadcaster )
if err != nil {
log . Error ( err )
}
2022-04-19 18:36:05 +00:00
s := & Server {
2025-02-28 14:43:53 +00:00
ns : namespace ,
2025-03-10 14:29:07 +00:00
appclientset : & deepCopyAppClientset { appclientset } ,
appLister : & deepCopyApplicationLister { appLister } ,
2025-02-28 14:43:53 +00:00
appInformer : appInformer ,
appBroadcaster : appBroadcaster ,
kubeclientset : kubeclientset ,
cache : cache ,
db : db ,
repoClientset : repoClientset ,
kubectl : kubectl ,
enf : enf ,
projectLock : projectLock ,
2026-03-04 13:51:48 +00:00
auditLogger : argo . NewAuditLogger ( kubeclientset , namespace , "argocd-server" , enableK8sEvent ) ,
2025-02-28 14:43:53 +00:00
settingsMgr : settingsMgr ,
projInformer : projInformer ,
enabledNamespaces : enabledNamespaces ,
syncWithReplaceAllowed : syncWithReplaceAllowed ,
2018-02-19 11:39:03 +00:00
}
2022-08-17 15:48:27 +00:00
return s , s . getAppResources
2018-06-12 17:43:16 +00:00
}
2023-03-23 13:22:05 +00:00
// getAppEnforceRBAC gets the Application with the given name in the given namespace. If no namespace is
// specified, the Application is fetched from the default namespace (the one in which the API server is running).
//
2023-07-19 13:22:28 +00:00
// If the user does not provide a "project," then we have to be very careful how we respond. If an app with the given
// name exists, and the user has access to that app in the app's project, we return the app. If the app exists but the
// user does not have access, we return "permission denied." If the app does not exist, we return "permission denied" -
// if we responded with a 404, then the user could infer that the app exists when they get "permission denied."
2023-03-23 13:22:05 +00:00
//
2023-07-19 13:22:28 +00:00
// If the user does provide a "project," we can respond more specifically. If the user does not have access to the given
// app name in the given project, we return "permission denied." If the app exists, but the project is different from
2025-01-06 16:34:32 +00:00
func ( s * Server ) getAppEnforceRBAC ( ctx context . Context , action , project , namespace , name string , getApp func ( ) ( * v1alpha1 . Application , error ) ) ( * v1alpha1 . Application , * v1alpha1 . AppProject , error ) {
2023-12-18 14:41:32 +00:00
user := session . Username ( ctx )
if user == "" {
user = "Unknown user"
}
2025-01-02 23:26:59 +00:00
logCtx := log . WithFields ( map [ string ] any {
2023-12-18 14:41:32 +00:00
"user" : user ,
2023-03-23 13:22:05 +00:00
"application" : name ,
"namespace" : namespace ,
} )
2023-07-19 13:22:28 +00:00
if project != "" {
// The user has provided everything we need to perform an initial RBAC check.
givenRBACName := security . RBACName ( s . ns , project , namespace , name )
2025-02-20 10:40:15 +00:00
if err := s . enf . EnforceErr ( ctx . Value ( "claims" ) , rbac . ResourceApplications , action , givenRBACName ) ; err != nil {
2025-01-02 23:26:59 +00:00
logCtx . WithFields ( map [ string ] any {
2023-07-19 13:22:28 +00:00
"project" : project ,
argocommon . SecurityField : argocommon . SecurityMedium ,
} ) . Warnf ( "user tried to %s application which they do not have access to: %s" , action , err )
// Do a GET on the app. This ensures that the timing of a "no access" response is the same as a "yes access,
// but the app is in a different project" response. We don't want the user inferring the existence of the
// app from response time.
_ , _ = getApp ( )
2024-12-31 08:29:01 +00:00
return nil , nil , argocommon . PermissionDeniedAPIError
2023-07-19 13:22:28 +00:00
}
}
2023-03-23 13:22:05 +00:00
a , err := getApp ( )
if err != nil {
2025-01-03 17:09:37 +00:00
if apierrors . IsNotFound ( err ) {
2023-07-19 13:22:28 +00:00
if project != "" {
// We know that the user was allowed to get the Application, but the Application does not exist. Return 404.
2025-01-03 17:09:37 +00:00
return nil , nil , status . Error ( codes . NotFound , apierrors . NewNotFound ( schema . GroupResource { Group : "argoproj.io" , Resource : "applications" } , name ) . Error ( ) )
2023-07-19 13:22:28 +00:00
}
// We don't know if the user was allowed to get the Application, and we don't want to leak information about
// the Application's existence. Return 403.
2023-03-23 13:22:05 +00:00
logCtx . Warn ( "application does not exist" )
2024-12-31 08:29:01 +00:00
return nil , nil , argocommon . PermissionDeniedAPIError
2023-03-23 13:22:05 +00:00
}
logCtx . Errorf ( "failed to get application: %s" , err )
2024-12-31 08:29:01 +00:00
return nil , nil , argocommon . PermissionDeniedAPIError
2023-03-23 13:22:05 +00:00
}
2023-07-19 13:22:28 +00:00
// Even if we performed an initial RBAC check (because the request was fully parameterized), we still need to
// perform a second RBAC check to ensure that the user has access to the actual Application's project (not just the
// project they specified in the request).
2025-02-20 10:40:15 +00:00
if err := s . enf . EnforceErr ( ctx . Value ( "claims" ) , rbac . ResourceApplications , action , a . RBACName ( s . ns ) ) ; err != nil {
2025-01-02 23:26:59 +00:00
logCtx . WithFields ( map [ string ] any {
2023-03-23 13:22:05 +00:00
"project" : a . Spec . Project ,
argocommon . SecurityField : argocommon . SecurityMedium ,
} ) . Warnf ( "user tried to %s application which they do not have access to: %s" , action , err )
2023-07-19 13:22:28 +00:00
if project != "" {
// The user specified a project. We would have returned a 404 if the user had access to the app, but the app
// did not exist. So we have to return a 404 when the app does exist, but the user does not have access.
// Otherwise, they could infer that the app exists based on the error code.
2025-01-03 17:09:37 +00:00
return nil , nil , status . Error ( codes . NotFound , apierrors . NewNotFound ( schema . GroupResource { Group : "argoproj.io" , Resource : "applications" } , name ) . Error ( ) )
2023-07-19 13:22:28 +00:00
}
// The user didn't specify a project. We always return permission denied for both lack of access and lack of
// existence.
2024-12-31 08:29:01 +00:00
return nil , nil , argocommon . PermissionDeniedAPIError
2023-03-23 13:22:05 +00:00
}
2023-07-19 13:22:28 +00:00
effectiveProject := "default"
if a . Spec . Project != "" {
effectiveProject = a . Spec . Project
}
if project != "" && effectiveProject != project {
2025-01-02 23:26:59 +00:00
logCtx . WithFields ( map [ string ] any {
2023-07-19 13:22:28 +00:00
"project" : a . Spec . Project ,
argocommon . SecurityField : argocommon . SecurityMedium ,
} ) . Warnf ( "user tried to %s application in project %s, but the application is in project %s" , action , project , effectiveProject )
// The user has access to the app, but the app is in a different project. Return 404, meaning "app doesn't
// exist in that project".
2025-01-03 17:09:37 +00:00
return nil , nil , status . Error ( codes . NotFound , apierrors . NewNotFound ( schema . GroupResource { Group : "argoproj.io" , Resource : "applications" } , name ) . Error ( ) )
2023-07-19 13:22:28 +00:00
}
2024-04-15 07:20:07 +00:00
// Get the app's associated project, and make sure all project restrictions are enforced.
proj , err := s . getAppProject ( ctx , a , logCtx )
if err != nil {
return a , nil , err
}
return a , proj , nil
2023-03-23 13:22:05 +00:00
}
// getApplicationEnforceRBACInformer uses an informer to get an Application. If the app does not exist, permission is
// denied, or any other error occurs when getting the app, we return a permission denied error to obscure any sensitive
// information.
2025-01-06 16:34:32 +00:00
func ( s * Server ) getApplicationEnforceRBACInformer ( ctx context . Context , action , project , namespace , name string ) ( * v1alpha1 . Application , * v1alpha1 . AppProject , error ) {
2023-03-23 13:22:05 +00:00
namespaceOrDefault := s . appNamespaceOrDefault ( namespace )
2025-01-06 16:34:32 +00:00
return s . getAppEnforceRBAC ( ctx , action , project , namespaceOrDefault , name , func ( ) ( * v1alpha1 . Application , error ) {
2025-08-12 17:02:21 +00:00
if ! s . isNamespaceEnabled ( namespaceOrDefault ) {
return nil , security . NamespaceNotPermittedError ( namespaceOrDefault )
}
2023-03-23 13:22:05 +00:00
return s . appLister . Applications ( namespaceOrDefault ) . Get ( name )
} )
}
// getApplicationEnforceRBACClient uses a client to get an Application. If the app does not exist, permission is denied,
// or any other error occurs when getting the app, we return a permission denied error to obscure any sensitive
// information.
2025-01-06 16:34:32 +00:00
func ( s * Server ) getApplicationEnforceRBACClient ( ctx context . Context , action , project , namespace , name , resourceVersion string ) ( * v1alpha1 . Application , * v1alpha1 . AppProject , error ) {
2023-03-23 13:22:05 +00:00
namespaceOrDefault := s . appNamespaceOrDefault ( namespace )
2025-01-06 16:34:32 +00:00
return s . getAppEnforceRBAC ( ctx , action , project , namespaceOrDefault , name , func ( ) ( * v1alpha1 . Application , error ) {
2023-09-11 23:17:02 +00:00
if ! s . isNamespaceEnabled ( namespaceOrDefault ) {
return nil , security . NamespaceNotPermittedError ( namespaceOrDefault )
}
2025-03-04 11:29:23 +00:00
app , err := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( namespaceOrDefault ) . Get ( ctx , name , metav1 . GetOptions {
2023-03-23 13:22:05 +00:00
ResourceVersion : resourceVersion ,
} )
2025-03-04 11:29:23 +00:00
if err != nil {
return nil , err
}
2025-03-10 14:29:07 +00:00
return app , nil
2023-03-23 13:22:05 +00:00
} )
}
2018-02-19 11:39:03 +00:00
// List returns list of applications
2025-01-06 16:34:32 +00:00
func ( s * Server ) List ( ctx context . Context , q * application . ApplicationQuery ) ( * v1alpha1 . ApplicationList , error ) {
2022-10-03 13:47:13 +00:00
selector , err := labels . Parse ( q . GetSelector ( ) )
2020-03-16 18:51:59 +00:00
if err != nil {
2022-10-03 13:47:13 +00:00
return nil , fmt . Errorf ( "error parsing the selector: %w" , err )
2020-03-16 18:51:59 +00:00
}
2025-01-06 16:34:32 +00:00
var apps [ ] * v1alpha1 . Application
2022-08-10 09:39:10 +00:00
if q . GetAppNamespace ( ) == "" {
2022-10-03 13:47:13 +00:00
apps , err = s . appLister . List ( selector )
2022-08-10 09:39:10 +00:00
} else {
2022-10-03 13:47:13 +00:00
apps , err = s . appLister . Applications ( q . GetAppNamespace ( ) ) . List ( selector )
2022-08-10 09:39:10 +00:00
}
2018-06-06 04:44:14 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error listing apps with selectors: %w" , err )
2018-06-06 04:44:14 +00:00
}
2023-03-30 14:04:41 +00:00
filteredApps := apps
// Filter applications by name
if q . Name != nil {
2025-01-06 16:34:32 +00:00
filteredApps = argo . FilterByNameP ( filteredApps , * q . Name )
2023-03-30 14:04:41 +00:00
}
// Filter applications by projects
2025-01-06 16:34:32 +00:00
filteredApps = argo . FilterByProjectsP ( filteredApps , getProjectsFromApplicationQuery ( * q ) )
2023-03-30 14:04:41 +00:00
// Filter applications by source repo URL
2025-01-06 16:34:32 +00:00
filteredApps = argo . FilterByRepoP ( filteredApps , q . GetRepo ( ) )
2023-03-30 14:04:41 +00:00
2025-01-06 16:34:32 +00:00
newItems := make ( [ ] v1alpha1 . Application , 0 )
2023-03-30 14:04:41 +00:00
for _ , a := range filteredApps {
2023-03-14 20:33:45 +00:00
// Skip any application that is neither in the control plane's namespace
2022-08-10 09:39:10 +00:00
// nor in the list of enabled namespaces.
2023-07-18 14:37:27 +00:00
if ! s . isNamespaceEnabled ( a . Namespace ) {
2022-08-10 09:39:10 +00:00
continue
}
2025-02-20 10:40:15 +00:00
if s . enf . Enforce ( ctx . Value ( "claims" ) , rbac . ResourceApplications , rbac . ActionGet , a . RBACName ( s . ns ) ) {
2025-10-15 01:38:14 +00:00
// Create a deep copy to ensure all metadata fields including annotations are preserved
appCopy := a . DeepCopy ( )
// Explicitly copy annotations in case DeepCopy does not preserve them
if a . Annotations != nil {
appCopy . Annotations = a . Annotations
}
newItems = append ( newItems , * appCopy )
2018-06-06 04:44:14 +00:00
}
}
2022-08-10 09:39:10 +00:00
2021-03-12 17:07:22 +00:00
// Sort found applications by name
2020-03-18 00:10:54 +00:00
sort . Slice ( newItems , func ( i , j int ) bool {
return newItems [ i ] . Name < newItems [ j ] . Name
} )
2021-03-12 17:07:22 +00:00
2025-01-06 16:34:32 +00:00
appList := v1alpha1 . ApplicationList {
2020-09-02 22:37:37 +00:00
ListMeta : metav1 . ListMeta {
ResourceVersion : s . appInformer . LastSyncResourceVersion ( ) ,
} ,
2020-03-16 18:51:59 +00:00
Items : newItems ,
2018-09-07 20:51:32 +00:00
}
2020-03-16 18:51:59 +00:00
return & appList , nil
2018-02-19 11:39:03 +00:00
}
2018-04-20 08:19:53 +00:00
// Create creates an application
2025-01-06 16:34:32 +00:00
func ( s * Server ) Create ( ctx context . Context , q * application . ApplicationCreateRequest ) ( * v1alpha1 . Application , error ) {
2022-04-18 14:47:13 +00:00
if q . GetApplication ( ) == nil {
2024-12-30 08:56:41 +00:00
return nil , errors . New ( "error creating application: application is nil in request" )
2022-04-18 14:47:13 +00:00
}
2022-08-10 09:39:10 +00:00
a := q . GetApplication ( )
2025-02-20 10:40:15 +00:00
if err := s . enf . EnforceErr ( ctx . Value ( "claims" ) , rbac . ResourceApplications , rbac . ActionCreate , a . RBACName ( s . ns ) ) ; err != nil {
2019-02-28 21:11:47 +00:00
return nil , err
2018-06-06 04:44:14 +00:00
}
2018-06-22 17:05:57 +00:00
2022-08-10 09:39:10 +00:00
s . projectLock . RLock ( a . Spec . GetProject ( ) )
defer s . projectLock . RUnlock ( a . Spec . GetProject ( ) )
2018-06-22 17:05:57 +00:00
2020-07-30 20:28:36 +00:00
validate := true
if q . Validate != nil {
validate = * q . Validate
}
2024-04-15 07:20:07 +00:00
2025-05-20 19:48:09 +00:00
proj , err := s . getAppProject ( ctx , a , log . WithFields ( applog . GetAppLogFields ( a ) ) )
2024-04-15 07:20:07 +00:00
if err != nil {
return nil , err
}
err = s . validateAndNormalizeApp ( ctx , a , proj , validate )
2018-04-20 08:19:53 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error while validating and normalizing app: %w" , err )
2018-04-20 08:19:53 +00:00
}
2022-08-10 09:39:10 +00:00
appNs := s . appNamespaceOrDefault ( a . Namespace )
if ! s . isNamespaceEnabled ( appNs ) {
2022-12-22 18:28:53 +00:00
return nil , security . NamespaceNotPermittedError ( appNs )
2022-08-10 09:39:10 +00:00
}
2024-03-13 18:28:43 +00:00
// Don't let the app creator set the operation explicitly. Those requests should always go through the Sync API.
if a . Operation != nil {
2025-05-20 19:48:09 +00:00
log . WithFields ( applog . GetAppLogFields ( a ) ) .
WithFields ( log . Fields {
argocommon . SecurityField : argocommon . SecurityLow ,
} ) . Warn ( "User attempted to set operation on application creation. This could have allowed them to bypass branch protection rules by setting manifests directly. Ignoring the set operation." )
2024-03-13 18:28:43 +00:00
a . Operation = nil
}
2022-08-10 09:39:10 +00:00
created , err := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( appNs ) . Create ( ctx , a , metav1 . CreateOptions { } )
2020-03-16 18:51:59 +00:00
if err == nil {
2025-01-07 15:08:51 +00:00
s . logAppEvent ( ctx , created , argo . EventReasonResourceCreated , "created application" )
2020-03-16 18:51:59 +00:00
s . waitSync ( created )
return created , nil
2018-05-07 20:20:38 +00:00
}
2025-01-03 17:09:37 +00:00
if ! apierrors . IsAlreadyExists ( err ) {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error creating application: %w" , err )
2020-03-16 18:51:59 +00:00
}
2022-08-10 09:39:10 +00:00
2020-03-16 18:51:59 +00:00
// act idempotent if existing spec matches new spec
2022-08-10 09:39:10 +00:00
existing , err := s . appLister . Applications ( appNs ) . Get ( a . Name )
2020-03-16 18:51:59 +00:00
if err != nil {
2022-08-10 09:39:10 +00:00
return nil , status . Errorf ( codes . Internal , "unable to check existing application details (%s): %v" , appNs , err )
2020-03-16 18:51:59 +00:00
}
2024-12-14 05:10:48 +00:00
2025-01-13 18:15:42 +00:00
equalSpecs := reflect . DeepEqual ( existing . Spec . Destination , a . Spec . Destination ) &&
2024-12-14 05:10:48 +00:00
reflect . DeepEqual ( existing . Spec , a . Spec ) &&
2020-03-16 18:51:59 +00:00
reflect . DeepEqual ( existing . Labels , a . Labels ) &&
reflect . DeepEqual ( existing . Annotations , a . Annotations ) &&
reflect . DeepEqual ( existing . Finalizers , a . Finalizers )
2018-07-24 15:48:13 +00:00
2020-03-16 18:51:59 +00:00
if equalSpecs {
return existing , nil
}
if q . Upsert == nil || ! * q . Upsert {
2022-01-15 01:19:43 +00:00
return nil , status . Errorf ( codes . InvalidArgument , "existing application spec is different, use upsert flag to force update" )
2020-03-16 18:51:59 +00:00
}
2025-02-20 10:40:15 +00:00
if err := s . enf . EnforceErr ( ctx . Value ( "claims" ) , rbac . ResourceApplications , rbac . ActionUpdate , a . RBACName ( s . ns ) ) ; err != nil {
2020-03-16 18:51:59 +00:00
return nil , err
2018-07-24 15:48:13 +00:00
}
2025-01-07 15:08:51 +00:00
updated , err := s . updateApp ( ctx , existing , a , true )
2020-03-16 18:51:59 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error updating application: %w" , err )
2020-03-16 18:51:59 +00:00
}
return updated , nil
2018-02-19 11:39:03 +00:00
}
2025-01-06 16:34:32 +00:00
func ( s * Server ) queryRepoServer ( ctx context . Context , proj * v1alpha1 . AppProject , action func (
2021-06-15 18:48:14 +00:00
client apiclient . RepoServerServiceClient ,
2025-01-06 16:34:32 +00:00
helmRepos [ ] * v1alpha1 . Repository ,
helmCreds [ ] * v1alpha1 . RepoCreds ,
2025-06-06 11:27:02 +00:00
ociRepos [ ] * v1alpha1 . Repository ,
ociCreds [ ] * v1alpha1 . RepoCreds ,
2025-01-06 16:34:32 +00:00
helmOptions * v1alpha1 . HelmOptions ,
2022-02-17 05:03:04 +00:00
enabledSourceTypes map [ string ] bool ,
2024-06-11 15:41:55 +00:00
) error ,
) error {
2021-06-15 18:48:14 +00:00
closer , client , err := s . repoClientset . NewRepoServerClient ( )
2018-05-17 23:33:04 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error creating repo server client: %w" , err )
2018-06-12 17:43:16 +00:00
}
2025-05-21 16:25:32 +00:00
defer utilio . Close ( closer )
2024-04-03 18:26:56 +00:00
2021-06-15 18:48:14 +00:00
helmRepos , err := s . db . ListHelmRepositories ( ctx )
2021-04-24 00:32:28 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error listing helm repositories: %w" , err )
2021-04-24 00:32:28 +00:00
}
2021-06-15 18:48:14 +00:00
permittedHelmRepos , err := argo . GetPermittedRepos ( proj , helmRepos )
2019-08-02 23:57:33 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error retrieving permitted repos: %w" , err )
2019-08-02 23:57:33 +00:00
}
2021-06-15 18:48:14 +00:00
helmRepositoryCredentials , err := s . db . GetAllHelmRepositoryCredentials ( ctx )
2020-04-15 19:04:31 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error getting helm repository credentials: %w" , err )
2019-08-02 23:57:33 +00:00
}
2022-02-17 19:51:17 +00:00
helmOptions , err := s . settingsMgr . GetHelmSettings ( )
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error getting helm settings: %w" , err )
2022-02-17 19:51:17 +00:00
}
2021-06-15 18:48:14 +00:00
permittedHelmCredentials , err := argo . GetPermittedReposCredentials ( proj , helmRepositoryCredentials )
2019-09-11 23:37:00 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error getting permitted repos credentials: %w" , err )
2019-09-11 23:37:00 +00:00
}
2022-02-17 05:03:04 +00:00
enabledSourceTypes , err := s . settingsMgr . GetEnabledSourceTypes ( )
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error getting settings enabled source types: %w" , err )
2022-02-17 05:03:04 +00:00
}
2025-06-06 11:27:02 +00:00
ociRepos , err := s . db . ListOCIRepositories ( context . Background ( ) )
if err != nil {
return fmt . Errorf ( "failed to list OCI repositories: %w" , err )
}
permittedOCIRepos , err := argo . GetPermittedRepos ( proj , ociRepos )
if err != nil {
return fmt . Errorf ( "failed to get permitted OCI repositories for project %q: %w" , proj . Name , err )
}
ociRepositoryCredentials , err := s . db . GetAllOCIRepositoryCredentials ( context . Background ( ) )
if err != nil {
return fmt . Errorf ( "failed to get OCI credentials: %w" , err )
}
permittedOCICredentials , err := argo . GetPermittedReposCredentials ( proj , ociRepositoryCredentials )
if err != nil {
return fmt . Errorf ( "failed to get permitted OCI credentials for project %q: %w" , proj . Name , err )
}
return action ( client , permittedHelmRepos , permittedHelmCredentials , permittedOCIRepos , permittedOCICredentials , helmOptions , enabledSourceTypes )
2021-06-15 18:48:14 +00:00
}
2020-06-20 23:12:46 +00:00
2021-06-15 18:48:14 +00:00
// GetManifests returns application manifests
func ( s * Server ) GetManifests ( ctx context . Context , q * application . ApplicationManifestQuery ) ( * apiclient . ManifestResponse , error ) {
2022-08-10 09:39:10 +00:00
if q . Name == nil || * q . Name == "" {
2024-12-30 08:56:41 +00:00
return nil , errors . New ( "invalid request: application name is missing" )
2022-08-10 09:39:10 +00:00
}
2025-02-20 10:40:15 +00:00
a , proj , err := s . getApplicationEnforceRBACInformer ( ctx , rbac . ActionGet , q . GetProject ( ) , q . GetAppNamespace ( ) , q . GetName ( ) )
2020-03-30 20:36:46 +00:00
if err != nil {
2019-09-11 23:37:00 +00:00
return nil , err
}
2021-04-19 20:17:26 +00:00
2022-08-10 09:39:10 +00:00
if ! s . isNamespaceEnabled ( a . Namespace ) {
2022-12-22 18:28:53 +00:00
return nil , security . NamespaceNotPermittedError ( a . Namespace )
2022-08-10 09:39:10 +00:00
}
2024-04-03 18:26:56 +00:00
manifestInfos := make ( [ ] * apiclient . ManifestResponse , 0 )
2024-04-15 07:20:07 +00:00
err = s . queryRepoServer ( ctx , proj , func (
2025-06-06 11:27:02 +00:00
client apiclient . RepoServerServiceClient , helmRepos [ ] * v1alpha1 . Repository , helmCreds [ ] * v1alpha1 . RepoCreds , ociRepos [ ] * v1alpha1 . Repository , ociCreds [ ] * v1alpha1 . RepoCreds , helmOptions * v1alpha1 . HelmOptions , enableGenerateManifests map [ string ] bool ,
2024-06-11 15:41:55 +00:00
) error {
2021-06-15 18:48:14 +00:00
appInstanceLabelKey , err := s . settingsMgr . GetAppInstanceLabelKey ( )
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error getting app instance label key from settings: %w" , err )
2021-06-15 18:48:14 +00:00
}
2021-04-19 20:17:26 +00:00
2026-04-02 09:48:29 +00:00
config , err := s . getApplicationClusterConfig ( ctx , a , proj )
2021-06-15 18:48:14 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error getting application cluster config: %w" , err )
2021-06-15 18:48:14 +00:00
}
serverVersion , err := s . kubectl . GetServerVersion ( config )
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error getting server version: %w" , err )
2021-06-15 18:48:14 +00:00
}
2021-10-25 23:52:19 +00:00
apiResources , err := s . kubectl . GetAPIResources ( config , false , kubecache . NewNoopSettings ( ) )
2021-06-15 18:48:14 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error getting API resources: %w" , err )
2021-06-15 18:48:14 +00:00
}
2024-04-15 12:50:06 +00:00
2025-01-06 16:34:32 +00:00
sources := make ( [ ] v1alpha1 . ApplicationSource , 0 )
2025-03-10 14:29:07 +00:00
appSpec := a . Spec
2024-04-03 18:26:56 +00:00
if a . Spec . HasMultipleSources ( ) {
2024-04-12 20:07:31 +00:00
numOfSources := int64 ( len ( a . Spec . GetSources ( ) ) )
for i , pos := range q . SourcePositions {
2024-04-29 08:23:49 +00:00
if pos <= 0 || pos > numOfSources {
2024-12-30 08:56:41 +00:00
return errors . New ( "source position is out of range" )
2024-04-03 18:26:56 +00:00
}
2024-05-24 20:50:56 +00:00
appSpec . Sources [ pos - 1 ] . TargetRevision = q . Revisions [ i ]
2024-04-03 18:26:56 +00:00
}
2024-05-24 20:50:56 +00:00
sources = appSpec . GetSources ( )
2024-04-03 18:26:56 +00:00
} else {
source := a . Spec . GetSource ( )
if q . GetRevision ( ) != "" {
source . TargetRevision = q . GetRevision ( )
}
sources = append ( sources , source )
}
// Store the map of all sources having ref field into a map for applications with sources field
2025-07-02 22:32:31 +00:00
refSources , err := argo . GetRefSources ( context . Background ( ) , sources , appSpec . Project , s . db . GetRepository , [ ] string { } )
2022-05-20 21:16:55 +00:00
if err != nil {
2024-06-12 23:43:25 +00:00
return fmt . Errorf ( "failed to get ref sources: %w" , err )
2024-04-03 18:26:56 +00:00
}
for _ , source := range sources {
2024-06-08 01:47:55 +00:00
repo , err := s . db . GetRepository ( ctx , source . RepoURL , proj . Name )
2024-04-03 18:26:56 +00:00
if err != nil {
return fmt . Errorf ( "error getting repository: %w" , err )
}
kustomizeSettings , err := s . settingsMgr . GetKustomizeSettings ( )
if err != nil {
return fmt . Errorf ( "error getting kustomize settings: %w" , err )
}
2024-10-05 00:54:37 +00:00
installationID , err := s . settingsMgr . GetInstallationID ( )
if err != nil {
return fmt . Errorf ( "error getting installation ID: %w" , err )
}
2025-05-15 08:26:47 +00:00
trackingMethod , err := s . settingsMgr . GetTrackingMethod ( )
if err != nil {
return fmt . Errorf ( "error getting trackingMethod from settings: %w" , err )
}
2024-04-03 18:26:56 +00:00
2025-06-06 11:27:02 +00:00
repos := helmRepos
helmRepoCreds := helmCreds
// If the source is OCI, there is a potential for an OCI image to be a Helm chart and that said chart in
// turn would have OCI dependencies. To ensure that those dependencies can be resolved, add them to the repos
// list.
if source . IsOCI ( ) {
repos = slices . Clone ( helmRepos )
helmRepoCreds = slices . Clone ( helmCreds )
repos = append ( repos , ociRepos ... )
helmRepoCreds = append ( helmRepoCreds , ociCreds ... )
}
2024-04-03 18:26:56 +00:00
manifestInfo , err := client . GenerateManifest ( ctx , & apiclient . ManifestRequest {
2024-09-24 16:14:02 +00:00
Repo : repo ,
Revision : source . TargetRevision ,
AppLabelKey : appInstanceLabelKey ,
AppName : a . InstanceName ( s . ns ) ,
Namespace : a . Spec . Destination . Namespace ,
ApplicationSource : & source ,
2025-06-06 11:27:02 +00:00
Repos : repos ,
2025-07-16 15:12:25 +00:00
KustomizeOptions : kustomizeSettings ,
2024-09-24 16:14:02 +00:00
KubeVersion : serverVersion ,
ApiVersions : argo . APIResourcesToStrings ( apiResources , true ) ,
2025-06-06 11:27:02 +00:00
HelmRepoCreds : helmRepoCreds ,
2024-09-24 16:14:02 +00:00
HelmOptions : helmOptions ,
2025-05-15 08:26:47 +00:00
TrackingMethod : trackingMethod ,
2024-09-24 16:14:02 +00:00
EnabledSourceTypes : enableGenerateManifests ,
ProjectName : proj . Name ,
ProjectSourceRepos : proj . Spec . SourceRepos ,
HasMultipleSources : a . Spec . HasMultipleSources ( ) ,
RefSources : refSources ,
AnnotationManifestGeneratePaths : a . GetAnnotation ( v1alpha1 . AnnotationKeyManifestGeneratePaths ) ,
2024-10-05 00:54:37 +00:00
InstallationID : installationID ,
2025-09-15 17:24:30 +00:00
NoCache : q . NoCache != nil && * q . NoCache ,
2024-04-03 18:26:56 +00:00
} )
if err != nil {
return fmt . Errorf ( "error generating manifests: %w" , err )
}
manifestInfos = append ( manifestInfos , manifestInfo )
2022-05-20 21:16:55 +00:00
}
return nil
2018-05-17 23:33:04 +00:00
} )
if err != nil {
return nil , err
}
2021-06-15 18:48:14 +00:00
2024-04-03 18:26:56 +00:00
manifests := & apiclient . ManifestResponse { }
for _ , manifestInfo := range manifestInfos {
for i , manifest := range manifestInfo . Manifests {
obj := & unstructured . Unstructured { }
err = json . Unmarshal ( [ ] byte ( manifest ) , obj )
2020-02-07 23:40:58 +00:00
if err != nil {
2024-04-03 18:26:56 +00:00
return nil , fmt . Errorf ( "error unmarshaling manifest into unstructured: %w" , err )
2020-02-07 23:40:58 +00:00
}
2024-04-03 18:26:56 +00:00
if obj . GetKind ( ) == kube . SecretKind && obj . GroupVersionKind ( ) . Group == "" {
2024-10-30 16:52:37 +00:00
obj , _ , err = diff . HideSecretData ( obj , nil , s . settingsMgr . GetSensitiveAnnotations ( ) )
2024-04-03 18:26:56 +00:00
if err != nil {
return nil , fmt . Errorf ( "error hiding secret data: %w" , err )
}
data , err := json . Marshal ( obj )
if err != nil {
return nil , fmt . Errorf ( "error marshaling manifest: %w" , err )
}
manifestInfo . Manifests [ i ] = string ( data )
2020-02-07 23:40:58 +00:00
}
}
2024-05-24 20:50:56 +00:00
manifests . Manifests = append ( manifests . Manifests , manifestInfo . Manifests ... )
2020-02-07 23:40:58 +00:00
}
2018-05-17 23:33:04 +00:00
2024-04-03 18:26:56 +00:00
return manifests , nil
2018-05-17 23:33:04 +00:00
}
2022-08-17 18:48:50 +00:00
func ( s * Server ) GetManifestsWithFiles ( stream application . ApplicationService_GetManifestsWithFilesServer ) error {
ctx := stream . Context ( )
query , err := manifeststream . ReceiveApplicationManifestQueryWithFiles ( stream )
if err != nil {
return fmt . Errorf ( "error getting query: %w" , err )
}
if query . Name == nil || * query . Name == "" {
2024-12-30 08:56:41 +00:00
return errors . New ( "invalid request: application name is missing" )
2022-08-17 18:48:50 +00:00
}
2025-02-20 10:40:15 +00:00
a , proj , err := s . getApplicationEnforceRBACInformer ( ctx , rbac . ActionGet , query . GetProject ( ) , query . GetAppNamespace ( ) , query . GetName ( ) )
2022-08-17 18:48:50 +00:00
if err != nil {
return err
}
var manifestInfo * apiclient . ManifestResponse
2024-04-15 07:20:07 +00:00
err = s . queryRepoServer ( ctx , proj , func (
2025-06-06 11:27:02 +00:00
client apiclient . RepoServerServiceClient , helmRepos [ ] * v1alpha1 . Repository , helmCreds [ ] * v1alpha1 . RepoCreds , _ [ ] * v1alpha1 . Repository , _ [ ] * v1alpha1 . RepoCreds , helmOptions * v1alpha1 . HelmOptions , enableGenerateManifests map [ string ] bool ,
2024-06-11 15:41:55 +00:00
) error {
2022-08-17 18:48:50 +00:00
appInstanceLabelKey , err := s . settingsMgr . GetAppInstanceLabelKey ( )
if err != nil {
return fmt . Errorf ( "error getting app instance label key from settings: %w" , err )
}
2025-05-15 08:26:47 +00:00
trackingMethod , err := s . settingsMgr . GetTrackingMethod ( )
if err != nil {
return fmt . Errorf ( "error getting trackingMethod from settings: %w" , err )
}
2026-04-02 09:48:29 +00:00
config , err := s . getApplicationClusterConfig ( ctx , a , proj )
2022-08-17 18:48:50 +00:00
if err != nil {
return fmt . Errorf ( "error getting application cluster config: %w" , err )
}
serverVersion , err := s . kubectl . GetServerVersion ( config )
if err != nil {
return fmt . Errorf ( "error getting server version: %w" , err )
}
apiResources , err := s . kubectl . GetAPIResources ( config , false , kubecache . NewNoopSettings ( ) )
if err != nil {
return fmt . Errorf ( "error getting API resources: %w" , err )
}
2022-12-16 20:47:08 +00:00
source := a . Spec . GetSource ( )
2023-08-14 14:06:43 +00:00
2025-01-07 15:08:51 +00:00
proj , err := argo . GetAppProject ( ctx , a , applisters . NewAppProjectLister ( s . projInformer . GetIndexer ( ) ) , s . ns , s . settingsMgr , s . db )
2023-08-14 14:06:43 +00:00
if err != nil {
return fmt . Errorf ( "error getting app project: %w" , err )
}
2024-06-08 01:47:55 +00:00
repo , err := s . db . GetRepository ( ctx , a . Spec . GetSource ( ) . RepoURL , proj . Name )
2024-04-03 18:26:56 +00:00
if err != nil {
return fmt . Errorf ( "error getting repository: %w" , err )
}
kustomizeSettings , err := s . settingsMgr . GetKustomizeSettings ( )
if err != nil {
return fmt . Errorf ( "error getting kustomize settings: %w" , err )
}
2022-08-17 18:48:50 +00:00
req := & apiclient . ManifestRequest {
2024-09-24 16:14:02 +00:00
Repo : repo ,
Revision : source . TargetRevision ,
AppLabelKey : appInstanceLabelKey ,
AppName : a . Name ,
Namespace : a . Spec . Destination . Namespace ,
ApplicationSource : & source ,
Repos : helmRepos ,
2025-07-16 15:12:25 +00:00
KustomizeOptions : kustomizeSettings ,
2024-09-24 16:14:02 +00:00
KubeVersion : serverVersion ,
ApiVersions : argo . APIResourcesToStrings ( apiResources , true ) ,
HelmRepoCreds : helmCreds ,
HelmOptions : helmOptions ,
2025-05-15 08:26:47 +00:00
TrackingMethod : trackingMethod ,
2024-09-24 16:14:02 +00:00
EnabledSourceTypes : enableGenerateManifests ,
ProjectName : proj . Name ,
ProjectSourceRepos : proj . Spec . SourceRepos ,
AnnotationManifestGeneratePaths : a . GetAnnotation ( v1alpha1 . AnnotationKeyManifestGeneratePaths ) ,
2022-08-17 18:48:50 +00:00
}
repoStreamClient , err := client . GenerateManifestWithFiles ( stream . Context ( ) )
if err != nil {
return fmt . Errorf ( "error opening stream: %w" , err )
}
err = manifeststream . SendRepoStream ( repoStreamClient , stream , req , * query . Checksum )
if err != nil {
return fmt . Errorf ( "error sending repo stream: %w" , err )
}
resp , err := repoStreamClient . CloseAndRecv ( )
if err != nil {
return fmt . Errorf ( "error generating manifests: %w" , err )
}
manifestInfo = resp
return nil
} )
if err != nil {
return err
}
for i , manifest := range manifestInfo . Manifests {
obj := & unstructured . Unstructured { }
err = json . Unmarshal ( [ ] byte ( manifest ) , obj )
if err != nil {
return fmt . Errorf ( "error unmarshaling manifest into unstructured: %w" , err )
}
if obj . GetKind ( ) == kube . SecretKind && obj . GroupVersionKind ( ) . Group == "" {
2024-10-30 16:52:37 +00:00
obj , _ , err = diff . HideSecretData ( obj , nil , s . settingsMgr . GetSensitiveAnnotations ( ) )
2022-08-17 18:48:50 +00:00
if err != nil {
return fmt . Errorf ( "error hiding secret data: %w" , err )
}
data , err := json . Marshal ( obj )
if err != nil {
return fmt . Errorf ( "error marshaling manifest: %w" , err )
}
manifestInfo . Manifests [ i ] = string ( data )
}
}
stream . SendAndClose ( manifestInfo )
return nil
}
2018-04-20 08:19:53 +00:00
// Get returns an application by name
2025-01-06 16:34:32 +00:00
func ( s * Server ) Get ( ctx context . Context , q * application . ApplicationQuery ) ( * v1alpha1 . Application , error ) {
2022-08-10 09:39:10 +00:00
appName := q . GetName ( )
appNs := s . appNamespaceOrDefault ( q . GetAppNamespace ( ) )
2023-07-19 13:22:28 +00:00
project := ""
projects := getProjectsFromApplicationQuery ( * q )
if len ( projects ) == 1 {
project = projects [ 0 ]
} else if len ( projects ) > 1 {
return nil , status . Errorf ( codes . InvalidArgument , "multiple projects specified - the get endpoint accepts either zero or one project" )
}
2023-09-11 23:17:02 +00:00
2020-03-16 18:51:59 +00:00
// We must use a client Get instead of an informer Get, because it's common to call Get immediately
// following a Watch (which is not yet powered by an informer), and the Get must reflect what was
// previously seen by the client.
2025-02-20 10:40:15 +00:00
a , proj , err := s . getApplicationEnforceRBACClient ( ctx , rbac . ActionGet , project , appNs , appName , q . GetResourceVersion ( ) )
2018-06-12 17:43:16 +00:00
if err != nil {
2019-02-28 21:11:47 +00:00
return nil , err
2018-06-06 04:44:14 +00:00
}
2022-08-17 15:48:27 +00:00
2020-09-09 20:08:18 +00:00
if q . Refresh == nil {
2025-10-06 19:11:51 +00:00
s . inferResourcesStatusHealth ( a )
2025-10-15 01:38:14 +00:00
return a . DeepCopy ( ) , nil
2020-09-09 20:08:18 +00:00
}
2025-01-06 16:34:32 +00:00
refreshType := v1alpha1 . RefreshTypeNormal
if * q . Refresh == string ( v1alpha1 . RefreshTypeHard ) {
refreshType = v1alpha1 . RefreshTypeHard
2020-09-09 20:08:18 +00:00
}
2022-08-10 09:39:10 +00:00
appIf := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( appNs )
2020-09-11 17:26:45 +00:00
2020-09-12 03:39:58 +00:00
// subscribe early with buffered channel to ensure we don't miss events
2025-01-06 16:34:32 +00:00
events := make ( chan * v1alpha1 . ApplicationWatchEvent , watchAPIBufferSize )
unsubscribe := s . appBroadcaster . Subscribe ( events , func ( event * v1alpha1 . ApplicationWatchEvent ) bool {
2022-08-10 09:39:10 +00:00
return event . Application . Name == appName && event . Application . Namespace == appNs
2020-09-12 03:39:58 +00:00
} )
2020-09-09 20:08:18 +00:00
defer unsubscribe ( )
2025-02-27 19:22:03 +00:00
app , err := argo . RefreshApp ( appIf , appName , refreshType , true )
2020-09-11 17:26:45 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error refreshing the app: %w" , err )
2020-09-11 17:26:45 +00:00
}
2025-01-06 16:34:32 +00:00
if refreshType == v1alpha1 . RefreshTypeHard {
2021-06-15 18:48:14 +00:00
// force refresh cached application details
2024-04-15 07:20:07 +00:00
if err := s . queryRepoServer ( ctx , proj , func (
2021-06-15 18:48:14 +00:00
client apiclient . RepoServerServiceClient ,
2025-01-06 16:34:32 +00:00
helmRepos [ ] * v1alpha1 . Repository ,
_ [ ] * v1alpha1 . RepoCreds ,
2025-06-06 11:27:02 +00:00
_ [ ] * v1alpha1 . Repository ,
_ [ ] * v1alpha1 . RepoCreds ,
2025-01-06 16:34:32 +00:00
helmOptions * v1alpha1 . HelmOptions ,
2022-02-17 05:03:04 +00:00
enabledSourceTypes map [ string ] bool ,
2021-06-15 18:48:14 +00:00
) error {
2022-12-16 20:47:08 +00:00
source := app . Spec . GetSource ( )
2024-06-08 01:47:55 +00:00
repo , err := s . db . GetRepository ( ctx , a . Spec . GetSource ( ) . RepoURL , proj . Name )
2024-04-03 18:26:56 +00:00
if err != nil {
return fmt . Errorf ( "error getting repository: %w" , err )
}
kustomizeSettings , err := s . settingsMgr . GetKustomizeSettings ( )
if err != nil {
return fmt . Errorf ( "error getting kustomize settings: %w" , err )
}
2025-05-15 08:26:47 +00:00
trackingMethod , err := s . settingsMgr . GetTrackingMethod ( )
if err != nil {
return fmt . Errorf ( "error getting trackingMethod from settings: %w" , err )
}
2024-04-03 18:26:56 +00:00
_ , err = client . GetAppDetails ( ctx , & apiclient . RepoServerAppDetailsQuery {
2022-02-17 05:03:04 +00:00
Repo : repo ,
2022-12-16 20:47:08 +00:00
Source : & source ,
2022-08-10 09:39:10 +00:00
AppName : appName ,
2025-07-16 15:12:25 +00:00
KustomizeOptions : kustomizeSettings ,
2022-02-17 05:03:04 +00:00
Repos : helmRepos ,
NoCache : true ,
2025-05-15 08:26:47 +00:00
TrackingMethod : trackingMethod ,
2022-02-17 05:03:04 +00:00
EnabledSourceTypes : enabledSourceTypes ,
2022-02-24 20:58:38 +00:00
HelmOptions : helmOptions ,
2021-06-15 18:48:14 +00:00
} )
return err
} ) ; err != nil {
log . Warnf ( "Failed to force refresh application details: %v" , err )
}
}
2020-09-11 17:26:45 +00:00
minVersion := 0
if minVersion , err = strconv . Atoi ( app . ResourceVersion ) ; err != nil {
minVersion = 0
}
2020-09-09 20:08:18 +00:00
for {
select {
case <- ctx . Done ( ) :
2024-12-30 08:56:41 +00:00
return nil , errors . New ( "application refresh deadline exceeded" )
2020-09-09 20:08:18 +00:00
case event := <- events :
2020-09-11 17:26:45 +00:00
if appVersion , err := strconv . Atoi ( event . Application . ResourceVersion ) ; err == nil && appVersion > minVersion {
annotations := event . Application . GetAnnotations ( )
if annotations == nil {
annotations = make ( map [ string ] string )
}
2025-01-06 16:34:32 +00:00
if _ , ok := annotations [ v1alpha1 . AnnotationKeyRefresh ] ; ! ok {
2025-10-06 19:11:51 +00:00
refreshedApp := event . Application . DeepCopy ( )
s . inferResourcesStatusHealth ( refreshedApp )
return refreshedApp , nil
2020-09-11 17:26:45 +00:00
}
2020-09-09 20:08:18 +00:00
}
2018-06-18 17:22:58 +00:00
}
}
2018-02-19 11:39:03 +00:00
}
2018-05-30 22:30:58 +00:00
// ListResourceEvents returns a list of event resources
2025-01-03 16:10:00 +00:00
func ( s * Server ) ListResourceEvents ( ctx context . Context , q * application . ApplicationResourceEventsQuery ) ( * corev1 . EventList , error ) {
2026-04-02 09:48:29 +00:00
a , p , err := s . getApplicationEnforceRBACInformer ( ctx , rbac . ActionGet , q . GetProject ( ) , q . GetAppNamespace ( ) , q . GetName ( ) )
2018-06-12 17:43:16 +00:00
if err != nil {
2019-02-28 21:11:47 +00:00
return nil , err
2018-06-06 04:44:14 +00:00
}
2022-08-17 15:48:27 +00:00
2018-07-31 21:15:53 +00:00
var (
kubeClientset kubernetes . Interface
fieldSelector string
namespace string
)
// There are two places where we get events. If we are getting application events, we query
// our own cluster. If it is events on a resource on an external cluster, then we query the
// external cluster using its rest.Config
2022-04-18 14:47:13 +00:00
if q . GetResourceName ( ) == "" && q . GetResourceUID ( ) == "" {
2018-07-31 21:15:53 +00:00
kubeClientset = s . kubeclientset
namespace = a . Namespace
2018-07-24 15:48:13 +00:00
fieldSelector = fields . SelectorFromSet ( map [ string ] string {
"involvedObject.name" : a . Name ,
"involvedObject.uid" : string ( a . UID ) ,
2018-07-31 21:15:53 +00:00
"involvedObject.namespace" : a . Namespace ,
2018-07-24 15:48:13 +00:00
} ) . String ( )
} else {
2022-08-17 15:48:27 +00:00
tree , err := s . getAppResources ( ctx , a )
2022-03-22 17:57:30 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error getting app resources: %w" , err )
2022-03-22 17:57:30 +00:00
}
found := false
for _ , n := range append ( tree . Nodes , tree . OrphanedNodes ... ) {
2025-03-27 16:37:52 +00:00
if n . UID == q . GetResourceUID ( ) && n . Name == q . GetResourceName ( ) && n . Namespace == q . GetResourceNamespace ( ) {
2022-03-22 17:57:30 +00:00
found = true
break
}
}
if ! found {
2022-04-18 14:47:13 +00:00
return nil , status . Errorf ( codes . InvalidArgument , "%s not found as part of application %s" , q . GetResourceName ( ) , q . GetName ( ) )
2022-03-22 17:57:30 +00:00
}
2022-04-18 14:47:13 +00:00
namespace = q . GetResourceNamespace ( )
2018-07-31 21:15:53 +00:00
var config * rest . Config
2026-04-02 09:48:29 +00:00
config , err = s . getApplicationClusterConfig ( ctx , a , p )
2018-07-31 21:15:53 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error getting application cluster config: %w" , err )
2018-07-31 21:15:53 +00:00
}
kubeClientset , err = kubernetes . NewForConfig ( config )
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error creating kube client: %w" , err )
2018-07-31 21:15:53 +00:00
}
2018-07-24 15:48:13 +00:00
fieldSelector = fields . SelectorFromSet ( map [ string ] string {
2022-04-18 14:47:13 +00:00
"involvedObject.name" : q . GetResourceName ( ) ,
"involvedObject.uid" : q . GetResourceUID ( ) ,
2018-07-24 15:48:13 +00:00
"involvedObject.namespace" : namespace ,
} ) . String ( )
}
2018-05-30 22:30:58 +00:00
log . Infof ( "Querying for resource events with field selector: %s" , fieldSelector )
opts := metav1 . ListOptions { FieldSelector : fieldSelector }
2022-05-20 21:16:55 +00:00
list , err := kubeClientset . CoreV1 ( ) . Events ( namespace ) . List ( ctx , opts )
if err != nil {
return nil , fmt . Errorf ( "error listing resource events: %w" , err )
}
2025-03-10 14:29:07 +00:00
return list . DeepCopy ( ) , nil
2018-05-30 22:30:58 +00:00
}
2023-07-19 13:22:28 +00:00
// validateAndUpdateApp validates and updates the application. currentProject is the name of the project the app
// currently is under. If not specified, we assume that the app is under the project specified in the app spec.
2025-01-06 16:34:32 +00:00
func ( s * Server ) validateAndUpdateApp ( ctx context . Context , newApp * v1alpha1 . Application , merge bool , validate bool , action string , currentProject string ) ( * v1alpha1 . Application , error ) {
2021-05-14 02:12:24 +00:00
s . projectLock . RLock ( newApp . Spec . GetProject ( ) )
defer s . projectLock . RUnlock ( newApp . Spec . GetProject ( ) )
2019-12-10 21:09:39 +00:00
2024-04-15 07:20:07 +00:00
app , proj , err := s . getApplicationEnforceRBACClient ( ctx , action , currentProject , newApp . Namespace , newApp . Name , "" )
2019-12-10 21:09:39 +00:00
if err != nil {
2023-03-23 13:22:05 +00:00
return nil , err
2018-06-06 04:44:14 +00:00
}
2018-06-22 17:05:57 +00:00
2024-04-15 07:20:07 +00:00
err = s . validateAndNormalizeApp ( ctx , newApp , proj , validate )
2018-04-20 08:19:53 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error validating and normalizing app: %w" , err )
2018-04-20 08:19:53 +00:00
}
2019-12-10 21:09:39 +00:00
2025-01-07 15:08:51 +00:00
a , err := s . updateApp ( ctx , app , newApp , merge )
2022-05-20 21:16:55 +00:00
if err != nil {
return nil , fmt . Errorf ( "error updating application: %w" , err )
}
return a , nil
2020-01-10 00:00:35 +00:00
}
2020-03-16 18:51:59 +00:00
var informerSyncTimeout = 2 * time . Second
// waitSync is a helper to wait until the application informer cache is synced after create/update.
// It waits until the app in the informer, has a resource version greater than the version in the
// supplied app, or after 2 seconds, whichever comes first. Returns true if synced.
// We use an informer cache for read operations (Get, List). Since the cache is only
// eventually consistent, it is possible that it doesn't reflect an application change immediately
// after a mutating API call (create/update). This function should be called after a creates &
// update to give a probable (but not guaranteed) chance of being up-to-date after the create/update.
2025-01-06 16:34:32 +00:00
func ( s * Server ) waitSync ( app * v1alpha1 . Application ) {
2025-05-20 19:48:09 +00:00
logCtx := log . WithFields ( applog . GetAppLogFields ( app ) )
2020-03-16 18:51:59 +00:00
deadline := time . Now ( ) . Add ( informerSyncTimeout )
minVersion , err := strconv . Atoi ( app . ResourceVersion )
if err != nil {
logCtx . Warnf ( "waitSync failed: could not parse resource version %s" , app . ResourceVersion )
2021-07-13 17:02:03 +00:00
time . Sleep ( 50 * time . Millisecond ) // sleep anyway
2020-03-16 18:51:59 +00:00
return
}
for {
2022-08-10 09:39:10 +00:00
if currApp , err := s . appLister . Applications ( app . Namespace ) . Get ( app . Name ) ; err == nil {
2020-03-16 18:51:59 +00:00
currVersion , err := strconv . Atoi ( currApp . ResourceVersion )
if err == nil && currVersion >= minVersion {
return
}
}
if time . Now ( ) . After ( deadline ) {
break
}
time . Sleep ( 20 * time . Millisecond )
}
logCtx . Warnf ( "waitSync failed: timed out" )
}
2025-01-07 15:08:51 +00:00
func ( s * Server ) updateApp ( ctx context . Context , app * v1alpha1 . Application , newApp * v1alpha1 . Application , merge bool ) ( * v1alpha1 . Application , error ) {
2026-02-09 12:37:34 +00:00
for range 10 {
2019-12-10 21:09:39 +00:00
app . Spec = newApp . Spec
2020-02-04 21:49:21 +00:00
if merge {
2024-10-09 14:34:03 +00:00
app . Labels = collections . Merge ( app . Labels , newApp . Labels )
app . Annotations = collections . Merge ( app . Annotations , newApp . Annotations )
2020-02-04 21:49:21 +00:00
} else {
app . Labels = newApp . Labels
app . Annotations = newApp . Annotations
}
2020-01-10 20:06:04 +00:00
app . Finalizers = newApp . Finalizers
2019-12-10 21:09:39 +00:00
2022-08-10 09:39:10 +00:00
res , err := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( app . Namespace ) . Update ( ctx , app , metav1 . UpdateOptions { } )
2019-12-10 21:09:39 +00:00
if err == nil {
2025-01-07 15:08:51 +00:00
s . logAppEvent ( ctx , app , argo . EventReasonResourceUpdated , "updated application spec" )
2020-03-16 18:51:59 +00:00
s . waitSync ( res )
2019-12-10 21:09:39 +00:00
return res , nil
}
2025-01-03 17:09:37 +00:00
if ! apierrors . IsConflict ( err ) {
2019-12-10 21:09:39 +00:00
return nil , err
}
2022-08-10 09:39:10 +00:00
app , err = s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( app . Namespace ) . Get ( ctx , newApp . Name , metav1 . GetOptions { } )
2019-12-10 21:09:39 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error getting application: %w" , err )
2019-12-10 21:09:39 +00:00
}
2022-08-17 21:03:24 +00:00
s . inferResourcesStatusHealth ( app )
2018-09-24 15:52:43 +00:00
}
2019-12-10 21:09:39 +00:00
return nil , status . Errorf ( codes . Internal , "Failed to update application. Too many conflicts" )
}
// Update updates an application
2025-01-06 16:34:32 +00:00
func ( s * Server ) Update ( ctx context . Context , q * application . ApplicationUpdateRequest ) ( * v1alpha1 . Application , error ) {
2022-08-10 09:39:10 +00:00
if q . GetApplication ( ) == nil {
2024-12-30 08:56:41 +00:00
return nil , errors . New ( "error updating application: application is nil in request" )
2022-08-10 09:39:10 +00:00
}
a := q . GetApplication ( )
2025-02-20 10:40:15 +00:00
if err := s . enf . EnforceErr ( ctx . Value ( "claims" ) , rbac . ResourceApplications , rbac . ActionUpdate , a . RBACName ( s . ns ) ) ; err != nil {
2019-12-10 21:09:39 +00:00
return nil , err
}
2020-07-30 20:28:36 +00:00
validate := true
if q . Validate != nil {
validate = * q . Validate
}
2025-02-20 10:40:15 +00:00
return s . validateAndUpdateApp ( ctx , q . Application , false , validate , rbac . ActionUpdate , q . GetProject ( ) )
2018-02-19 11:39:03 +00:00
}
2018-06-22 21:22:30 +00:00
// UpdateSpec updates an application spec and filters out any invalid parameter overrides
2025-01-06 16:34:32 +00:00
func ( s * Server ) UpdateSpec ( ctx context . Context , q * application . ApplicationUpdateSpecRequest ) ( * v1alpha1 . ApplicationSpec , error ) {
2022-04-18 14:47:13 +00:00
if q . GetSpec ( ) == nil {
2024-12-30 08:56:41 +00:00
return nil , errors . New ( "error updating application spec: spec is nil in request" )
2022-04-18 14:47:13 +00:00
}
2025-02-20 10:40:15 +00:00
a , _ , err := s . getApplicationEnforceRBACClient ( ctx , rbac . ActionUpdate , q . GetProject ( ) , q . GetAppNamespace ( ) , q . GetName ( ) , "" )
2018-06-12 17:43:16 +00:00
if err != nil {
2019-02-28 21:11:47 +00:00
return nil , err
2018-06-06 04:44:14 +00:00
}
2022-08-17 15:48:27 +00:00
2022-04-18 14:47:13 +00:00
a . Spec = * q . GetSpec ( )
2020-07-30 20:28:36 +00:00
validate := true
if q . Validate != nil {
validate = * q . Validate
}
2025-02-20 10:40:15 +00:00
a , err = s . validateAndUpdateApp ( ctx , a , false , validate , rbac . ActionUpdate , q . GetProject ( ) )
2018-05-03 22:55:01 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error validating and updating app: %w" , err )
2018-05-03 22:55:01 +00:00
}
2019-12-10 21:09:39 +00:00
return & a . Spec , nil
2018-05-03 22:55:01 +00:00
}
2019-02-25 22:25:25 +00:00
// Patch patches an application
2025-01-06 16:34:32 +00:00
func ( s * Server ) Patch ( ctx context . Context , q * application . ApplicationPatchRequest ) ( * v1alpha1 . Application , error ) {
2025-02-20 10:40:15 +00:00
app , _ , err := s . getApplicationEnforceRBACClient ( ctx , rbac . ActionGet , q . GetProject ( ) , q . GetAppNamespace ( ) , q . GetName ( ) , "" )
2019-02-25 22:25:25 +00:00
if err != nil {
2023-03-23 13:22:05 +00:00
return nil , err
2019-02-25 22:25:25 +00:00
}
2025-06-17 15:46:50 +00:00
err = s . enf . EnforceErr ( ctx . Value ( "claims" ) , rbac . ResourceApplications , rbac . ActionUpdate , app . RBACName ( s . ns ) )
if err != nil {
2019-02-28 21:11:47 +00:00
return nil , err
2019-02-25 22:25:25 +00:00
}
jsonApp , err := json . Marshal ( app )
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error marshaling application: %w" , err )
2019-02-25 22:25:25 +00:00
}
2019-07-02 20:41:25 +00:00
var patchApp [ ] byte
2022-04-18 14:47:13 +00:00
switch q . GetPatchType ( ) {
2019-07-02 20:41:25 +00:00
case "json" , "" :
2022-04-18 14:47:13 +00:00
patch , err := jsonpatch . DecodePatch ( [ ] byte ( q . GetPatch ( ) ) )
2019-07-02 20:41:25 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error decoding json patch: %w" , err )
2019-07-02 20:41:25 +00:00
}
patchApp , err = patch . Apply ( jsonApp )
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error applying patch: %w" , err )
2019-07-02 20:41:25 +00:00
}
case "merge" :
2022-04-18 14:47:13 +00:00
patchApp , err = jsonpatch . MergePatch ( jsonApp , [ ] byte ( q . GetPatch ( ) ) )
2019-07-02 20:41:25 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error calculating merge patch: %w" , err )
2019-07-02 20:41:25 +00:00
}
default :
2022-04-18 14:47:13 +00:00
return nil , status . Error ( codes . InvalidArgument , fmt . Sprintf ( "Patch type '%s' is not supported" , q . GetPatchType ( ) ) )
2019-02-25 22:25:25 +00:00
}
2025-01-06 16:34:32 +00:00
newApp := & v1alpha1 . Application { }
2020-11-20 18:19:59 +00:00
err = json . Unmarshal ( patchApp , newApp )
2019-02-25 22:25:25 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error unmarshaling patched app: %w" , err )
2019-02-25 22:25:25 +00:00
}
2025-02-20 10:40:15 +00:00
return s . validateAndUpdateApp ( ctx , newApp , false , true , rbac . ActionUpdate , q . GetProject ( ) )
2019-02-25 22:25:25 +00:00
}
2025-01-06 16:34:32 +00:00
func ( s * Server ) getAppProject ( ctx context . Context , a * v1alpha1 . Application , logCtx * log . Entry ) ( * v1alpha1 . AppProject , error ) {
2025-01-07 15:08:51 +00:00
proj , err := argo . GetAppProject ( ctx , a , applisters . NewAppProjectLister ( s . projInformer . GetIndexer ( ) ) , s . ns , s . settingsMgr , s . db )
2024-04-15 07:20:07 +00:00
if err == nil {
return proj , nil
}
// If there's a permission issue or the app doesn't exist, return a vague error to avoid letting the user enumerate project names.
vagueError := status . Errorf ( codes . InvalidArgument , "app is not allowed in project %q, or the project does not exist" , a . Spec . Project )
2025-01-03 17:09:37 +00:00
if apierrors . IsNotFound ( err ) {
2024-04-15 07:20:07 +00:00
return nil , vagueError
}
2025-07-29 14:29:22 +00:00
var applicationNotAllowedToUseProjectErr * argo . ErrApplicationNotAllowedToUseProject
2024-06-12 23:43:25 +00:00
if errors . As ( err , & applicationNotAllowedToUseProjectErr ) {
2024-04-15 07:20:07 +00:00
return nil , vagueError
}
2025-07-29 14:29:22 +00:00
// Unknown error, log it but return the vague error to the user
logCtx . WithFields ( map [ string ] any {
"project" : a . Spec . Project ,
argocommon . SecurityField : argocommon . SecurityMedium ,
} ) . Warnf ( "error getting app project: %s" , err )
2024-04-15 07:20:07 +00:00
return nil , vagueError
}
2018-03-16 23:17:03 +00:00
// Delete removes an application and all associated resources
2019-06-05 19:58:11 +00:00
func ( s * Server ) Delete ( ctx context . Context , q * application . ApplicationDeleteRequest ) ( * application . ApplicationResponse , error ) {
2022-08-10 09:39:10 +00:00
appName := q . GetName ( )
appNs := s . appNamespaceOrDefault ( q . GetAppNamespace ( ) )
2025-02-20 10:40:15 +00:00
a , _ , err := s . getApplicationEnforceRBACClient ( ctx , rbac . ActionGet , q . GetProject ( ) , appNs , appName , "" )
2020-06-09 18:06:34 +00:00
if err != nil {
2023-03-23 13:22:05 +00:00
return nil , err
2018-03-16 23:17:03 +00:00
}
2018-04-06 15:27:51 +00:00
2021-05-14 02:12:24 +00:00
s . projectLock . RLock ( a . Spec . Project )
defer s . projectLock . RUnlock ( a . Spec . Project )
2018-06-22 17:05:57 +00:00
2025-02-20 10:40:15 +00:00
if err := s . enf . EnforceErr ( ctx . Value ( "claims" ) , rbac . ResourceApplications , rbac . ActionDelete , a . RBACName ( s . ns ) ) ; err != nil {
2019-02-28 21:11:47 +00:00
return nil , err
2018-06-12 17:43:16 +00:00
}
2021-03-15 16:27:41 +00:00
if q . Cascade != nil && ! * q . Cascade && q . GetPropagationPolicy ( ) != "" {
return nil , status . Error ( codes . InvalidArgument , "cannot set propagation policy when cascading is disabled" )
}
2018-07-10 17:50:29 +00:00
patchFinalizer := false
if q . Cascade == nil || * q . Cascade {
2021-03-15 16:27:41 +00:00
// validate the propgation policy
policyFinalizer := getPropagationPolicyFinalizer ( q . GetPropagationPolicy ( ) )
if policyFinalizer == "" {
return nil , status . Errorf ( codes . InvalidArgument , "invalid propagation policy: %s" , * q . PropagationPolicy )
}
if ! a . IsFinalizerPresent ( policyFinalizer ) {
a . SetCascadedDeletion ( policyFinalizer )
2018-07-10 17:50:29 +00:00
patchFinalizer = true
}
2024-06-13 19:10:00 +00:00
} else if a . CascadedDeletion ( ) {
a . UnSetCascadedDeletion ( )
patchFinalizer = true
2018-07-10 17:50:29 +00:00
}
if patchFinalizer {
2021-03-15 16:27:41 +00:00
// Although the cascaded deletion/propagation policy finalizer is not set when apps are created via
// API, they will often be set by the user as part of declarative config. As part of a delete
2019-05-13 21:58:22 +00:00
// request, we always calculate the patch to see if we need to set/unset the finalizer.
2025-01-02 23:26:59 +00:00
patch , err := json . Marshal ( map [ string ] any {
"metadata" : map [ string ] any {
2018-06-01 00:54:27 +00:00
"finalizers" : a . Finalizers ,
} ,
} )
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error marshaling finalizers: %w" , err )
2018-06-01 00:54:27 +00:00
}
2020-08-05 18:36:40 +00:00
_ , err = s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( a . Namespace ) . Patch ( ctx , a . Name , types . MergePatchType , patch , metav1 . PatchOptions { } )
2018-06-01 00:54:27 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error patching application with finalizers: %w" , err )
2018-03-16 23:17:03 +00:00
}
}
2022-08-10 09:39:10 +00:00
err = s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( appNs ) . Delete ( ctx , appName , metav1 . DeleteOptions { } )
2020-06-09 18:06:34 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error deleting application: %w" , err )
2018-04-06 15:27:51 +00:00
}
2025-01-07 15:08:51 +00:00
s . logAppEvent ( ctx , a , argo . EventReasonResourceDeleted , "deleted application" )
2019-06-05 19:58:11 +00:00
return & application . ApplicationResponse { } , nil
2018-02-19 11:39:03 +00:00
}
2025-01-06 16:34:32 +00:00
func ( s * Server ) isApplicationPermitted ( selector labels . Selector , minVersion int , claims any , appName , appNs string , projects map [ string ] bool , a v1alpha1 . Application ) bool {
2023-07-18 14:37:27 +00:00
if len ( projects ) > 0 && ! projects [ a . Spec . GetProject ( ) ] {
return false
}
if appVersion , err := strconv . Atoi ( a . ResourceVersion ) ; err == nil && appVersion < minVersion {
return false
}
matchedEvent := ( appName == "" || ( a . Name == appName && a . Namespace == appNs ) ) && selector . Matches ( labels . Set ( a . Labels ) )
if ! matchedEvent {
return false
}
if ! s . isNamespaceEnabled ( a . Namespace ) {
return false
}
2025-02-20 10:40:15 +00:00
if ! s . enf . Enforce ( claims , rbac . ResourceApplications , rbac . ActionGet , a . RBACName ( s . ns ) ) {
2023-07-18 14:37:27 +00:00
// do not emit apps user does not have accessing
return false
}
return true
}
2019-06-05 19:58:11 +00:00
func ( s * Server ) Watch ( q * application . ApplicationQuery , ws application . ApplicationService_WatchServer ) error {
2022-08-10 09:39:10 +00:00
appName := q . GetName ( )
appNs := s . appNamespaceOrDefault ( q . GetAppNamespace ( ) )
2019-03-19 04:50:11 +00:00
logCtx := log . NewEntry ( log . New ( ) )
if q . Name != nil {
logCtx = logCtx . WithField ( "application" , * q . Name )
}
2022-01-06 17:39:37 +00:00
projects := map [ string ] bool { }
2023-03-14 20:33:45 +00:00
for _ , project := range getProjectsFromApplicationQuery ( * q ) {
projects [ project ] = true
2022-01-06 17:39:37 +00:00
}
2018-06-06 04:44:14 +00:00
claims := ws . Context ( ) . Value ( "claims" )
2022-04-18 14:47:13 +00:00
selector , err := labels . Parse ( q . GetSelector ( ) )
2020-06-29 22:32:18 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error parsing labels with selectors: %w" , err )
2020-06-29 22:32:18 +00:00
}
minVersion := 0
2022-04-18 14:47:13 +00:00
if q . GetResourceVersion ( ) != "" {
if minVersion , err = strconv . Atoi ( q . GetResourceVersion ( ) ) ; err != nil {
2020-06-29 22:32:18 +00:00
minVersion = 0
}
}
2019-05-13 21:58:22 +00:00
// sendIfPermitted is a helper to send the application to the client's streaming channel if the
// caller has RBAC privileges permissions to view it
2025-01-06 16:34:32 +00:00
sendIfPermitted := func ( a v1alpha1 . Application , eventType watch . EventType ) {
2023-07-18 14:37:27 +00:00
permitted := s . isApplicationPermitted ( selector , minVersion , claims , appName , appNs , projects , a )
if ! permitted {
2020-06-29 22:32:18 +00:00
return
2019-05-13 21:58:22 +00:00
}
2022-08-17 21:03:24 +00:00
s . inferResourcesStatusHealth ( & a )
2025-01-06 16:34:32 +00:00
err := ws . Send ( & v1alpha1 . ApplicationWatchEvent {
2019-05-13 21:58:22 +00:00
Type : eventType ,
Application : a ,
} )
if err != nil {
logCtx . Warnf ( "Unable to send stream message: %v" , err )
2020-06-29 22:32:18 +00:00
return
2019-05-13 21:58:22 +00:00
}
}
2025-01-06 16:34:32 +00:00
events := make ( chan * v1alpha1 . ApplicationWatchEvent , watchAPIBufferSize )
2020-09-17 02:03:48 +00:00
// Mimic watch API behavior: send ADDED events if no resource version provided
// If watch API is executed for one application when emit event even if resource version is provided
// This is required since single app watch API is used for during operations like app syncing and it is
// critical to never miss events.
2022-04-18 14:47:13 +00:00
if q . GetResourceVersion ( ) == "" || q . GetName ( ) != "" {
2020-09-04 05:13:49 +00:00
apps , err := s . appLister . List ( selector )
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error listing apps with selector: %w" , err )
2020-09-04 05:13:49 +00:00
}
2021-03-10 17:25:44 +00:00
sort . Slice ( apps , func ( i , j int ) bool {
2022-08-10 09:39:10 +00:00
return apps [ i ] . QualifiedName ( ) < apps [ j ] . QualifiedName ( )
2021-03-10 17:25:44 +00:00
} )
2020-09-04 05:13:49 +00:00
for i := range apps {
sendIfPermitted ( * apps [ i ] , watch . Added )
}
2020-06-29 22:32:18 +00:00
}
unsubscribe := s . appBroadcaster . Subscribe ( events )
defer unsubscribe ( )
for {
select {
case event := <- events :
sendIfPermitted ( event . Application , event . Type )
case <- ws . Context ( ) . Done ( ) :
return nil
2018-02-28 05:37:23 +00:00
}
}
}
2018-02-28 11:07:56 +00:00
2025-01-06 16:34:32 +00:00
func ( s * Server ) validateAndNormalizeApp ( ctx context . Context , app * v1alpha1 . Application , proj * v1alpha1 . AppProject , validate bool ) error {
2022-08-10 09:39:10 +00:00
if app . GetName ( ) == "" {
2024-12-30 08:56:41 +00:00
return errors . New ( "resource name may not be empty" )
2022-08-10 09:39:10 +00:00
}
2024-11-18 21:23:49 +00:00
// ensure sources names are unique
if app . Spec . HasMultipleSources ( ) {
sourceNames := make ( map [ string ] bool )
for _ , source := range app . Spec . Sources {
2025-06-15 20:25:12 +00:00
if source . Name != "" && sourceNames [ source . Name ] {
2024-11-18 21:23:49 +00:00
return fmt . Errorf ( "application %s has duplicate source name: %s" , app . Name , source . Name )
}
sourceNames [ source . Name ] = true
}
}
2022-08-10 09:39:10 +00:00
appNs := s . appNamespaceOrDefault ( app . Namespace )
currApp , err := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( appNs ) . Get ( ctx , app . Name , metav1 . GetOptions { } )
2019-01-17 18:30:31 +00:00
if err != nil {
2025-01-03 17:09:37 +00:00
if ! apierrors . IsNotFound ( err ) {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error getting application by name: %w" , err )
2019-01-17 18:30:31 +00:00
}
// Kubernetes go-client will return a pointer to a zero-value app instead of nil, even
// though the API response was NotFound. This behavior was confirmed via logs.
currApp = nil
2019-01-10 19:37:32 +00:00
}
if currApp != nil && currApp . Spec . GetProject ( ) != app . Spec . GetProject ( ) {
// When changing projects, caller must have application create & update privileges in new project
// NOTE: the update check was already verified in the caller to this function
2025-02-20 10:40:15 +00:00
if err := s . enf . EnforceErr ( ctx . Value ( "claims" ) , rbac . ResourceApplications , rbac . ActionCreate , app . RBACName ( s . ns ) ) ; err != nil {
2019-02-28 21:11:47 +00:00
return err
2019-01-10 19:37:32 +00:00
}
// They also need 'update' privileges in the old project
2025-02-20 10:40:15 +00:00
if err := s . enf . EnforceErr ( ctx . Value ( "claims" ) , rbac . ResourceApplications , rbac . ActionUpdate , currApp . RBACName ( s . ns ) ) ; err != nil {
2019-02-28 21:11:47 +00:00
return err
2019-01-10 19:37:32 +00:00
}
2025-08-18 03:29:22 +00:00
// Validate that the new project exists and the application is allowed to use it
newProj , err := s . getAppProject ( ctx , app , log . WithFields ( applog . GetAppLogFields ( app ) ) )
if err != nil {
return err
}
proj = newProj
2018-06-23 18:38:35 +00:00
}
2019-09-06 22:37:25 +00:00
2025-01-13 18:15:42 +00:00
if _ , err := argo . GetDestinationCluster ( ctx , app . Spec . Destination , s . db ) ; err != nil {
2021-08-18 09:10:52 +00:00
return status . Errorf ( codes . InvalidArgument , "application destination spec for %s is invalid: %s" , app . Name , err . Error ( ) )
2020-11-21 00:45:22 +00:00
}
2025-01-06 16:34:32 +00:00
var conditions [ ] v1alpha1 . ApplicationCondition
2023-08-14 14:06:43 +00:00
2020-07-30 20:28:36 +00:00
if validate {
2025-01-06 16:34:32 +00:00
conditions := make ( [ ] v1alpha1 . ApplicationCondition , 0 )
2023-06-21 20:30:46 +00:00
condition , err := argo . ValidateRepo ( ctx , app , s . repoClientset , s . db , s . kubectl , proj , s . settingsMgr )
2020-07-30 20:28:36 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error validating the repo: %w" , err )
2020-07-30 20:28:36 +00:00
}
2022-12-16 20:47:08 +00:00
conditions = append ( conditions , condition ... )
2020-07-30 20:28:36 +00:00
if len ( conditions ) > 0 {
2021-08-18 09:10:52 +00:00
return status . Errorf ( codes . InvalidArgument , "application spec for %s is invalid: %s" , app . Name , argo . FormatAppConditions ( conditions ) )
2020-07-30 20:28:36 +00:00
}
2019-04-29 22:04:25 +00:00
}
2019-07-03 20:09:54 +00:00
conditions , err = argo . ValidatePermissions ( ctx , & app . Spec , proj , s . db )
2018-04-20 08:19:53 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error validating project permissions: %w" , err )
2018-04-20 08:19:53 +00:00
}
2018-07-09 17:45:03 +00:00
if len ( conditions ) > 0 {
2021-08-18 09:10:52 +00:00
return status . Errorf ( codes . InvalidArgument , "application spec for %s is invalid: %s" , app . Name , argo . FormatAppConditions ( conditions ) )
2018-07-09 17:45:03 +00:00
}
2019-07-03 20:09:54 +00:00
2025-10-15 01:38:14 +00:00
// Validate managed-by-url annotation
managedByURLConditions := argo . ValidateManagedByURL ( app )
if len ( managedByURLConditions ) > 0 {
return status . Errorf ( codes . InvalidArgument , "application spec for %s is invalid: %s" , app . Name , argo . FormatAppConditions ( managedByURLConditions ) )
}
2019-09-06 22:37:25 +00:00
app . Spec = * argo . NormalizeApplicationSpec ( & app . Spec )
2018-04-20 08:19:53 +00:00
return nil
}
2026-04-02 09:48:29 +00:00
func ( s * Server ) getApplicationClusterConfig ( ctx context . Context , a * v1alpha1 . Application , p * v1alpha1 . AppProject ) ( * rest . Config , error ) {
2025-01-13 18:15:42 +00:00
cluster , err := argo . GetDestinationCluster ( ctx , a . Spec . Destination , s . db )
2018-04-06 20:08:29 +00:00
if err != nil {
2025-01-13 18:15:42 +00:00
return nil , fmt . Errorf ( "error validating destination: %w" , err )
2018-04-06 20:08:29 +00:00
}
2025-01-13 18:15:42 +00:00
config , err := cluster . RESTConfig ( )
2024-10-06 14:55:26 +00:00
if err != nil {
return nil , fmt . Errorf ( "error getting cluster REST config: %w" , err )
}
2026-04-02 09:48:29 +00:00
impersonationEnabled , err := s . settingsMgr . IsImpersonationEnabled ( )
if err != nil {
return nil , fmt . Errorf ( "error getting impersonation setting: %w" , err )
}
if ! impersonationEnabled {
return config , nil
}
user , err := settings . DeriveServiceAccountToImpersonate ( p , a , cluster )
if err != nil {
return nil , fmt . Errorf ( "error deriving service account to impersonate: %w" , err )
}
config . Impersonate = rest . ImpersonationConfig {
UserName : user ,
}
2019-10-15 21:36:33 +00:00
return config , err
2018-04-09 17:39:46 +00:00
}
2019-10-10 22:17:13 +00:00
// getCachedAppState loads the cached state and trigger app refresh if cache is missing
2025-01-06 16:34:32 +00:00
func ( s * Server ) getCachedAppState ( ctx context . Context , a * v1alpha1 . Application , getFromCache func ( ) error ) error {
2019-10-10 22:17:13 +00:00
err := getFromCache ( )
2024-06-12 23:43:25 +00:00
if err != nil && errors . Is ( err , servercache . ErrCacheMiss ) {
2025-01-06 16:34:32 +00:00
conditions := a . Status . GetConditions ( map [ v1alpha1 . ApplicationConditionType ] bool {
v1alpha1 . ApplicationConditionComparisonError : true ,
v1alpha1 . ApplicationConditionInvalidSpecError : true ,
2019-10-10 22:17:13 +00:00
} )
if len ( conditions ) > 0 {
2025-01-06 16:34:32 +00:00
return errors . New ( argo . FormatAppConditions ( conditions ) )
2019-10-10 22:17:13 +00:00
}
_ , err = s . Get ( ctx , & application . ApplicationQuery {
2026-02-24 16:42:12 +00:00
Name : new ( a . GetName ( ) ) ,
AppNamespace : new ( a . GetNamespace ( ) ) ,
Refresh : new ( string ( v1alpha1 . RefreshTypeNormal ) ) ,
2019-10-10 22:17:13 +00:00
} )
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error getting application by query: %w" , err )
2019-10-10 22:17:13 +00:00
}
return getFromCache ( )
}
return err
}
2025-01-06 16:34:32 +00:00
func ( s * Server ) getAppResources ( ctx context . Context , a * v1alpha1 . Application ) ( * v1alpha1 . ApplicationTree , error ) {
var tree v1alpha1 . ApplicationTree
2019-10-10 22:17:13 +00:00
err := s . getCachedAppState ( ctx , a , func ( ) error {
2022-08-10 09:39:10 +00:00
return s . cache . GetAppResourcesTree ( a . InstanceName ( s . ns ) , & tree )
2019-10-10 22:17:13 +00:00
} )
2022-05-20 21:16:55 +00:00
if err != nil {
2025-02-11 01:38:20 +00:00
if errors . Is ( err , ErrCacheMiss ) {
fmt . Println ( "Cache Key is missing.\nEnsure that the Redis compression setting on the Application controller and CLI is same. See --redis-compress." )
}
2023-03-23 13:22:05 +00:00
return & tree , fmt . Errorf ( "error getting cached app resource tree: %w" , err )
2022-05-20 21:16:55 +00:00
}
return & tree , nil
2018-11-17 01:10:04 +00:00
}
2025-01-06 16:34:32 +00:00
func ( s * Server ) getAppLiveResource ( ctx context . Context , action string , q * application . ApplicationResourceRequest ) ( * v1alpha1 . ResourceNode , * rest . Config , * v1alpha1 . Application , error ) {
2025-01-17 20:20:40 +00:00
fineGrainedInheritanceDisabled , err := s . settingsMgr . ApplicationFineGrainedRBACInheritanceDisabled ( )
if err != nil {
return nil , nil , nil , err
}
2025-02-20 10:40:15 +00:00
if fineGrainedInheritanceDisabled && ( action == rbac . ActionDelete || action == rbac . ActionUpdate ) {
2025-01-17 20:20:40 +00:00
action = fmt . Sprintf ( "%s/%s/%s/%s/%s" , action , q . GetGroup ( ) , q . GetKind ( ) , q . GetNamespace ( ) , q . GetResourceName ( ) )
}
2026-04-02 09:48:29 +00:00
a , p , err := s . getApplicationEnforceRBACInformer ( ctx , action , q . GetProject ( ) , q . GetAppNamespace ( ) , q . GetName ( ) )
2025-02-20 10:40:15 +00:00
if ! fineGrainedInheritanceDisabled && err != nil && errors . Is ( err , argocommon . PermissionDeniedAPIError ) && ( action == rbac . ActionDelete || action == rbac . ActionUpdate ) {
2024-05-13 19:04:18 +00:00
action = fmt . Sprintf ( "%s/%s/%s/%s/%s" , action , q . GetGroup ( ) , q . GetKind ( ) , q . GetNamespace ( ) , q . GetResourceName ( ) )
a , _ , err = s . getApplicationEnforceRBACInformer ( ctx , action , q . GetProject ( ) , q . GetAppNamespace ( ) , q . GetName ( ) )
}
2018-06-12 17:43:16 +00:00
if err != nil {
2019-02-28 21:11:47 +00:00
return nil , nil , nil , err
2018-06-06 04:44:14 +00:00
}
2024-05-13 19:04:18 +00:00
2022-08-17 15:48:27 +00:00
tree , err := s . getAppResources ( ctx , a )
2018-11-17 01:10:04 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , nil , nil , fmt . Errorf ( "error getting app resources: %w" , err )
2018-11-17 01:10:04 +00:00
}
2022-04-18 14:47:13 +00:00
found := tree . FindNode ( q . GetGroup ( ) , q . GetKind ( ) , q . GetNamespace ( ) , q . GetResourceName ( ) )
2025-03-27 16:37:52 +00:00
if found == nil || found . UID == "" {
2022-04-18 14:47:13 +00:00
return nil , nil , nil , status . Errorf ( codes . InvalidArgument , "%s %s %s not found as part of application %s" , q . GetKind ( ) , q . GetGroup ( ) , q . GetResourceName ( ) , q . GetName ( ) )
2018-11-28 21:38:02 +00:00
}
2026-04-02 09:48:29 +00:00
config , err := s . getApplicationClusterConfig ( ctx , a , p )
2018-11-28 21:38:02 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , nil , nil , fmt . Errorf ( "error getting application cluster config: %w" , err )
2018-08-15 22:01:29 +00:00
}
2026-04-02 09:48:29 +00:00
2018-11-28 21:38:02 +00:00
return found , config , a , nil
}
2019-06-05 19:58:11 +00:00
func ( s * Server ) GetResource ( ctx context . Context , q * application . ApplicationResourceRequest ) ( * application . ApplicationResourceResponse , error ) {
2025-02-20 10:40:15 +00:00
res , config , _ , err := s . getAppLiveResource ( ctx , rbac . ActionGet , q )
2018-04-09 17:39:46 +00:00
if err != nil {
2023-03-23 13:22:05 +00:00
return nil , err
2018-04-09 17:39:46 +00:00
}
2020-11-03 19:24:14 +00:00
// make sure to use specified resource version if provided
2022-04-18 14:47:13 +00:00
if q . GetVersion ( ) != "" {
res . Version = q . GetVersion ( )
2020-11-03 19:24:14 +00:00
}
2020-08-05 18:36:40 +00:00
obj , err := s . kubectl . GetResource ( ctx , config , res . GroupKindVersion ( ) , res . Name , res . Namespace )
2018-04-09 17:39:46 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error getting resource: %w" , err )
2018-04-09 17:39:46 +00:00
}
2024-10-30 16:52:37 +00:00
obj , err = s . replaceSecretValues ( obj )
2018-12-07 23:40:55 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error replacing secret values: %w" , err )
2018-12-07 23:40:55 +00:00
}
2018-11-28 21:38:02 +00:00
data , err := json . Marshal ( obj . Object )
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error marshaling object: %w" , err )
2018-11-28 21:38:02 +00:00
}
2022-04-18 14:47:13 +00:00
manifest := string ( data )
return & application . ApplicationResourceResponse { Manifest : & manifest } , nil
2018-08-15 22:01:29 +00:00
}
2024-10-30 16:52:37 +00:00
func ( s * Server ) replaceSecretValues ( obj * unstructured . Unstructured ) ( * unstructured . Unstructured , error ) {
2018-12-07 23:40:55 +00:00
if obj . GetKind ( ) == kube . SecretKind && obj . GroupVersionKind ( ) . Group == "" {
2024-10-30 16:52:37 +00:00
_ , obj , err := diff . HideSecretData ( nil , obj , s . settingsMgr . GetSensitiveAnnotations ( ) )
2019-07-11 16:40:48 +00:00
if err != nil {
return nil , err
2018-12-07 23:40:55 +00:00
}
2019-07-11 16:40:48 +00:00
return obj , err
2018-12-07 23:40:55 +00:00
}
2019-07-11 16:40:48 +00:00
return obj , nil
2018-12-07 23:40:55 +00:00
}
2018-12-28 23:57:52 +00:00
// PatchResource patches a resource
2019-06-05 19:58:11 +00:00
func ( s * Server ) PatchResource ( ctx context . Context , q * application . ApplicationResourcePatchRequest ) ( * application . ApplicationResourceResponse , error ) {
resourceRequest := & application . ApplicationResourceRequest {
2018-12-28 23:57:52 +00:00
Name : q . Name ,
2022-08-10 09:39:10 +00:00
AppNamespace : q . AppNamespace ,
2018-12-28 23:57:52 +00:00
Namespace : q . Namespace ,
ResourceName : q . ResourceName ,
Kind : q . Kind ,
Version : q . Version ,
Group : q . Group ,
2023-07-19 13:22:28 +00:00
Project : q . Project ,
2018-12-28 23:57:52 +00:00
}
2025-02-20 10:40:15 +00:00
res , config , a , err := s . getAppLiveResource ( ctx , rbac . ActionUpdate , resourceRequest )
2018-12-28 23:57:52 +00:00
if err != nil {
2019-02-28 21:11:47 +00:00
return nil , err
2018-12-28 23:57:52 +00:00
}
2022-04-18 14:47:13 +00:00
manifest , err := s . kubectl . PatchResource ( ctx , config , res . GroupKindVersion ( ) , res . Name , res . Namespace , types . PatchType ( q . GetPatchType ( ) ) , [ ] byte ( q . GetPatch ( ) ) )
2018-12-28 23:57:52 +00:00
if err != nil {
2021-02-26 03:24:40 +00:00
// don't expose real error for secrets since it might contain secret data
if res . Kind == kube . SecretKind && res . Group == "" {
return nil , fmt . Errorf ( "failed to patch Secret %s/%s" , res . Namespace , res . Name )
}
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error patching resource: %w" , err )
2018-12-28 23:57:52 +00:00
}
2023-03-23 13:22:05 +00:00
if manifest == nil {
2024-12-30 08:56:41 +00:00
return nil , errors . New ( "failed to patch resource: manifest was nil" )
2023-03-23 13:22:05 +00:00
}
2024-10-30 16:52:37 +00:00
manifest , err = s . replaceSecretValues ( manifest )
2018-12-28 23:57:52 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error replacing secret values: %w" , err )
2018-12-28 23:57:52 +00:00
}
data , err := json . Marshal ( manifest . Object )
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "erro marshaling manifest object: %w" , err )
2018-12-28 23:57:52 +00:00
}
2025-01-07 15:08:51 +00:00
s . logAppEvent ( ctx , a , argo . EventReasonResourceUpdated , fmt . Sprintf ( "patched resource %s/%s '%s'" , q . GetGroup ( ) , q . GetKind ( ) , q . GetResourceName ( ) ) )
2022-04-18 14:47:13 +00:00
m := string ( data )
2019-06-05 19:58:11 +00:00
return & application . ApplicationResourceResponse {
2022-04-18 14:47:13 +00:00
Manifest : & m ,
2018-12-28 23:57:52 +00:00
} , nil
}
2020-05-27 17:22:13 +00:00
// DeleteResource deletes a specified resource
2019-06-05 19:58:11 +00:00
func ( s * Server ) DeleteResource ( ctx context . Context , q * application . ApplicationResourceDeleteRequest ) ( * application . ApplicationResponse , error ) {
resourceRequest := & application . ApplicationResourceRequest {
2018-12-07 00:00:10 +00:00
Name : q . Name ,
2022-08-10 09:39:10 +00:00
AppNamespace : q . AppNamespace ,
2018-12-07 00:00:10 +00:00
Namespace : q . Namespace ,
ResourceName : q . ResourceName ,
Kind : q . Kind ,
Version : q . Version ,
Group : q . Group ,
2023-07-19 13:22:28 +00:00
Project : q . Project ,
2018-12-07 00:00:10 +00:00
}
2025-02-20 10:40:15 +00:00
res , config , a , err := s . getAppLiveResource ( ctx , rbac . ActionDelete , resourceRequest )
2018-11-17 01:10:04 +00:00
if err != nil {
2019-02-28 21:11:47 +00:00
return nil , err
2018-11-17 01:10:04 +00:00
}
2021-03-09 13:04:14 +00:00
var deleteOption metav1 . DeleteOptions
2025-01-22 21:13:51 +00:00
switch {
case q . GetOrphan ( ) :
2021-03-09 13:04:14 +00:00
propagationPolicy := metav1 . DeletePropagationOrphan
deleteOption = metav1 . DeleteOptions { PropagationPolicy : & propagationPolicy }
2025-01-22 21:13:51 +00:00
case q . GetForce ( ) :
2021-03-09 13:04:14 +00:00
propagationPolicy := metav1 . DeletePropagationBackground
zeroGracePeriod := int64 ( 0 )
deleteOption = metav1 . DeleteOptions { PropagationPolicy : & propagationPolicy , GracePeriodSeconds : & zeroGracePeriod }
2025-01-22 21:13:51 +00:00
default :
2021-03-09 13:04:14 +00:00
propagationPolicy := metav1 . DeletePropagationForeground
deleteOption = metav1 . DeleteOptions { PropagationPolicy : & propagationPolicy }
2018-12-07 00:00:10 +00:00
}
2026-04-02 09:48:29 +00:00
2021-03-09 13:04:14 +00:00
err = s . kubectl . DeleteResource ( ctx , config , res . GroupKindVersion ( ) , res . Name , res . Namespace , deleteOption )
2018-11-28 21:38:02 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error deleting resource: %w" , err )
2018-11-28 21:38:02 +00:00
}
2025-01-07 15:08:51 +00:00
s . logAppEvent ( ctx , a , argo . EventReasonResourceDeleted , fmt . Sprintf ( "deleted resource %s/%s '%s'" , q . GetGroup ( ) , q . GetKind ( ) , q . GetResourceName ( ) ) )
2019-06-05 19:58:11 +00:00
return & application . ApplicationResponse { } , nil
2018-11-28 21:38:02 +00:00
}
2025-01-06 16:34:32 +00:00
func ( s * Server ) ResourceTree ( ctx context . Context , q * application . ResourcesQuery ) ( * v1alpha1 . ApplicationTree , error ) {
2025-02-20 10:40:15 +00:00
a , _ , err := s . getApplicationEnforceRBACInformer ( ctx , rbac . ActionGet , q . GetProject ( ) , q . GetAppNamespace ( ) , q . GetApplicationName ( ) )
2018-11-30 18:32:31 +00:00
if err != nil {
2019-02-28 21:11:47 +00:00
return nil , err
2018-11-30 18:32:31 +00:00
}
2022-08-17 15:48:27 +00:00
return s . getAppResources ( ctx , a )
2018-11-17 01:10:04 +00:00
}
2020-08-31 17:18:12 +00:00
func ( s * Server ) WatchResourceTree ( q * application . ResourcesQuery , ws application . ApplicationService_WatchResourceTreeServer ) error {
2025-02-20 10:40:15 +00:00
_ , _ , err := s . getApplicationEnforceRBACInformer ( ws . Context ( ) , rbac . ActionGet , q . GetProject ( ) , q . GetAppNamespace ( ) , q . GetApplicationName ( ) )
2020-09-11 18:14:08 +00:00
if err != nil {
return err
}
2023-08-30 13:34:52 +00:00
cacheKey := argo . AppInstanceName ( q . GetApplicationName ( ) , q . GetAppNamespace ( ) , s . ns )
return s . cache . OnAppResourcesTreeChanged ( ws . Context ( ) , cacheKey , func ( ) error {
2025-01-06 16:34:32 +00:00
var tree v1alpha1 . ApplicationTree
2023-08-30 13:34:52 +00:00
err := s . cache . GetAppResourcesTree ( cacheKey , & tree )
2020-08-31 17:18:12 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error getting app resource tree: %w" , err )
2020-08-31 17:18:12 +00:00
}
return ws . Send ( & tree )
} )
}
2025-01-06 16:34:32 +00:00
func ( s * Server ) RevisionMetadata ( ctx context . Context , q * application . RevisionMetadataQuery ) ( * v1alpha1 . RevisionMetadata , error ) {
2025-02-20 10:40:15 +00:00
a , proj , err := s . getApplicationEnforceRBACInformer ( ctx , rbac . ActionGet , q . GetProject ( ) , q . GetAppNamespace ( ) , q . GetName ( ) )
2019-06-21 22:51:48 +00:00
if err != nil {
return nil , err
}
2022-08-17 15:48:27 +00:00
2024-06-24 01:08:17 +00:00
source , err := getAppSourceBySourceIndexAndVersionId ( a , q . SourceIndex , q . VersionId )
if err != nil {
return nil , fmt . Errorf ( "error getting app source by source index and version ID: %w" , err )
2024-06-10 21:54:07 +00:00
}
2024-06-08 01:47:55 +00:00
repo , err := s . db . GetRepository ( ctx , source . RepoURL , proj . Name )
2019-06-21 22:51:48 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error getting repository by URL: %w" , err )
2019-06-21 22:51:48 +00:00
}
conn , repoClient , err := s . repoClientset . NewRepoServerClient ( )
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error creating repo server client: %w" , err )
2019-06-21 22:51:48 +00:00
}
2025-05-21 16:25:32 +00:00
defer utilio . Close ( conn )
2020-11-03 17:43:02 +00:00
return repoClient . GetRevisionMetadata ( ctx , & apiclient . RepoServerRevisionMetadataRequest {
Repo : repo ,
Revision : q . GetRevision ( ) ,
CheckSignature : len ( proj . Spec . SignatureKeys ) > 0 ,
} )
2019-06-21 22:51:48 +00:00
}
2023-04-18 17:04:21 +00:00
// RevisionChartDetails returns the helm chart metadata, as fetched from the reposerver
2025-01-06 16:34:32 +00:00
func ( s * Server ) RevisionChartDetails ( ctx context . Context , q * application . RevisionMetadataQuery ) ( * v1alpha1 . ChartDetails , error ) {
2025-02-20 10:40:15 +00:00
a , _ , err := s . getApplicationEnforceRBACInformer ( ctx , rbac . ActionGet , q . GetProject ( ) , q . GetAppNamespace ( ) , q . GetName ( ) )
2023-04-18 17:04:21 +00:00
if err != nil {
2023-07-14 17:03:48 +00:00
return nil , err
2023-04-18 17:04:21 +00:00
}
2024-06-10 21:54:07 +00:00
2024-06-24 01:08:17 +00:00
source , err := getAppSourceBySourceIndexAndVersionId ( a , q . SourceIndex , q . VersionId )
if err != nil {
return nil , fmt . Errorf ( "error getting app source by source index and version ID: %w" , err )
2024-06-10 21:54:07 +00:00
}
if source . Chart == "" {
return nil , fmt . Errorf ( "no chart found for application: %v" , q . GetName ( ) )
2023-04-18 17:04:21 +00:00
}
2024-06-10 21:54:07 +00:00
repo , err := s . db . GetRepository ( ctx , source . RepoURL , a . Spec . Project )
2023-04-18 17:04:21 +00:00
if err != nil {
return nil , fmt . Errorf ( "error getting repository by URL: %w" , err )
}
conn , repoClient , err := s . repoClientset . NewRepoServerClient ( )
if err != nil {
return nil , fmt . Errorf ( "error creating repo server client: %w" , err )
}
2025-05-21 16:25:32 +00:00
defer utilio . Close ( conn )
2023-04-18 17:04:21 +00:00
return repoClient . GetRevisionChartDetails ( ctx , & apiclient . RepoServerRevisionChartDetailsRequest {
Repo : repo ,
2024-06-10 21:54:07 +00:00
Name : source . Chart ,
2023-04-18 17:04:21 +00:00
Revision : q . GetRevision ( ) ,
} )
}
2025-06-06 11:27:02 +00:00
func ( s * Server ) GetOCIMetadata ( ctx context . Context , q * application . RevisionMetadataQuery ) ( * v1alpha1 . OCIMetadata , error ) {
a , proj , err := s . getApplicationEnforceRBACInformer ( ctx , rbac . ActionGet , q . GetProject ( ) , q . GetAppNamespace ( ) , q . GetName ( ) )
if err != nil {
return nil , err
}
source , err := getAppSourceBySourceIndexAndVersionId ( a , q . SourceIndex , q . VersionId )
if err != nil {
return nil , fmt . Errorf ( "error getting app source by source index and version ID: %w" , err )
}
repo , err := s . db . GetRepository ( ctx , source . RepoURL , proj . Name )
if err != nil {
return nil , fmt . Errorf ( "error getting repository by URL: %w" , err )
}
conn , repoClient , err := s . repoClientset . NewRepoServerClient ( )
if err != nil {
return nil , fmt . Errorf ( "error creating repo server client: %w" , err )
}
defer utilio . Close ( conn )
return repoClient . GetOCIMetadata ( ctx , & apiclient . RepoServerRevisionChartDetailsRequest {
Repo : repo ,
Name : source . Chart ,
Revision : q . GetRevision ( ) ,
} )
}
2024-06-24 01:08:17 +00:00
// getAppSourceBySourceIndexAndVersionId returns the source for a specific source index and version ID. Source index and
// version ID are optional. If the source index is not specified, it defaults to 0. If the version ID is not specified,
// we use the source(s) currently configured for the app. If the version ID is specified, we find the source for that
// version ID. If the version ID is not found, we return an error. If the source index is out of bounds for whichever
// source we choose (configured sources or sources for a specific version), we return an error.
2025-01-06 16:34:32 +00:00
func getAppSourceBySourceIndexAndVersionId ( a * v1alpha1 . Application , sourceIndexMaybe * int32 , versionIdMaybe * int32 ) ( v1alpha1 . ApplicationSource , error ) {
2024-06-24 01:08:17 +00:00
// Start with all the app's configured sources.
sources := a . Spec . GetSources ( )
// If the user specified a version, get the sources for that version. If the version is not found, return an error.
if versionIdMaybe != nil {
versionId := int64 ( * versionIdMaybe )
var err error
sources , err = getSourcesByVersionId ( a , versionId )
if err != nil {
2025-01-06 16:34:32 +00:00
return v1alpha1 . ApplicationSource { } , fmt . Errorf ( "error getting source by version ID: %w" , err )
2024-06-24 01:08:17 +00:00
}
}
// Start by assuming we want the first source.
sourceIndex := 0
// If the user specified a source index, use that instead.
if sourceIndexMaybe != nil {
sourceIndex = int ( * sourceIndexMaybe )
if sourceIndex >= len ( sources ) {
if len ( sources ) == 1 {
2025-01-06 16:34:32 +00:00
return v1alpha1 . ApplicationSource { } , fmt . Errorf ( "source index %d not found because there is only 1 source" , sourceIndex )
2024-06-24 01:08:17 +00:00
}
2025-01-06 16:34:32 +00:00
return v1alpha1 . ApplicationSource { } , fmt . Errorf ( "source index %d not found because there are only %d sources" , sourceIndex , len ( sources ) )
2024-06-24 01:08:17 +00:00
}
}
source := sources [ sourceIndex ]
return source , nil
}
// getRevisionHistoryByVersionId returns the revision history for a specific version ID.
// If the version ID is not found, it returns an empty revision history and false.
2025-01-06 16:34:32 +00:00
func getRevisionHistoryByVersionId ( histories v1alpha1 . RevisionHistories , versionId int64 ) ( v1alpha1 . RevisionHistory , bool ) {
2024-06-24 01:08:17 +00:00
for _ , h := range histories {
if h . ID == versionId {
return h , true
}
}
2025-01-06 16:34:32 +00:00
return v1alpha1 . RevisionHistory { } , false
2024-06-24 01:08:17 +00:00
}
// getSourcesByVersionId returns the sources for a specific version ID. If there is no history, it returns an error.
// If the version ID is not found, it returns an error. If the version ID is found, and there are multiple sources,
// it returns the sources for that version ID. If the version ID is found, and there is only one source, it returns
// a slice with just the single source.
2025-01-06 16:34:32 +00:00
func getSourcesByVersionId ( a * v1alpha1 . Application , versionId int64 ) ( [ ] v1alpha1 . ApplicationSource , error ) {
2024-06-24 01:08:17 +00:00
if len ( a . Status . History ) == 0 {
return nil , fmt . Errorf ( "version ID %d not found because the app has no history" , versionId )
}
h , ok := getRevisionHistoryByVersionId ( a . Status . History , versionId )
if ! ok {
return nil , fmt . Errorf ( "revision history not found for version ID %d" , versionId )
}
if len ( h . Sources ) > 0 {
return h . Sources , nil
}
return [ ] v1alpha1 . ApplicationSource { h . Source } , nil
}
2019-12-26 22:42:56 +00:00
func isMatchingResource ( q * application . ResourcesQuery , key kube . ResourceKey ) bool {
2022-04-18 14:47:13 +00:00
return ( q . GetName ( ) == "" || q . GetName ( ) == key . Name ) &&
( q . GetNamespace ( ) == "" || q . GetNamespace ( ) == key . Namespace ) &&
( q . GetGroup ( ) == "" || q . GetGroup ( ) == key . Group ) &&
( q . GetKind ( ) == "" || q . GetKind ( ) == key . Kind )
2019-12-26 22:42:56 +00:00
}
2019-06-05 19:58:11 +00:00
func ( s * Server ) ManagedResources ( ctx context . Context , q * application . ResourcesQuery ) ( * application . ManagedResourcesResponse , error ) {
2025-02-20 10:40:15 +00:00
a , _ , err := s . getApplicationEnforceRBACInformer ( ctx , rbac . ActionGet , q . GetProject ( ) , q . GetAppNamespace ( ) , q . GetApplicationName ( ) )
2018-11-28 21:38:02 +00:00
if err != nil {
2023-03-23 13:22:05 +00:00
return nil , err
2018-11-28 21:38:02 +00:00
}
2022-08-17 15:48:27 +00:00
2025-01-06 16:34:32 +00:00
items := make ( [ ] * v1alpha1 . ResourceDiff , 0 )
2019-10-10 22:17:13 +00:00
err = s . getCachedAppState ( ctx , a , func ( ) error {
2022-08-10 09:39:10 +00:00
return s . cache . GetAppManagedResources ( a . InstanceName ( s . ns ) , & items )
2019-10-10 22:17:13 +00:00
} )
2018-11-28 21:38:02 +00:00
if err != nil {
2026-02-21 13:39:41 +00:00
return nil , fmt . Errorf ( "error getting cached app managed resources: %w" , err )
2018-11-28 21:38:02 +00:00
}
2019-12-26 22:42:56 +00:00
res := & application . ManagedResourcesResponse { }
for i := range items {
item := items [ i ]
2023-08-01 15:18:36 +00:00
if ! item . Hook && isMatchingResource ( q , kube . ResourceKey { Name : item . Name , Namespace : item . Namespace , Kind : item . Kind , Group : item . Group } ) {
2019-12-26 22:42:56 +00:00
res . Items = append ( res . Items , item )
}
}
return res , nil
2018-08-15 22:01:29 +00:00
}
2019-06-05 19:58:11 +00:00
func ( s * Server ) PodLogs ( q * application . ApplicationPodLogsQuery , ws application . ApplicationService_PodLogsServer ) error {
2021-02-08 17:27:24 +00:00
if q . PodName != nil {
podKind := "Pod"
q . Kind = & podKind
q . ResourceName = q . PodName
}
2021-02-11 01:58:13 +00:00
var sinceSeconds , tailLines * int64
2022-04-18 14:47:13 +00:00
if q . GetSinceSeconds ( ) > 0 {
2026-02-24 16:42:12 +00:00
sinceSeconds = new ( q . GetSinceSeconds ( ) )
2021-02-11 01:58:13 +00:00
}
2022-04-18 14:47:13 +00:00
if q . GetTailLines ( ) > 0 {
2026-02-24 16:42:12 +00:00
tailLines = new ( q . GetTailLines ( ) )
2021-02-11 01:58:13 +00:00
}
var untilTime * metav1 . Time
if q . GetUntilTime ( ) != "" {
2025-01-07 15:25:22 +00:00
val , err := time . Parse ( time . RFC3339Nano , q . GetUntilTime ( ) )
if err != nil {
2024-06-12 23:43:25 +00:00
return fmt . Errorf ( "invalid untilTime parameter value: %w" , err )
2021-02-11 01:58:13 +00:00
}
2025-01-07 15:25:22 +00:00
untilTimeVal := metav1 . NewTime ( val )
untilTime = & untilTimeVal
2021-02-11 01:58:13 +00:00
}
literal := ""
inverse := false
if q . GetFilter ( ) != "" {
literal = * q . Filter
if literal [ 0 ] == '!' {
literal = literal [ 1 : ]
inverse = true
}
}
2026-04-02 09:48:29 +00:00
a , p , err := s . getApplicationEnforceRBACInformer ( ws . Context ( ) , rbac . ActionGet , q . GetProject ( ) , q . GetAppNamespace ( ) , q . GetName ( ) )
2021-02-11 01:58:13 +00:00
if err != nil {
return err
}
2025-02-25 18:27:32 +00:00
if err := s . enf . EnforceErr ( ws . Context ( ) . Value ( "claims" ) , rbac . ResourceLogs , rbac . ActionGet , a . RBACName ( s . ns ) ) ; err != nil {
return err
feat: Introduce RBAC based approach to pod logs #7211 (#8353)
* initial changes in settings, app, account, admin, rbac, doc and tests
Signed-off-by: reggie-k <reginakagan@gmail.com>
* rbac.md docs and better comments in account and app
Signed-off-by: reggie-k <reginakagan@gmail.com>
* initial changes in settings, app, account, admin, rbac, doc and tests
Signed-off-by: reggie-k <reginakagan@gmail.com>
* rbac.md docs and better comments in account and app
Signed-off-by: reggie-k <reginakagan@gmail.com>
* initial changes in settings, app, account, admin, rbac, doc and tests
Signed-off-by: reggie-k <reginakagan@gmail.com>
* rbac.md docs and better comments in account and app
Signed-off-by: reggie-k <reginakagan@gmail.com>
* rebase fix
Signed-off-by: reggie-k <reginakagan@gmail.com>
* updated docs for argocd-cm.yaml
Signed-off-by: reggie-k <reginakagan@gmail.com>
2022-03-18 18:40:48 +00:00
}
2022-08-17 15:48:27 +00:00
tree , err := s . getAppResources ( ws . Context ( ) , a )
2021-02-11 01:58:13 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error getting app resource tree: %w" , err )
2021-02-11 01:58:13 +00:00
}
2026-04-02 09:48:29 +00:00
config , err := s . getApplicationClusterConfig ( ws . Context ( ) , a , p )
2021-02-11 01:58:13 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error getting application cluster config: %w" , err )
2021-02-11 01:58:13 +00:00
}
kubeClientset , err := kubernetes . NewForConfig ( config )
2021-02-08 17:27:24 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return fmt . Errorf ( "error creating kube client: %w" , err )
2021-02-08 17:27:24 +00:00
}
// from the tree find pods which match query of kind, group, and resource name
pods := getSelectedPods ( tree . Nodes , q )
if len ( pods ) == 0 {
return nil
}
2024-04-03 17:06:12 +00:00
maxPodLogsToRender , err := s . settingsMgr . GetMaxPodLogsToRender ( )
if err != nil {
return fmt . Errorf ( "error getting MaxPodLogsToRender config: %w" , err )
}
if int64 ( len ( pods ) ) > maxPodLogsToRender {
return status . Error ( codes . InvalidArgument , "max pods to view logs are reached. Please provide more granular query" )
2021-02-08 17:27:24 +00:00
}
2021-02-11 01:58:13 +00:00
var streams [ ] chan logEntry
2021-02-08 17:27:24 +00:00
for _ , pod := range pods {
2025-01-03 16:10:00 +00:00
stream , err := kubeClientset . CoreV1 ( ) . Pods ( pod . Namespace ) . GetLogs ( pod . Name , & corev1 . PodLogOptions {
2022-04-18 14:47:13 +00:00
Container : q . GetContainer ( ) ,
Follow : q . GetFollow ( ) ,
2021-02-11 01:58:13 +00:00
Timestamps : true ,
SinceSeconds : sinceSeconds ,
2022-04-18 14:47:13 +00:00
SinceTime : q . GetSinceTime ( ) ,
2021-02-11 01:58:13 +00:00
TailLines : tailLines ,
2022-04-18 14:47:13 +00:00
Previous : q . GetPrevious ( ) ,
2021-02-11 01:58:13 +00:00
} ) . Stream ( ws . Context ( ) )
podName := pod . Name
logStream := make ( chan logEntry )
2021-05-19 22:52:11 +00:00
if err == nil {
2025-05-21 16:25:32 +00:00
defer utilio . Close ( stream )
2021-05-19 22:52:11 +00:00
}
2021-02-11 01:58:13 +00:00
streams = append ( streams , logStream )
go func ( ) {
2021-05-19 22:52:11 +00:00
// if k8s failed to start steaming logs (typically because Pod is not ready yet)
// then the error should be shown in the UI so that user know the reason
if err != nil {
logStream <- logEntry { line : err . Error ( ) }
} else {
parseLogsStream ( podName , stream , logStream )
}
2021-02-11 01:58:13 +00:00
close ( logStream )
} ( )
2021-02-08 17:27:24 +00:00
}
2021-02-11 01:58:13 +00:00
logStream := mergeLogStreams ( streams , time . Millisecond * 100 )
sentCount := int64 ( 0 )
2021-03-01 21:06:21 +00:00
done := make ( chan error )
go func ( ) {
for entry := range logStream {
2021-02-11 01:58:13 +00:00
if entry . err != nil {
2021-03-01 21:06:21 +00:00
done <- entry . err
return
2025-01-07 15:25:22 +00:00
}
if q . Filter != nil {
2025-03-02 23:21:51 +00:00
var lineContainsFilter bool
if q . GetMatchCase ( ) {
lineContainsFilter = strings . Contains ( entry . line , literal )
} else {
lineContainsFilter = strings . Contains ( strings . ToLower ( entry . line ) , strings . ToLower ( literal ) )
}
2025-01-07 15:25:22 +00:00
if ( inverse && lineContainsFilter ) || ( ! inverse && ! lineContainsFilter ) {
continue
2021-02-11 01:58:13 +00:00
}
}
2025-01-07 15:25:22 +00:00
ts := metav1 . NewTime ( entry . timeStamp )
if untilTime != nil && entry . timeStamp . After ( untilTime . Time ) {
done <- ws . Send ( & application . LogEntry {
2026-02-24 16:42:12 +00:00
Last : new ( true ) ,
2025-01-07 15:25:22 +00:00
PodName : & entry . podName ,
Content : & entry . line ,
2026-02-24 16:42:12 +00:00
TimeStampStr : new ( entry . timeStamp . Format ( time . RFC3339Nano ) ) ,
2025-01-07 15:25:22 +00:00
TimeStamp : & ts ,
} )
return
}
sentCount ++
if err := ws . Send ( & application . LogEntry {
PodName : & entry . podName ,
Content : & entry . line ,
2026-02-24 16:42:12 +00:00
TimeStampStr : new ( entry . timeStamp . Format ( time . RFC3339Nano ) ) ,
2025-01-07 15:25:22 +00:00
TimeStamp : & ts ,
2026-02-24 16:42:12 +00:00
Last : new ( false ) ,
2025-01-07 15:25:22 +00:00
} ) ; err != nil {
done <- err
break
}
2021-02-08 17:27:24 +00:00
}
2022-04-18 14:47:13 +00:00
now := time . Now ( )
nowTS := metav1 . NewTime ( now )
done <- ws . Send ( & application . LogEntry {
2026-02-24 16:42:12 +00:00
Last : new ( true ) ,
PodName : new ( "" ) ,
Content : new ( "" ) ,
TimeStampStr : new ( now . Format ( time . RFC3339Nano ) ) ,
2022-04-18 14:47:13 +00:00
TimeStamp : & nowTS ,
} )
2021-03-01 21:06:21 +00:00
} ( )
select {
case err := <- done :
return err
case <- ws . Context ( ) . Done ( ) :
log . WithField ( "application" , q . Name ) . Debug ( "k8s pod logs reader completed due to closed grpc context" )
return nil
2021-02-08 17:27:24 +00:00
}
}
// from all of the treeNodes, get the pod who meets the criteria or whose parents meets the criteria
2025-01-06 16:34:32 +00:00
func getSelectedPods ( treeNodes [ ] v1alpha1 . ResourceNode , q * application . ApplicationPodLogsQuery ) [ ] v1alpha1 . ResourceNode {
var pods [ ] v1alpha1 . ResourceNode
2021-02-08 17:27:24 +00:00
isTheOneMap := make ( map [ string ] bool )
for _ , treeNode := range treeNodes {
2022-03-22 17:57:30 +00:00
if treeNode . Kind == kube . PodKind && treeNode . Group == "" && treeNode . UID != "" {
2021-02-08 17:27:24 +00:00
if isTheSelectedOne ( & treeNode , q , treeNodes , isTheOneMap ) {
pods = append ( pods , treeNode )
}
}
}
return pods
}
// check is currentNode is matching with group, kind, and name, or if any of its parents matches
2025-01-06 16:34:32 +00:00
func isTheSelectedOne ( currentNode * v1alpha1 . ResourceNode , q * application . ApplicationPodLogsQuery , resourceNodes [ ] v1alpha1 . ResourceNode , isTheOneMap map [ string ] bool ) bool {
2021-02-08 17:27:24 +00:00
exist , value := isTheOneMap [ currentNode . UID ]
if exist {
return value
}
2022-04-18 14:47:13 +00:00
if ( q . GetResourceName ( ) == "" || currentNode . Name == q . GetResourceName ( ) ) &&
( q . GetKind ( ) == "" || currentNode . Kind == q . GetKind ( ) ) &&
( q . GetGroup ( ) == "" || currentNode . Group == q . GetGroup ( ) ) &&
( q . GetNamespace ( ) == "" || currentNode . Namespace == q . GetNamespace ( ) ) {
2021-02-08 17:27:24 +00:00
isTheOneMap [ currentNode . UID ] = true
return true
}
if len ( currentNode . ParentRefs ) == 0 {
isTheOneMap [ currentNode . UID ] = false
return false
}
for _ , parentResource := range currentNode . ParentRefs {
2023-08-30 19:55:11 +00:00
// look up parentResource from resourceNodes
// then check if the parent isTheSelectedOne
2021-02-08 17:27:24 +00:00
for _ , resourceNode := range resourceNodes {
if resourceNode . Namespace == parentResource . Namespace &&
resourceNode . Name == parentResource . Name &&
resourceNode . Group == parentResource . Group &&
resourceNode . Kind == parentResource . Kind {
if isTheSelectedOne ( & resourceNode , q , resourceNodes , isTheOneMap ) {
isTheOneMap [ currentNode . UID ] = true
return true
}
}
}
}
isTheOneMap [ currentNode . UID ] = false
return false
}
2018-05-11 18:50:32 +00:00
// Sync syncs an application to its target state
2025-01-06 16:34:32 +00:00
func ( s * Server ) Sync ( ctx context . Context , syncReq * application . ApplicationSyncRequest ) ( * v1alpha1 . Application , error ) {
2025-02-20 10:40:15 +00:00
a , proj , err := s . getApplicationEnforceRBACClient ( ctx , rbac . ActionGet , syncReq . GetProject ( ) , syncReq . GetAppNamespace ( ) , syncReq . GetName ( ) , "" )
2018-06-12 17:43:16 +00:00
if err != nil {
2023-03-23 13:22:05 +00:00
return nil , err
2018-06-12 17:43:16 +00:00
}
2019-10-01 22:23:09 +00:00
2022-08-17 21:03:24 +00:00
s . inferResourcesStatusHealth ( a )
2024-10-12 17:32:46 +00:00
canSync , err := proj . Spec . SyncWindows . Matches ( a ) . CanSync ( true )
if err != nil {
return a , status . Errorf ( codes . PermissionDenied , "cannot sync: invalid sync window: %v" , err )
}
if ! canSync {
2022-05-20 21:16:55 +00:00
return a , status . Errorf ( codes . PermissionDenied , "cannot sync: blocked by sync window" )
2019-10-01 22:23:09 +00:00
}
2019-10-08 22:20:19 +00:00
2025-02-20 10:40:15 +00:00
if err := s . enf . EnforceErr ( ctx . Value ( "claims" ) , rbac . ResourceApplications , rbac . ActionSync , a . RBACName ( s . ns ) ) ; err != nil {
2019-02-28 21:11:47 +00:00
return nil , err
2018-06-06 04:44:14 +00:00
}
2022-08-17 15:48:27 +00:00
2019-06-18 02:09:43 +00:00
if syncReq . Manifests != nil {
2025-02-20 10:40:15 +00:00
if err := s . enf . EnforceErr ( ctx . Value ( "claims" ) , rbac . ResourceApplications , rbac . ActionOverride , a . RBACName ( s . ns ) ) ; err != nil {
2019-06-18 02:09:43 +00:00
return nil , err
}
2025-03-21 11:47:01 +00:00
if a . Spec . SyncPolicy != nil && a . Spec . SyncPolicy . IsAutomatedSyncEnabled ( ) && ! syncReq . GetDryRun ( ) {
2022-05-20 21:16:55 +00:00
return nil , status . Error ( codes . FailedPrecondition , "cannot use local sync when Automatic Sync Policy is enabled unless for dry run" )
2019-06-18 02:09:43 +00:00
}
}
2018-11-19 20:25:45 +00:00
if a . DeletionTimestamp != nil {
return nil , status . Errorf ( codes . FailedPrecondition , "application is deleting" )
}
2024-04-29 08:23:49 +00:00
revision , displayRevision , sourceRevisions , displayRevisions , err := s . resolveSourceRevisions ( ctx , a , syncReq )
2019-11-28 20:55:22 +00:00
if err != nil {
2024-04-29 08:23:49 +00:00
return nil , err
2018-11-27 21:38:00 +00:00
}
2020-07-28 17:14:17 +00:00
2025-01-06 16:34:32 +00:00
var retry * v1alpha1 . RetryStrategy
var syncOptions v1alpha1 . SyncOptions
2020-02-10 22:09:25 +00:00
if a . Spec . SyncPolicy != nil {
syncOptions = a . Spec . SyncPolicy . SyncOptions
2020-07-28 17:14:17 +00:00
retry = a . Spec . SyncPolicy . Retry
}
if syncReq . RetryStrategy != nil {
retry = syncReq . RetryStrategy
2020-02-10 22:09:25 +00:00
}
2021-02-12 00:12:05 +00:00
if syncReq . SyncOptions != nil {
syncOptions = syncReq . SyncOptions . Items
}
2020-06-22 16:21:53 +00:00
2025-02-28 14:43:53 +00:00
if syncOptions . HasOption ( common . SyncOptionReplace ) && ! s . syncWithReplaceAllowed {
return nil , status . Error ( codes . FailedPrecondition , "sync with replace was disabled on the API Server level via the server configuration" )
}
2020-06-22 16:21:53 +00:00
// We cannot use local manifests if we're only allowed to sync to signed commits
if syncReq . Manifests != nil && len ( proj . Spec . SignatureKeys ) > 0 {
return nil , status . Errorf ( codes . FailedPrecondition , "Cannot use local sync when signature keys are required." )
}
2025-01-06 16:34:32 +00:00
resources := [ ] v1alpha1 . SyncOperationResource { }
2022-04-18 14:47:13 +00:00
if syncReq . GetResources ( ) != nil {
for _ , r := range syncReq . GetResources ( ) {
if r != nil {
resources = append ( resources , * r )
}
}
}
2025-09-11 15:19:00 +00:00
var source * v1alpha1 . ApplicationSource
if ! a . Spec . HasMultipleSources ( ) {
2026-02-24 16:42:12 +00:00
source = new ( a . Spec . GetSource ( ) )
2025-09-11 15:19:00 +00:00
}
2025-01-06 16:34:32 +00:00
op := v1alpha1 . Operation {
Sync : & v1alpha1 . SyncOperation {
2025-09-11 15:19:00 +00:00
Source : source ,
2019-09-26 19:05:12 +00:00
Revision : revision ,
2022-04-18 14:47:13 +00:00
Prune : syncReq . GetPrune ( ) ,
DryRun : syncReq . GetDryRun ( ) ,
2020-02-10 22:09:25 +00:00
SyncOptions : syncOptions ,
2019-03-04 08:56:36 +00:00
SyncStrategy : syncReq . Strategy ,
2022-04-18 14:47:13 +00:00
Resources : resources ,
2019-06-18 02:09:43 +00:00
Manifests : syncReq . Manifests ,
2024-04-29 08:23:49 +00:00
Sources : a . Spec . Sources ,
Revisions : sourceRevisions ,
2018-09-11 21:28:53 +00:00
} ,
2025-01-06 16:34:32 +00:00
InitiatedBy : v1alpha1 . OperationInitiator { Username : session . Username ( ctx ) } ,
2020-06-10 22:28:07 +00:00
Info : syncReq . Infos ,
2018-09-11 21:28:53 +00:00
}
2020-07-28 17:14:17 +00:00
if retry != nil {
op . Retry = * retry
}
2023-03-23 13:22:05 +00:00
appName := syncReq . GetName ( )
appNs := s . appNamespaceOrDefault ( syncReq . GetAppNamespace ( ) )
appIf := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( appNs )
2022-08-10 09:39:10 +00:00
a , err = argo . SetAppOperation ( appIf , appName , & op )
2022-05-20 21:16:55 +00:00
if err != nil {
return nil , fmt . Errorf ( "error setting app operation: %w" , err )
}
partial := ""
if len ( syncReq . Resources ) > 0 {
partial = "partial "
2018-09-24 15:52:43 +00:00
}
2024-04-29 08:23:49 +00:00
var reason string
if a . Spec . HasMultipleSources ( ) {
reason = fmt . Sprintf ( "initiated %ssync to %s" , partial , strings . Join ( displayRevisions , "," ) )
} else {
reason = fmt . Sprintf ( "initiated %ssync to %s" , partial , displayRevision )
}
2022-05-20 21:16:55 +00:00
if syncReq . Manifests != nil {
reason = fmt . Sprintf ( "initiated %ssync locally" , partial )
}
2025-01-07 15:08:51 +00:00
s . logAppEvent ( ctx , a , argo . EventReasonOperationStarted , reason )
2022-05-20 21:16:55 +00:00
return a , nil
2018-05-11 18:50:32 +00:00
}
2018-03-07 22:34:43 +00:00
2025-01-06 16:34:32 +00:00
func ( s * Server ) resolveSourceRevisions ( ctx context . Context , a * v1alpha1 . Application , syncReq * application . ApplicationSyncRequest ) ( string , string , [ ] string , [ ] string , error ) {
2025-10-07 15:01:25 +00:00
requireOverridePrivilegeForRevisionSync , err := s . settingsMgr . RequireOverridePrivilegeForRevisionSync ( )
if err != nil {
// give up, and return the error
return "" , "" , nil , nil ,
fmt . Errorf ( "error getting setting 'RequireOverridePrivilegeForRevisionSync' from configmap: : %w" , err )
}
2024-04-29 08:23:49 +00:00
if a . Spec . HasMultipleSources ( ) {
numOfSources := int64 ( len ( a . Spec . GetSources ( ) ) )
sourceRevisions := make ( [ ] string , numOfSources )
displayRevisions := make ( [ ] string , numOfSources )
2025-10-07 15:01:25 +00:00
desiredRevisions := make ( [ ] string , numOfSources )
2024-04-29 08:23:49 +00:00
for i , pos := range syncReq . SourcePositions {
if pos <= 0 || pos > numOfSources {
2024-12-30 08:56:41 +00:00
return "" , "" , nil , nil , errors . New ( "source position is out of range" )
2024-04-29 08:23:49 +00:00
}
2025-10-07 15:01:25 +00:00
desiredRevisions [ pos - 1 ] = syncReq . Revisions [ i ]
}
for index , desiredRevision := range desiredRevisions {
if desiredRevision != "" && desiredRevision != text . FirstNonEmpty ( a . Spec . GetSources ( ) [ index ] . TargetRevision , "HEAD" ) {
// User is trying to sync to a different revision than the ones specified in the app sources
// Enforce that they have the 'override' privilege if the setting is enabled
if requireOverridePrivilegeForRevisionSync {
if err := s . enf . EnforceErr ( ctx . Value ( "claims" ) , rbac . ResourceApplications , rbac . ActionOverride , a . RBACName ( s . ns ) ) ; err != nil {
return "" , "" , nil , nil , err
}
}
if a . Spec . SyncPolicy != nil && a . Spec . SyncPolicy . IsAutomatedSyncEnabled ( ) && ! syncReq . GetDryRun ( ) {
return "" , "" , nil , nil , status . Errorf ( codes . FailedPrecondition ,
"Cannot sync source %s to %s: auto-sync currently set to %s" ,
a . Spec . GetSources ( ) [ index ] . RepoURL , desiredRevision , a . Spec . Sources [ index ] . TargetRevision )
2024-04-29 08:23:49 +00:00
}
}
revision , displayRevision , err := s . resolveRevision ( ctx , a , syncReq , index )
if err != nil {
2024-09-04 14:58:15 +00:00
return "" , "" , nil , nil , status . Error ( codes . FailedPrecondition , err . Error ( ) )
2024-04-29 08:23:49 +00:00
}
sourceRevisions [ index ] = revision
displayRevisions [ index ] = displayRevision
}
return "" , "" , sourceRevisions , displayRevisions , nil
2025-01-07 15:25:22 +00:00
}
source := a . Spec . GetSource ( )
2025-10-07 15:01:25 +00:00
if syncReq . GetRevision ( ) != "" &&
syncReq . GetRevision ( ) != text . FirstNonEmpty ( source . TargetRevision , "HEAD" ) {
// User is trying to sync to a different revision than the one specified in the app spec
// Enforce that they have the 'override' privilege if the setting is enabled
if requireOverridePrivilegeForRevisionSync {
if err := s . enf . EnforceErr ( ctx . Value ( "claims" ) , rbac . ResourceApplications , rbac . ActionOverride , a . RBACName ( s . ns ) ) ; err != nil {
return "" , "" , nil , nil , err
}
}
if a . Spec . SyncPolicy != nil &&
a . Spec . SyncPolicy . IsAutomatedSyncEnabled ( ) && ! syncReq . GetDryRun ( ) {
// If the app has auto-sync enabled, we cannot allow syncing to a different revision
2025-01-07 15:25:22 +00:00
return "" , "" , nil , nil , status . Errorf ( codes . FailedPrecondition , "Cannot sync to %s: auto-sync currently set to %s" , syncReq . GetRevision ( ) , source . TargetRevision )
2024-04-29 08:23:49 +00:00
}
}
2025-01-07 15:25:22 +00:00
revision , displayRevision , err := s . resolveRevision ( ctx , a , syncReq , - 1 )
if err != nil {
return "" , "" , nil , nil , status . Error ( codes . FailedPrecondition , err . Error ( ) )
}
return revision , displayRevision , nil , nil , nil
2024-04-29 08:23:49 +00:00
}
2025-01-06 16:34:32 +00:00
func ( s * Server ) Rollback ( ctx context . Context , rollbackReq * application . ApplicationRollbackRequest ) ( * v1alpha1 . Application , error ) {
2025-02-20 10:40:15 +00:00
a , _ , err := s . getApplicationEnforceRBACClient ( ctx , rbac . ActionSync , rollbackReq . GetProject ( ) , rollbackReq . GetAppNamespace ( ) , rollbackReq . GetName ( ) , "" )
2018-06-12 17:43:16 +00:00
if err != nil {
2019-02-28 21:11:47 +00:00
return nil , err
2018-06-06 04:44:14 +00:00
}
2022-08-17 15:48:27 +00:00
2022-08-17 21:03:24 +00:00
s . inferResourcesStatusHealth ( a )
2018-11-19 20:25:45 +00:00
if a . DeletionTimestamp != nil {
return nil , status . Errorf ( codes . FailedPrecondition , "application is deleting" )
}
2025-03-21 11:47:01 +00:00
if a . Spec . SyncPolicy != nil && a . Spec . SyncPolicy . IsAutomatedSyncEnabled ( ) {
2018-11-19 20:25:45 +00:00
return nil , status . Errorf ( codes . FailedPrecondition , "rollback cannot be initiated when auto-sync is enabled" )
2018-09-11 21:28:53 +00:00
}
2018-10-19 03:23:22 +00:00
2025-01-06 16:34:32 +00:00
var deploymentInfo * v1alpha1 . RevisionHistory
2018-10-19 03:23:22 +00:00
for _ , info := range a . Status . History {
2022-04-18 14:47:13 +00:00
if info . ID == rollbackReq . GetId ( ) {
2018-10-19 03:23:22 +00:00
deploymentInfo = & info
break
}
}
if deploymentInfo == nil {
2022-08-10 09:39:10 +00:00
return nil , status . Errorf ( codes . InvalidArgument , "application %s does not have deployment with id %v" , a . QualifiedName ( ) , rollbackReq . GetId ( ) )
2018-10-19 03:23:22 +00:00
}
2024-06-10 21:54:07 +00:00
if deploymentInfo . Source . IsZero ( ) && deploymentInfo . Sources . IsZero ( ) {
2019-03-04 08:56:36 +00:00
// Since source type was introduced to history starting with v0.12, and is now required for
// rollback, we cannot support rollback to revisions deployed using Argo CD v0.11 or below
2024-06-10 21:54:07 +00:00
// As multi source doesn't use app.Source, we need to check to the Sources length
2019-03-04 08:56:36 +00:00
return nil , status . Errorf ( codes . FailedPrecondition , "cannot rollback to revision deployed with Argo CD v0.11 or lower. sync to revision instead." )
2019-01-04 18:16:41 +00:00
}
2019-03-04 08:56:36 +00:00
2025-01-06 16:34:32 +00:00
var syncOptions v1alpha1 . SyncOptions
2020-02-10 22:09:25 +00:00
if a . Spec . SyncPolicy != nil {
syncOptions = a . Spec . SyncPolicy . SyncOptions
}
2018-10-19 03:23:22 +00:00
// Rollback is just a convenience around Sync
2025-01-06 16:34:32 +00:00
op := v1alpha1 . Operation {
Sync : & v1alpha1 . SyncOperation {
2019-03-04 08:56:36 +00:00
Revision : deploymentInfo . Revision ,
2024-06-10 21:54:07 +00:00
Revisions : deploymentInfo . Revisions ,
2022-04-18 14:47:13 +00:00
DryRun : rollbackReq . GetDryRun ( ) ,
Prune : rollbackReq . GetPrune ( ) ,
2020-02-10 22:09:25 +00:00
SyncOptions : syncOptions ,
2025-01-06 16:34:32 +00:00
SyncStrategy : & v1alpha1 . SyncStrategy { Apply : & v1alpha1 . SyncStrategyApply { } } ,
2019-03-04 08:56:36 +00:00
Source : & deploymentInfo . Source ,
2024-06-10 21:54:07 +00:00
Sources : deploymentInfo . Sources ,
2018-09-11 21:28:53 +00:00
} ,
2025-01-06 16:34:32 +00:00
InitiatedBy : v1alpha1 . OperationInitiator { Username : session . Username ( ctx ) } ,
2018-02-28 11:07:56 +00:00
}
2023-03-23 13:22:05 +00:00
appName := rollbackReq . GetName ( )
appNs := s . appNamespaceOrDefault ( rollbackReq . GetAppNamespace ( ) )
appIf := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( appNs )
2022-08-10 09:39:10 +00:00
a , err = argo . SetAppOperation ( appIf , appName , & op )
2022-05-20 21:16:55 +00:00
if err != nil {
return nil , fmt . Errorf ( "error setting app operation: %w" , err )
2018-09-24 15:52:43 +00:00
}
2025-01-07 15:08:51 +00:00
s . logAppEvent ( ctx , a , argo . EventReasonOperationStarted , fmt . Sprintf ( "initiated rollback to %d" , rollbackReq . GetId ( ) ) )
2022-05-20 21:16:55 +00:00
return a , nil
2018-02-28 11:07:56 +00:00
}
2018-07-14 00:13:31 +00:00
2022-12-07 19:39:45 +00:00
func ( s * Server ) ListLinks ( ctx context . Context , req * application . ListAppLinksRequest ) ( * application . LinksResponse , error ) {
2025-02-20 10:40:15 +00:00
a , proj , err := s . getApplicationEnforceRBACClient ( ctx , rbac . ActionGet , req . GetProject ( ) , req . GetNamespace ( ) , req . GetName ( ) , "" )
2022-12-07 19:39:45 +00:00
if err != nil {
2023-03-23 13:22:05 +00:00
return nil , err
2022-12-07 19:39:45 +00:00
}
obj , err := kube . ToUnstructured ( a )
if err != nil {
return nil , fmt . Errorf ( "error getting application: %w" , err )
}
deepLinks , err := s . settingsMgr . GetDeepLinks ( settings . ApplicationDeepLinks )
if err != nil {
return nil , fmt . Errorf ( "failed to read application deep links from configmap: %w" , err )
}
2024-04-15 07:20:07 +00:00
clstObj , _ , err := s . getObjectsForDeepLinks ( ctx , a , proj )
2023-03-22 20:37:52 +00:00
if err != nil {
return nil , err
}
2025-10-15 01:38:14 +00:00
// Create deep links object with managed-by URL
2023-03-22 20:37:52 +00:00
deepLinksObject := deeplinks . CreateDeepLinksObject ( nil , obj , clstObj , nil )
2025-10-15 01:38:14 +00:00
// If no managed-by URL is set, use the current instance's URL
if deepLinksObject [ deeplinks . ManagedByURLKey ] == nil {
settings , err := s . settingsMgr . GetSettings ( )
if err != nil {
log . Warnf ( "Failed to get settings: %v" , err )
} else if settings . URL != "" {
deepLinksObject [ deeplinks . ManagedByURLKey ] = settings . URL
}
}
2023-03-22 20:37:52 +00:00
finalList , errorList := deeplinks . EvaluateDeepLinksResponse ( deepLinksObject , obj . GetName ( ) , deepLinks )
2022-12-07 19:39:45 +00:00
if len ( errorList ) > 0 {
log . Errorf ( "errorList while evaluating application deep links, %v" , strings . Join ( errorList , ", " ) )
}
return finalList , nil
}
2025-01-06 16:34:32 +00:00
func ( s * Server ) getObjectsForDeepLinks ( ctx context . Context , app * v1alpha1 . Application , proj * v1alpha1 . AppProject ) ( cluster * unstructured . Unstructured , project * unstructured . Unstructured , err error ) {
2023-03-22 20:37:52 +00:00
// sanitize project jwt tokens
2025-01-06 16:34:32 +00:00
proj . Status = v1alpha1 . AppProjectStatus { }
2023-03-22 20:37:52 +00:00
project , err = kube . ToUnstructured ( proj )
if err != nil {
return nil , nil , err
}
2025-01-06 16:34:32 +00:00
getProjectClusters := func ( project string ) ( [ ] * v1alpha1 . Cluster , error ) {
2023-03-22 20:37:52 +00:00
return s . db . GetProjectClusters ( ctx , project )
}
2025-01-13 18:15:42 +00:00
destCluster , err := argo . GetDestinationCluster ( ctx , app . Spec . Destination , s . db )
if err != nil {
2025-05-20 19:48:09 +00:00
log . WithFields ( applog . GetAppLogFields ( app ) ) .
WithFields ( map [ string ] any {
"destination" : app . Spec . Destination ,
} ) . Warnf ( "cannot validate cluster, error=%v" , err . Error ( ) )
2023-03-22 20:37:52 +00:00
return nil , nil , nil
}
2023-05-12 14:55:48 +00:00
2025-01-13 18:15:42 +00:00
permitted , err := proj . IsDestinationPermitted ( destCluster , app . Spec . Destination . Namespace , getProjectClusters )
2023-05-12 14:55:48 +00:00
if err != nil {
return nil , nil , err
}
if ! permitted {
2024-12-30 08:56:41 +00:00
return nil , nil , errors . New ( "error getting destination cluster" )
2023-05-12 14:55:48 +00:00
}
2023-03-22 20:37:52 +00:00
// sanitize cluster, remove cluster config creds and other unwanted fields
2025-01-13 18:15:42 +00:00
cluster , err = deeplinks . SanitizeCluster ( destCluster )
2023-03-22 20:37:52 +00:00
return cluster , project , err
}
2022-12-07 19:39:45 +00:00
func ( s * Server ) ListResourceLinks ( ctx context . Context , req * application . ApplicationResourceRequest ) ( * application . LinksResponse , error ) {
2025-02-20 10:40:15 +00:00
obj , _ , app , _ , err := s . getUnstructuredLiveResourceOrApp ( ctx , rbac . ActionGet , req )
2022-12-07 19:39:45 +00:00
if err != nil {
return nil , err
}
deepLinks , err := s . settingsMgr . GetDeepLinks ( settings . ResourceDeepLinks )
if err != nil {
return nil , fmt . Errorf ( "failed to read application deep links from configmap: %w" , err )
}
2024-10-30 16:52:37 +00:00
obj , err = s . replaceSecretValues ( obj )
2023-01-10 17:36:19 +00:00
if err != nil {
return nil , fmt . Errorf ( "error replacing secret values: %w" , err )
}
2023-03-22 20:37:52 +00:00
appObj , err := kube . ToUnstructured ( app )
if err != nil {
return nil , err
}
2025-05-20 19:48:09 +00:00
proj , err := s . getAppProject ( ctx , app , log . WithFields ( applog . GetAppLogFields ( app ) ) )
2024-04-15 07:20:07 +00:00
if err != nil {
return nil , err
}
clstObj , projObj , err := s . getObjectsForDeepLinks ( ctx , app , proj )
2023-03-22 20:37:52 +00:00
if err != nil {
return nil , err
}
deepLinksObject := deeplinks . CreateDeepLinksObject ( obj , appObj , clstObj , projObj )
finalList , errorList := deeplinks . EvaluateDeepLinksResponse ( deepLinksObject , obj . GetName ( ) , deepLinks )
2022-12-07 19:39:45 +00:00
if len ( errorList ) > 0 {
log . Errorf ( "errors while evaluating resource deep links, %v" , strings . Join ( errorList , ", " ) )
}
return finalList , nil
}
2025-01-06 16:34:32 +00:00
func getAmbiguousRevision ( app * v1alpha1 . Application , syncReq * application . ApplicationSyncRequest , sourceIndex int ) string {
2024-04-29 08:23:49 +00:00
ambiguousRevision := ""
if app . Spec . HasMultipleSources ( ) {
for i , pos := range syncReq . SourcePositions {
2024-12-05 16:35:05 +00:00
if pos == int64 ( sourceIndex + 1 ) {
2024-04-29 08:23:49 +00:00
ambiguousRevision = syncReq . Revisions [ i ]
}
}
if ambiguousRevision == "" {
ambiguousRevision = app . Spec . Sources [ sourceIndex ] . TargetRevision
}
} else {
ambiguousRevision = syncReq . GetRevision ( )
if ambiguousRevision == "" {
ambiguousRevision = app . Spec . GetSource ( ) . TargetRevision
}
}
return ambiguousRevision
}
2019-09-06 22:37:25 +00:00
// resolveRevision resolves the revision specified either in the sync request, or the
// application source, into a concrete revision that will be used for a sync operation.
2025-01-06 16:34:32 +00:00
func ( s * Server ) resolveRevision ( ctx context . Context , app * v1alpha1 . Application , syncReq * application . ApplicationSyncRequest , sourceIndex int ) ( string , string , error ) {
2021-04-15 19:51:03 +00:00
if syncReq . Manifests != nil {
return "" , "" , nil
}
2024-04-29 08:23:49 +00:00
ambiguousRevision := getAmbiguousRevision ( app , syncReq , sourceIndex )
2025-02-18 16:02:38 +00:00
repoURL := app . Spec . GetSource ( ) . RepoURL
2024-05-13 20:42:14 +00:00
if app . Spec . HasMultipleSources ( ) {
2025-02-18 16:02:38 +00:00
repoURL = app . Spec . Sources [ sourceIndex ] . RepoURL
2024-05-13 20:42:14 +00:00
}
2025-02-18 16:02:38 +00:00
repo , err := s . db . GetRepository ( ctx , repoURL , app . Spec . Project )
2022-01-12 21:06:00 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return "" , "" , fmt . Errorf ( "error getting repository by URL: %w" , err )
2022-01-12 21:06:00 +00:00
}
conn , repoClient , err := s . repoClientset . NewRepoServerClient ( )
if err != nil {
2022-05-20 21:16:55 +00:00
return "" , "" , fmt . Errorf ( "error getting repo server client: %w" , err )
2022-01-12 21:06:00 +00:00
}
2025-05-21 16:25:32 +00:00
defer utilio . Close ( conn )
2022-01-12 21:06:00 +00:00
2024-04-29 08:23:49 +00:00
source := app . Spec . GetSourcePtrByIndex ( sourceIndex )
2022-12-16 20:47:08 +00:00
if ! source . IsHelm ( ) {
2019-11-28 20:55:22 +00:00
if git . IsCommitSHA ( ambiguousRevision ) {
// If it's already a commit SHA, then no need to look it up
return ambiguousRevision , ambiguousRevision , nil
}
2018-11-27 21:38:00 +00:00
}
2022-01-12 21:06:00 +00:00
resolveRevisionResponse , err := repoClient . ResolveRevision ( ctx , & apiclient . ResolveRevisionRequest {
Repo : repo ,
App : app ,
AmbiguousRevision : ambiguousRevision ,
2024-04-29 08:23:49 +00:00
SourceIndex : int64 ( sourceIndex ) ,
2022-01-12 21:06:00 +00:00
} )
if err != nil {
2022-05-20 21:16:55 +00:00
return "" , "" , fmt . Errorf ( "error resolving repo revision: %w" , err )
2022-01-12 21:06:00 +00:00
}
return resolveRevisionResponse . Revision , resolveRevisionResponse . AmbiguousRevision , nil
2018-11-27 21:38:00 +00:00
}
2019-06-05 19:58:11 +00:00
func ( s * Server ) TerminateOperation ( ctx context . Context , termOpReq * application . OperationTerminateRequest ) ( * application . OperationTerminateResponse , error ) {
2022-08-10 09:39:10 +00:00
appName := termOpReq . GetName ( )
appNs := s . appNamespaceOrDefault ( termOpReq . GetAppNamespace ( ) )
2025-02-20 10:40:15 +00:00
a , _ , err := s . getApplicationEnforceRBACClient ( ctx , rbac . ActionSync , termOpReq . GetProject ( ) , appNs , appName , "" )
2018-07-14 00:13:31 +00:00
if err != nil {
2019-02-28 21:11:47 +00:00
return nil , err
2018-07-14 00:13:31 +00:00
}
2026-02-09 12:37:34 +00:00
for range 10 {
2018-07-14 00:13:31 +00:00
if a . Operation == nil || a . Status . OperationState == nil {
return nil , status . Errorf ( codes . InvalidArgument , "Unable to terminate operation. No operation is in progress" )
}
2020-05-15 17:01:18 +00:00
a . Status . OperationState . Phase = common . OperationTerminating
2022-08-10 09:39:10 +00:00
updated , err := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( appNs ) . Update ( ctx , a , metav1 . UpdateOptions { } )
2018-07-14 00:13:31 +00:00
if err == nil {
2020-03-16 18:51:59 +00:00
s . waitSync ( updated )
2025-01-07 15:08:51 +00:00
s . logAppEvent ( ctx , a , argo . EventReasonResourceUpdated , "terminated running operation" )
2019-06-05 19:58:11 +00:00
return & application . OperationTerminateResponse { } , nil
2018-07-14 00:13:31 +00:00
}
2025-01-03 17:09:37 +00:00
if ! apierrors . IsConflict ( err ) {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error updating application: %w" , err )
2018-07-14 00:13:31 +00:00
}
2022-05-20 21:16:55 +00:00
log . Warnf ( "failed to set operation for app %q due to update conflict. retrying again..." , * termOpReq . Name )
2018-07-14 00:13:31 +00:00
time . Sleep ( 100 * time . Millisecond )
2025-12-17 21:53:34 +00:00
a , err = s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( appNs ) . Get ( ctx , appName , metav1 . GetOptions { } )
2018-07-14 00:13:31 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error getting application by name: %w" , err )
2018-07-14 00:13:31 +00:00
}
}
return nil , status . Errorf ( codes . Internal , "Failed to terminate app. Too many conflicts" )
}
2018-07-24 15:48:13 +00:00
2025-01-07 15:08:51 +00:00
func ( s * Server ) logAppEvent ( ctx context . Context , a * v1alpha1 . Application , reason string , action string ) {
2025-01-03 16:10:00 +00:00
eventInfo := argo . EventInfo { Type : corev1 . EventTypeNormal , Reason : reason }
2018-09-24 15:52:43 +00:00
user := session . Username ( ctx )
if user == "" {
user = "Unknown user"
}
message := fmt . Sprintf ( "%s %s" , user , action )
2025-01-07 15:08:51 +00:00
eventLabels := argo . GetAppEventLabels ( ctx , a , applisters . NewAppProjectLister ( s . projInformer . GetIndexer ( ) ) , s . ns , s . settingsMgr , s . db )
2024-06-17 17:27:54 +00:00
s . auditLogger . LogAppEvent ( a , eventInfo , message , user , eventLabels )
2018-07-24 15:48:13 +00:00
}
2019-04-16 21:50:44 +00:00
2025-01-07 15:08:51 +00:00
func ( s * Server ) logResourceEvent ( ctx context . Context , res * v1alpha1 . ResourceNode , reason string , action string ) {
2025-01-03 16:10:00 +00:00
eventInfo := argo . EventInfo { Type : corev1 . EventTypeNormal , Reason : reason }
2019-10-24 00:19:59 +00:00
user := session . Username ( ctx )
if user == "" {
user = "Unknown user"
}
message := fmt . Sprintf ( "%s %s" , user , action )
2023-03-16 20:20:10 +00:00
s . auditLogger . LogResourceEvent ( res , eventInfo , message , user )
2019-10-24 00:19:59 +00:00
}
2019-06-05 19:58:11 +00:00
func ( s * Server ) ListResourceActions ( ctx context . Context , q * application . ApplicationResourceRequest ) ( * application . ResourceActionsListResponse , error ) {
2025-02-20 10:40:15 +00:00
obj , _ , _ , _ , err := s . getUnstructuredLiveResourceOrApp ( ctx , rbac . ActionGet , q )
2019-04-16 21:50:44 +00:00
if err != nil {
2022-08-19 20:43:35 +00:00
return nil , err
2019-04-16 21:50:44 +00:00
}
resourceOverrides , err := s . settingsMgr . GetResourceOverrides ( )
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error getting resource overrides: %w" , err )
2019-04-16 21:50:44 +00:00
}
2019-10-09 17:25:52 +00:00
availableActions , err := s . getAvailableActions ( resourceOverrides , obj )
2019-04-16 21:50:44 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error getting available actions: %w" , err )
2019-04-16 21:50:44 +00:00
}
2025-01-06 16:34:32 +00:00
actionsPtr := [ ] * v1alpha1 . ResourceAction { }
2022-05-13 14:24:34 +00:00
for i := range availableActions {
actionsPtr = append ( actionsPtr , & availableActions [ i ] )
2022-04-18 14:47:13 +00:00
}
2019-10-04 00:11:42 +00:00
2022-04-18 14:47:13 +00:00
return & application . ResourceActionsListResponse { Actions : actionsPtr } , nil
2019-04-16 21:50:44 +00:00
}
2025-01-06 16:34:32 +00:00
func ( s * Server ) getUnstructuredLiveResourceOrApp ( ctx context . Context , rbacRequest string , q * application . ApplicationResourceRequest ) ( obj * unstructured . Unstructured , res * v1alpha1 . ResourceNode , app * v1alpha1 . Application , config * rest . Config , err error ) {
2023-03-24 14:43:43 +00:00
if q . GetKind ( ) == applicationType . ApplicationKind && q . GetGroup ( ) == applicationType . Group && q . GetName ( ) == q . GetResourceName ( ) {
2026-04-02 09:48:29 +00:00
var p * v1alpha1 . AppProject
app , p , err = s . getApplicationEnforceRBACInformer ( ctx , rbacRequest , q . GetProject ( ) , q . GetAppNamespace ( ) , q . GetName ( ) )
2022-08-19 20:43:35 +00:00
if err != nil {
2023-03-23 13:22:05 +00:00
return nil , nil , nil , nil , err
2022-08-19 20:43:35 +00:00
}
2025-06-17 15:46:50 +00:00
err = s . enf . EnforceErr ( ctx . Value ( "claims" ) , rbac . ResourceApplications , rbacRequest , app . RBACName ( s . ns ) )
if err != nil {
2022-08-19 20:43:35 +00:00
return nil , nil , nil , nil , err
}
2026-04-02 09:48:29 +00:00
config , err = s . getApplicationClusterConfig ( ctx , app , p )
2022-08-19 20:43:35 +00:00
if err != nil {
return nil , nil , nil , nil , fmt . Errorf ( "error getting application cluster config: %w" , err )
}
obj , err = kube . ToUnstructured ( app )
} else {
res , config , app , err = s . getAppLiveResource ( ctx , rbacRequest , q )
if err != nil {
2023-03-23 13:22:05 +00:00
return nil , nil , nil , nil , err
2022-08-19 20:43:35 +00:00
}
obj , err = s . kubectl . GetResource ( ctx , config , res . GroupKindVersion ( ) , res . Name , res . Namespace )
}
if err != nil {
return nil , nil , nil , nil , fmt . Errorf ( "error getting resource: %w" , err )
}
2025-09-22 04:05:06 +00:00
return obj , res , app , config , err
2022-08-19 20:43:35 +00:00
}
2025-01-06 16:34:32 +00:00
func ( s * Server ) getAvailableActions ( resourceOverrides map [ string ] v1alpha1 . ResourceOverride , obj * unstructured . Unstructured ) ( [ ] v1alpha1 . ResourceAction , error ) {
2019-04-16 21:50:44 +00:00
luaVM := lua . VM {
ResourceOverrides : resourceOverrides ,
}
2019-10-04 00:11:42 +00:00
2024-09-10 18:57:29 +00:00
discoveryScripts , err := luaVM . GetResourceActionDiscovery ( obj )
2019-04-16 21:50:44 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error getting Lua discovery script: %w" , err )
2019-04-16 21:50:44 +00:00
}
2024-09-10 18:57:29 +00:00
if len ( discoveryScripts ) == 0 {
2025-01-06 16:34:32 +00:00
return [ ] v1alpha1 . ResourceAction { } , nil
2019-04-19 17:27:12 +00:00
}
2024-09-10 18:57:29 +00:00
availableActions , err := luaVM . ExecuteResourceActionDiscovery ( obj , discoveryScripts )
2019-04-16 21:50:44 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error executing Lua discovery script: %w" , err )
2019-04-16 21:50:44 +00:00
}
return availableActions , nil
}
2025-07-09 14:41:28 +00:00
// RunResourceAction runs a resource action on a live resource
//
// Deprecated: use RunResourceActionV2 instead. This version does not support resource action parameters but is
// maintained for backward compatibility. It will be removed in a future release.
2019-06-05 19:58:11 +00:00
func ( s * Server ) RunResourceAction ( ctx context . Context , q * application . ResourceActionRunRequest ) ( * application . ApplicationResponse , error ) {
2025-07-09 14:41:28 +00:00
log . WithFields ( log . Fields {
"action" : q . Action ,
"application" : q . Name ,
"app-namespace" : q . AppNamespace ,
"project" : q . Project ,
"user" : session . Username ( ctx ) ,
} ) . Warn ( "RunResourceAction was called. RunResourceAction is deprecated and will be removed in a future release. Use RunResourceActionV2 instead." )
qV2 := & application . ResourceActionRunRequestV2 {
Name : q . Name ,
AppNamespace : q . AppNamespace ,
Namespace : q . Namespace ,
ResourceName : q . ResourceName ,
Kind : q . Kind ,
Version : q . Version ,
Group : q . Group ,
2025-09-09 20:58:22 +00:00
Action : q . Action ,
2025-07-09 14:41:28 +00:00
Project : q . Project ,
}
return s . RunResourceActionV2 ( ctx , qV2 )
}
func ( s * Server ) RunResourceActionV2 ( ctx context . Context , q * application . ResourceActionRunRequestV2 ) ( * application . ApplicationResponse , error ) {
2019-06-05 19:58:11 +00:00
resourceRequest := & application . ApplicationResourceRequest {
2019-04-16 21:50:44 +00:00
Name : q . Name ,
2022-08-10 09:39:10 +00:00
AppNamespace : q . AppNamespace ,
2019-04-16 21:50:44 +00:00
Namespace : q . Namespace ,
2019-04-19 17:27:12 +00:00
ResourceName : q . ResourceName ,
2019-04-16 21:50:44 +00:00
Kind : q . Kind ,
Version : q . Version ,
Group : q . Group ,
2023-07-19 13:22:28 +00:00
Project : q . Project ,
2019-04-16 21:50:44 +00:00
}
2025-02-20 10:40:15 +00:00
actionRequest := fmt . Sprintf ( "%s/%s/%s/%s" , rbac . ActionAction , q . GetGroup ( ) , q . GetKind ( ) , q . GetAction ( ) )
2022-08-19 20:43:35 +00:00
liveObj , res , a , config , err := s . getUnstructuredLiveResourceOrApp ( ctx , actionRequest , resourceRequest )
2019-04-16 21:50:44 +00:00
if err != nil {
2022-08-19 20:43:35 +00:00
return nil , err
2019-04-16 21:50:44 +00:00
}
2023-06-23 18:45:53 +00:00
liveObjBytes , err := json . Marshal ( liveObj )
if err != nil {
return nil , fmt . Errorf ( "error marshaling live object: %w" , err )
}
2019-04-16 21:50:44 +00:00
resourceOverrides , err := s . settingsMgr . GetResourceOverrides ( )
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error getting resource overrides: %w" , err )
2019-04-16 21:50:44 +00:00
}
luaVM := lua . VM {
ResourceOverrides : resourceOverrides ,
}
2022-04-18 14:47:13 +00:00
action , err := luaVM . GetResourceAction ( liveObj , q . GetAction ( ) )
2019-04-16 21:50:44 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error getting Lua resource action: %w" , err )
2019-04-16 21:50:44 +00:00
}
2025-05-08 08:19:04 +00:00
newObjects , err := luaVM . ExecuteResourceAction ( liveObj , action . ActionLua , q . GetResourceActionParameters ( ) )
2019-04-16 21:50:44 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error executing Lua resource action: %w" , err )
2019-04-16 21:50:44 +00:00
}
2025-01-06 16:34:32 +00:00
var app * v1alpha1 . Application
2023-06-23 18:45:53 +00:00
// Only bother getting the app if we know we're going to need it for a resource permission check.
if len ( newObjects ) > 0 {
// No need for an RBAC check, we checked above that the user is allowed to run this action.
app , err = s . appLister . Applications ( s . appNamespaceOrDefault ( q . GetAppNamespace ( ) ) ) . Get ( q . GetName ( ) )
if err != nil {
return nil , err
}
2019-04-16 21:50:44 +00:00
}
2025-05-20 19:48:09 +00:00
proj , err := s . getAppProject ( ctx , a , log . WithFields ( applog . GetAppLogFields ( a ) ) )
2024-04-15 07:20:07 +00:00
if err != nil {
return nil , err
}
2025-01-13 18:15:42 +00:00
destCluster , err := argo . GetDestinationCluster ( ctx , app . Spec . Destination , s . db )
if err != nil {
return nil , err
}
2023-06-23 18:45:53 +00:00
// First, make sure all the returned resources are permitted, for each operation.
// Also perform create with dry-runs for all create-operation resources.
// This is performed separately to reduce the risk of only some of the resources being successfully created later.
// TODO: when apply/delete operations would be supported for custom actions,
// the dry-run for relevant apply/delete operation would have to be invoked as well.
for _ , impactedResource := range newObjects {
newObj := impactedResource . UnstructuredObj
2025-01-13 18:15:42 +00:00
err := s . verifyResourcePermitted ( destCluster , proj , newObj )
2023-06-23 18:45:53 +00:00
if err != nil {
return nil , err
}
2025-01-07 14:56:38 +00:00
if impactedResource . K8SOperation == lua . CreateOperation {
2023-06-23 18:45:53 +00:00
createOptions := metav1 . CreateOptions { DryRun : [ ] string { "All" } }
_ , err := s . kubectl . CreateResource ( ctx , config , newObj . GroupVersionKind ( ) , newObj . GetName ( ) , newObj . GetNamespace ( ) , newObj , createOptions )
if err != nil {
return nil , err
}
}
}
// Now, perform the actual operations.
// The creation itself is not transactional.
// TODO: maybe create a k8s list representation of the resources,
// and invoke create on this list resource to make it semi-transactional (there is still patch operation that is separate,
// thus can fail separately from create).
for _ , impactedResource := range newObjects {
newObj := impactedResource . UnstructuredObj
newObjBytes , err := json . Marshal ( newObj )
if err != nil {
return nil , fmt . Errorf ( "error marshaling new object: %w" , err )
}
switch impactedResource . K8SOperation {
// No default case since a not supported operation would have failed upon unmarshaling earlier
case lua . PatchOperation :
_ , err := s . patchResource ( ctx , config , liveObjBytes , newObjBytes , newObj )
if err != nil {
return nil , err
}
case lua . CreateOperation :
_ , err := s . createResource ( ctx , config , newObj )
if err != nil {
return nil , err
}
}
2019-04-16 21:50:44 +00:00
}
2023-06-23 18:45:53 +00:00
if res == nil {
2025-01-07 15:08:51 +00:00
s . logAppEvent ( ctx , a , argo . EventReasonResourceActionRan , "ran action " + q . GetAction ( ) )
2023-06-23 18:45:53 +00:00
} else {
2025-01-07 15:08:51 +00:00
s . logAppEvent ( ctx , a , argo . EventReasonResourceActionRan , fmt . Sprintf ( "ran action %s on resource %s/%s/%s" , q . GetAction ( ) , res . Group , res . Kind , res . Name ) )
s . logResourceEvent ( ctx , res , argo . EventReasonResourceActionRan , "ran action " + q . GetAction ( ) )
2023-06-23 18:45:53 +00:00
}
return & application . ApplicationResponse { } , nil
}
func ( s * Server ) patchResource ( ctx context . Context , config * rest . Config , liveObjBytes , newObjBytes [ ] byte , newObj * unstructured . Unstructured ) ( * application . ApplicationResponse , error ) {
2019-04-16 21:50:44 +00:00
diffBytes , err := jsonpatch . CreateMergePatch ( liveObjBytes , newObjBytes )
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error calculating merge patch: %w" , err )
2019-04-16 21:50:44 +00:00
}
if string ( diffBytes ) == "{}" {
2019-06-05 19:58:11 +00:00
return & application . ApplicationResponse { } , nil
2019-04-16 21:50:44 +00:00
}
2020-11-02 10:09:05 +00:00
// The following logic detects if the resource action makes a modification to status and/or spec.
// If status was modified, we attempt to patch the status using status subresource, in case the
// CRD is configured using the status subresource feature. See:
// https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#status-subresource
// If status subresource is in use, the patch has to be split into two:
// * one to update spec (and other non-status fields)
// * the other to update only status.
nonStatusPatch , statusPatch , err := splitStatusPatch ( diffBytes )
2019-04-16 21:50:44 +00:00
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error splitting status patch: %w" , err )
2019-04-16 21:50:44 +00:00
}
2020-11-02 10:09:05 +00:00
if statusPatch != nil {
_ , err = s . kubectl . PatchResource ( ctx , config , newObj . GroupVersionKind ( ) , newObj . GetName ( ) , newObj . GetNamespace ( ) , types . MergePatchType , diffBytes , "status" )
if err != nil {
2025-01-03 17:09:37 +00:00
if ! apierrors . IsNotFound ( err ) {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error patching resource: %w" , err )
2020-11-02 10:09:05 +00:00
}
// K8s API server returns 404 NotFound when the CRD does not support the status subresource
// if we get here, the CRD does not use the status subresource. We will fall back to a normal patch
} else {
// If we get here, the CRD does use the status subresource, so we must patch status and
// spec separately. update the diffBytes to the spec-only patch and fall through.
diffBytes = nonStatusPatch
}
}
if diffBytes != nil {
_ , err = s . kubectl . PatchResource ( ctx , config , newObj . GroupVersionKind ( ) , newObj . GetName ( ) , newObj . GetNamespace ( ) , types . MergePatchType , diffBytes )
if err != nil {
2022-05-20 21:16:55 +00:00
return nil , fmt . Errorf ( "error patching resource: %w" , err )
2020-11-02 10:09:05 +00:00
}
}
2023-06-23 18:45:53 +00:00
return & application . ApplicationResponse { } , nil
}
2020-11-02 10:09:05 +00:00
2025-01-13 18:15:42 +00:00
func ( s * Server ) verifyResourcePermitted ( destCluster * v1alpha1 . Cluster , proj * v1alpha1 . AppProject , obj * unstructured . Unstructured ) error {
2025-12-03 20:55:28 +00:00
permitted , err := proj . IsResourcePermitted ( schema . GroupKind { Group : obj . GroupVersionKind ( ) . Group , Kind : obj . GroupVersionKind ( ) . Kind } , obj . GetName ( ) , obj . GetNamespace ( ) , destCluster , func ( project string ) ( [ ] * v1alpha1 . Cluster , error ) {
2023-06-23 18:45:53 +00:00
clusters , err := s . db . GetProjectClusters ( context . TODO ( ) , project )
if err != nil {
return nil , fmt . Errorf ( "failed to get project clusters: %w" , err )
}
return clusters , nil
} )
if err != nil {
return fmt . Errorf ( "error checking resource permissions: %w" , err )
}
if ! permitted {
2025-01-13 18:15:42 +00:00
return fmt . Errorf ( "application is not permitted to manage %s/%s/%s in %s" , obj . GroupVersionKind ( ) . Group , obj . GroupVersionKind ( ) . Kind , obj . GetName ( ) , obj . GetNamespace ( ) )
2023-06-23 18:45:53 +00:00
}
return nil
}
func ( s * Server ) createResource ( ctx context . Context , config * rest . Config , newObj * unstructured . Unstructured ) ( * application . ApplicationResponse , error ) {
_ , err := s . kubectl . CreateResource ( ctx , config , newObj . GroupVersionKind ( ) , newObj . GetName ( ) , newObj . GetNamespace ( ) , newObj , metav1 . CreateOptions { } )
if err != nil {
return nil , fmt . Errorf ( "error creating resource: %w" , err )
2022-08-19 20:43:35 +00:00
}
2019-06-05 19:58:11 +00:00
return & application . ApplicationResponse { } , nil
2019-04-16 21:50:44 +00:00
}
2019-09-06 22:37:25 +00:00
2020-11-02 10:09:05 +00:00
// splitStatusPatch splits a patch into two: one for a non-status patch, and the status-only patch.
// Returns nil for either if the patch doesn't have modifications to non-status, or status, respectively.
func splitStatusPatch ( patch [ ] byte ) ( [ ] byte , [ ] byte , error ) {
2025-01-02 23:26:59 +00:00
var obj map [ string ] any
2020-11-02 10:09:05 +00:00
err := json . Unmarshal ( patch , & obj )
if err != nil {
return nil , nil , err
}
var nonStatusPatch , statusPatch [ ] byte
if statusVal , ok := obj [ "status" ] ; ok {
// calculate the status-only patch
2025-01-02 23:26:59 +00:00
statusObj := map [ string ] any {
2020-11-02 10:09:05 +00:00
"status" : statusVal ,
}
statusPatch , err = json . Marshal ( statusObj )
if err != nil {
return nil , nil , err
}
// remove status, and calculate the non-status patch
delete ( obj , "status" )
if len ( obj ) > 0 {
nonStatusPatch , err = json . Marshal ( obj )
if err != nil {
return nil , nil , err
}
}
} else {
// status was not modified in patch
nonStatusPatch = patch
}
return nonStatusPatch , statusPatch , nil
}
2019-10-08 22:20:19 +00:00
func ( s * Server ) GetApplicationSyncWindows ( ctx context . Context , q * application . ApplicationSyncWindowsQuery ) ( * application . ApplicationSyncWindowsResponse , error ) {
2025-02-20 10:40:15 +00:00
a , proj , err := s . getApplicationEnforceRBACClient ( ctx , rbac . ActionGet , q . GetProject ( ) , q . GetAppNamespace ( ) , q . GetName ( ) , "" )
2019-10-01 22:23:09 +00:00
if err != nil {
return nil , err
}
2019-10-08 22:20:19 +00:00
windows := proj . Spec . SyncWindows . Matches ( a )
2024-10-12 17:32:46 +00:00
sync , err := windows . CanSync ( true )
if err != nil {
return nil , fmt . Errorf ( "invalid sync windows: %w" , err )
}
2019-10-01 22:23:09 +00:00
2024-10-12 17:32:46 +00:00
activeWindows , err := windows . Active ( )
if err != nil {
return nil , fmt . Errorf ( "invalid sync windows: %w" , err )
}
2019-10-08 22:20:19 +00:00
res := & application . ApplicationSyncWindowsResponse {
2024-10-12 17:32:46 +00:00
ActiveWindows : convertSyncWindows ( activeWindows ) ,
2019-10-08 22:20:19 +00:00
AssignedWindows : convertSyncWindows ( windows ) ,
CanSync : & sync ,
2019-10-01 22:23:09 +00:00
}
return res , nil
}
2019-10-08 22:20:19 +00:00
2025-01-06 16:34:32 +00:00
func ( s * Server ) inferResourcesStatusHealth ( app * v1alpha1 . Application ) {
if app . Status . ResourceHealthSource == v1alpha1 . ResourceHealthLocationAppTree {
tree := & v1alpha1 . ApplicationTree { }
2025-05-22 13:46:48 +00:00
if err := s . cache . GetAppResourcesTree ( app . InstanceName ( s . ns ) , tree ) ; err == nil {
2025-01-06 16:34:32 +00:00
healthByKey := map [ kube . ResourceKey ] * v1alpha1 . HealthStatus { }
2022-08-17 21:03:24 +00:00
for _ , node := range tree . Nodes {
2025-03-04 11:29:23 +00:00
if node . Health != nil {
healthByKey [ kube . NewResourceKey ( node . Group , node . Kind , node . Namespace , node . Name ) ] = node . Health
2025-03-27 16:37:52 +00:00
} else if node . ResourceVersion == "" && node . UID == "" && node . CreatedAt == nil {
2025-03-04 11:29:23 +00:00
healthByKey [ kube . NewResourceKey ( node . Group , node . Kind , node . Namespace , node . Name ) ] = & v1alpha1 . HealthStatus {
Status : health . HealthStatusMissing ,
Message : "Resource has not been created" ,
}
}
2022-08-17 21:03:24 +00:00
}
for i , res := range app . Status . Resources {
res . Health = healthByKey [ kube . NewResourceKey ( res . Group , res . Kind , res . Namespace , res . Name ) ]
app . Status . Resources [ i ] = res
}
}
}
}
2025-01-06 16:34:32 +00:00
func convertSyncWindows ( w * v1alpha1 . SyncWindows ) [ ] * application . ApplicationSyncWindow {
2019-10-08 22:20:19 +00:00
if w != nil {
var windows [ ] * application . ApplicationSyncWindow
for _ , w := range * w {
nw := & application . ApplicationSyncWindow {
Kind : & w . Kind ,
Schedule : & w . Schedule ,
Duration : & w . Duration ,
ManualSync : & w . ManualSync ,
}
windows = append ( windows , nw )
}
if len ( windows ) > 0 {
return windows
}
}
return nil
}
2021-03-15 16:27:41 +00:00
func getPropagationPolicyFinalizer ( policy string ) string {
switch strings . ToLower ( policy ) {
case backgroundPropagationPolicy :
2025-01-06 16:34:32 +00:00
return v1alpha1 . BackgroundPropagationPolicyFinalizer
2021-03-15 16:27:41 +00:00
case foregroundPropagationPolicy :
2025-01-06 16:34:32 +00:00
return v1alpha1 . ForegroundPropagationPolicyFinalizer
2021-03-15 16:27:41 +00:00
case "" :
2025-01-06 16:34:32 +00:00
return v1alpha1 . ResourcesFinalizerName
2021-03-15 16:27:41 +00:00
default :
return ""
}
}
2022-08-10 09:39:10 +00:00
func ( s * Server ) appNamespaceOrDefault ( appNs string ) string {
if appNs == "" {
return s . ns
}
2025-01-07 15:25:22 +00:00
return appNs
2022-08-10 09:39:10 +00:00
}
func ( s * Server ) isNamespaceEnabled ( namespace string ) bool {
2022-12-22 18:28:53 +00:00
return security . IsNamespaceEnabled ( namespace , s . ns , s . enabledNamespaces )
2022-08-10 09:39:10 +00:00
}
2023-03-14 20:33:45 +00:00
2024-08-04 19:48:46 +00:00
// getProjectsFromApplicationQuery gets the project names from a query. If the legacy "project" field was specified, use
2023-03-14 20:33:45 +00:00
// that. Otherwise, use the newer "projects" field.
func getProjectsFromApplicationQuery ( q application . ApplicationQuery ) [ ] string {
if q . Project != nil {
return q . Project
}
return q . Projects
}
2025-08-12 17:02:21 +00:00
// ServerSideDiff gets the destination cluster and creates a server-side dry run applier and performs the diff
// It returns the diff result in the form of a list of ResourceDiffs.
func ( s * Server ) ServerSideDiff ( ctx context . Context , q * application . ApplicationServerSideDiffQuery ) ( * application . ApplicationServerSideDiffResponse , error ) {
a , _ , err := s . getApplicationEnforceRBACInformer ( ctx , rbac . ActionGet , q . GetProject ( ) , q . GetAppNamespace ( ) , q . GetAppName ( ) )
if err != nil {
return nil , fmt . Errorf ( "error getting application: %w" , err )
}
argoSettings , err := s . settingsMgr . GetSettings ( )
if err != nil {
return nil , fmt . Errorf ( "error getting ArgoCD settings: %w" , err )
}
resourceOverrides , err := s . settingsMgr . GetResourceOverrides ( )
if err != nil {
return nil , fmt . Errorf ( "error getting resource overrides: %w" , err )
}
// Convert to map format expected by DiffConfigBuilder
overrides := make ( map [ string ] v1alpha1 . ResourceOverride )
2026-02-09 12:37:34 +00:00
maps . Copy ( overrides , resourceOverrides )
2025-08-12 17:02:21 +00:00
// Get cluster connection for server-side dry run
cluster , err := argo . GetDestinationCluster ( ctx , a . Spec . Destination , s . db )
if err != nil {
return nil , fmt . Errorf ( "error getting destination cluster: %w" , err )
}
clusterConfig , err := cluster . RawRestConfig ( )
if err != nil {
return nil , fmt . Errorf ( "error getting cluster raw REST config: %w" , err )
}
// Create server-side diff dry run applier
openAPISchema , gvkParser , err := s . kubectl . LoadOpenAPISchema ( clusterConfig )
if err != nil {
return nil , fmt . Errorf ( "failed to get OpenAPI schema: %w" , err )
}
applier , cleanup , err := kubeutil . ManageServerSideDiffDryRuns ( clusterConfig , openAPISchema , func ( _ string ) ( kube . CleanupFunc , error ) {
return func ( ) { } , nil
} )
if err != nil {
return nil , fmt . Errorf ( "error creating server-side dry run applier: %w" , err )
}
defer cleanup ( )
dryRunner := diff . NewK8sServerSideDryRunner ( applier )
appLabelKey , err := s . settingsMgr . GetAppInstanceLabelKey ( )
if err != nil {
return nil , fmt . Errorf ( "error getting app instance label key: %w" , err )
}
// Build diff config like the CLI does, but with server-side diff enabled
ignoreAggregatedRoles := false
diffConfig , err := argodiff . NewDiffConfigBuilder ( ) .
WithDiffSettings ( a . Spec . IgnoreDifferences , overrides , ignoreAggregatedRoles , normalizers . IgnoreNormalizerOpts { } ) .
WithTracking ( appLabelKey , argoSettings . TrackingMethod ) .
WithNoCache ( ) .
WithManager ( argocommon . ArgoCDSSAManager ) .
WithServerSideDiff ( true ) .
WithServerSideDryRunner ( dryRunner ) .
WithGVKParser ( gvkParser ) .
WithIgnoreMutationWebhook ( ! resourceutil . HasAnnotationOption ( a , argocommon . AnnotationCompareOptions , "IncludeMutationWebhook=true" ) ) .
Build ( )
if err != nil {
return nil , fmt . Errorf ( "error building diff config: %w" , err )
}
// Convert live resources to unstructured objects
liveObjs := make ( [ ] * unstructured . Unstructured , 0 , len ( q . GetLiveResources ( ) ) )
for _ , liveResource := range q . GetLiveResources ( ) {
if liveResource . LiveState != "" && liveResource . LiveState != "null" {
liveObj := & unstructured . Unstructured { }
err := json . Unmarshal ( [ ] byte ( liveResource . LiveState ) , liveObj )
if err != nil {
return nil , fmt . Errorf ( "error unmarshaling live state for %s/%s: %w" , liveResource . Kind , liveResource . Name , err )
}
liveObjs = append ( liveObjs , liveObj )
} else {
liveObjs = append ( liveObjs , nil )
}
}
// Convert target manifests to unstructured objects
targetObjs := make ( [ ] * unstructured . Unstructured , 0 , len ( q . GetTargetManifests ( ) ) )
for i , manifestStr := range q . GetTargetManifests ( ) {
obj , err := v1alpha1 . UnmarshalToUnstructured ( manifestStr )
if err != nil {
return nil , fmt . Errorf ( "error unmarshaling target manifest %d: %w" , i , err )
}
targetObjs = append ( targetObjs , obj )
}
diffResults , err := argodiff . StateDiffs ( liveObjs , targetObjs , diffConfig )
if err != nil {
return nil , fmt . Errorf ( "error performing state diffs: %w" , err )
}
// Convert StateDiffs results to ResourceDiff format for API response
responseDiffs := make ( [ ] * v1alpha1 . ResourceDiff , 0 , len ( diffResults . Diffs ) )
modified := false
for i , diffRes := range diffResults . Diffs {
if diffRes . Modified {
modified = true
}
// Extract resource metadata for the diff result. Resources should be pre-aligned by the CLI.
var group , kind , namespace , name string
var hook bool
var resourceVersion string
// Extract resource metadata for the ResourceDiff response. The CLI sends aligned arrays
// of live resources and target manifests, but individual resources may only exist in one
// array depending on the operation
switch {
case i < len ( q . GetLiveResources ( ) ) :
// A live resource exists at this index
lr := q . GetLiveResources ( ) [ i ]
group = lr . Group
kind = lr . Kind
namespace = lr . Namespace
name = lr . Name
hook = lr . Hook
resourceVersion = lr . ResourceVersion
case i < len ( targetObjs ) && targetObjs [ i ] != nil :
// A target resource exists at this index, but no live resource exists at this index
obj := targetObjs [ i ]
group = obj . GroupVersionKind ( ) . Group
kind = obj . GroupVersionKind ( ) . Kind
namespace = obj . GetNamespace ( )
name = obj . GetName ( )
hook = false
resourceVersion = ""
default :
return nil , fmt . Errorf ( "diff result index %d out of bounds: live resources (%d), target objects (%d)" ,
i , len ( q . GetLiveResources ( ) ) , len ( targetObjs ) )
}
// Create ResourceDiff with StateDiffs results
// TargetState = PredictedLive (what the target should be after applying)
// LiveState = NormalizedLive (current normalized live state)
responseDiffs = append ( responseDiffs , & v1alpha1 . ResourceDiff {
Group : group ,
Kind : kind ,
Namespace : namespace ,
Name : name ,
TargetState : string ( diffRes . PredictedLive ) ,
LiveState : string ( diffRes . NormalizedLive ) ,
Diff : "" , // Diff string is generated client-side
Hook : hook ,
Modified : diffRes . Modified ,
ResourceVersion : resourceVersion ,
} )
}
log . Infof ( "ServerSideDiff completed with %d results, overall modified: %t" , len ( responseDiffs ) , modified )
return & application . ApplicationServerSideDiffResponse {
Items : responseDiffs ,
Modified : & modified ,
} , nil
}