2018-02-19 11:39:03 +00:00
package application
import (
2018-04-06 20:08:29 +00:00
"bufio"
2018-05-01 18:05:56 +00:00
"encoding/json"
2018-06-13 20:05:39 +00:00
"fmt"
2018-05-07 20:20:38 +00:00
"reflect"
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
2018-02-28 05:37:23 +00:00
log "github.com/sirupsen/logrus"
2018-02-19 11:39:03 +00:00
"golang.org/x/net/context"
2018-04-20 08:19:53 +00:00
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
2018-04-06 20:08:29 +00:00
"k8s.io/api/core/v1"
2018-03-16 23:17:03 +00:00
apierr "k8s.io/apimachinery/pkg/api/errors"
2018-02-19 11:39:03 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2018-08-15 22:01:29 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2018-05-30 22:30:58 +00:00
"k8s.io/apimachinery/pkg/fields"
2018-05-01 18:05:56 +00:00
"k8s.io/apimachinery/pkg/types"
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"
2018-06-06 04:44:14 +00:00
"github.com/argoproj/argo-cd/common"
"github.com/argoproj/argo-cd/controller"
appv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned"
"github.com/argoproj/argo-cd/reposerver"
"github.com/argoproj/argo-cd/reposerver/repository"
"github.com/argoproj/argo-cd/util"
2018-07-07 07:54:06 +00:00
"github.com/argoproj/argo-cd/util/argo"
2018-06-18 17:22:58 +00:00
argoutil "github.com/argoproj/argo-cd/util/argo"
2018-06-06 04:44:14 +00:00
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/grpc"
2018-08-15 22:01:29 +00:00
"github.com/argoproj/argo-cd/util/kube"
2018-06-06 04:44:14 +00:00
"github.com/argoproj/argo-cd/util/rbac"
2018-07-24 15:48:13 +00:00
"github.com/argoproj/argo-cd/util/session"
2018-02-19 11:39:03 +00:00
)
2018-02-28 11:07:56 +00:00
// Server provides a Application service
2018-02-19 11:39:03 +00:00
type Server struct {
ns string
kubeclientset kubernetes . Interface
appclientset appclientset . Interface
2018-03-07 22:34:43 +00:00
repoClientset reposerver . Clientset
2018-09-10 17:14:14 +00:00
kubectl kube . Kubectl
2018-05-14 18:36:08 +00:00
db db . ArgoDB
appComparator controller . AppStateManager
2018-06-06 04:44:14 +00:00
enf * rbac . Enforcer
2018-06-22 17:05:57 +00:00
projectLock * util . KeyLock
2018-07-24 15:48:13 +00:00
auditLogger * argo . AuditLogger
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 ,
repoClientset reposerver . Clientset ,
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 ,
2018-06-22 17:05:57 +00:00
projectLock * util . KeyLock ,
2018-05-14 18:36:08 +00:00
) ApplicationServiceServer {
2018-03-07 22:34:43 +00:00
2018-02-19 11:39:03 +00:00
return & Server {
2018-05-14 18:36:08 +00:00
ns : namespace ,
appclientset : appclientset ,
kubeclientset : kubeclientset ,
db : db ,
repoClientset : repoClientset ,
2018-09-10 17:14:14 +00:00
kubectl : kubectl ,
appComparator : controller . NewAppStateManager ( db , appclientset , repoClientset , namespace , kubectl ) ,
2018-06-06 04:44:14 +00:00
enf : enf ,
2018-06-22 17:05:57 +00:00
projectLock : projectLock ,
2018-07-24 15:48:13 +00:00
auditLogger : argo . NewAuditLogger ( namespace , kubeclientset , "argocd-server" ) ,
2018-02-19 11:39:03 +00:00
}
}
2018-06-12 17:43:16 +00:00
// appRBACName formats fully qualified application name for RBAC check
func appRBACName ( app appv1 . Application ) string {
2018-06-22 17:05:57 +00:00
return fmt . Sprintf ( "%s/%s" , app . Spec . GetProject ( ) , app . Name )
2018-06-12 17:43:16 +00:00
}
2018-09-07 20:51:32 +00:00
func toString ( val interface { } ) string {
if val == nil {
return ""
}
return fmt . Sprintf ( "%s" , val )
}
// hideSecretData checks if given object kind is Secret, replaces data keys with stars and returns unchanged data map. The method additionally check if data key if different
// from corresponding key of optional parameter `otherData` and adds extra star to keep information about difference. So if secret data is out of sync user still can see which
// fields are different.
func hideSecretData ( state string , otherData map [ string ] interface { } ) ( string , map [ string ] interface { } ) {
obj , err := appv1 . UnmarshalToUnstructured ( state )
if err == nil {
if obj != nil && obj . GetKind ( ) == kube . SecretKind {
if data , ok , err := unstructured . NestedMap ( obj . Object , "data" ) ; err == nil && ok {
unchangedData := make ( map [ string ] interface { } )
for k , v := range data {
unchangedData [ k ] = v
}
for k := range data {
replacement := "********"
if otherData != nil {
if val , ok := otherData [ k ] ; ok && toString ( val ) != toString ( data [ k ] ) {
replacement = replacement + "*"
}
}
data [ k ] = replacement
}
_ = unstructured . SetNestedMap ( obj . Object , data , "data" )
newState , err := json . Marshal ( obj )
if err == nil {
return string ( newState ) , unchangedData
}
}
}
}
return state , nil
}
func hideNodesSecrets ( nodes [ ] appv1 . ResourceNode ) {
for i := range nodes {
node := nodes [ i ]
node . State , _ = hideSecretData ( node . State , nil )
hideNodesSecrets ( node . Children )
nodes [ i ] = node
}
}
func hideAppSecrets ( app * appv1 . Application ) {
for i := range app . Status . ComparisonResult . Resources {
res := app . Status . ComparisonResult . Resources [ i ]
var data map [ string ] interface { }
res . LiveState , data = hideSecretData ( res . LiveState , nil )
res . TargetState , _ = hideSecretData ( res . TargetState , data )
hideNodesSecrets ( res . ChildLiveResources )
app . Status . ComparisonResult . Resources [ i ] = res
}
}
2018-02-19 11:39:03 +00:00
// List returns list of applications
func ( s * Server ) List ( ctx context . Context , q * ApplicationQuery ) ( * appv1 . ApplicationList , error ) {
2018-06-06 04:44:14 +00:00
appList , err := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . List ( metav1 . ListOptions { } )
if err != nil {
return nil , err
}
newItems := make ( [ ] appv1 . Application , 0 )
2018-06-12 17:43:16 +00:00
for _ , a := range appList . Items {
if s . enf . EnforceClaims ( ctx . Value ( "claims" ) , "applications" , "get" , appRBACName ( a ) ) {
newItems = append ( newItems , a )
2018-06-06 04:44:14 +00:00
}
}
2018-06-22 17:05:57 +00:00
newItems = argoutil . FilterByProjects ( newItems , q . Projects )
2018-09-07 20:51:32 +00:00
for i := range newItems {
app := newItems [ i ]
hideAppSecrets ( & app )
newItems [ i ] = app
}
2018-06-06 04:44:14 +00:00
appList . Items = newItems
return appList , nil
2018-02-19 11:39:03 +00:00
}
2018-04-20 08:19:53 +00:00
// Create creates an application
2018-05-31 21:21:08 +00:00
func ( s * Server ) Create ( ctx context . Context , q * ApplicationCreateRequest ) ( * appv1 . Application , error ) {
2018-06-12 17:43:16 +00:00
if ! s . enf . EnforceClaims ( ctx . Value ( "claims" ) , "applications" , "create" , appRBACName ( q . Application ) ) {
2018-06-06 04:44:14 +00:00
return nil , grpc . ErrPermissionDenied
}
2018-06-22 17:05:57 +00:00
2018-08-28 17:06:01 +00:00
s . projectLock . Lock ( q . Application . Spec . Project )
defer s . projectLock . Unlock ( q . Application . Spec . Project )
2018-06-22 17:05:57 +00:00
2018-05-31 21:21:08 +00:00
a := q . Application
2018-05-03 22:55:01 +00:00
err := s . validateApp ( ctx , & a . Spec )
2018-04-20 08:19:53 +00:00
if err != nil {
return nil , err
}
2018-05-31 21:21:08 +00:00
out , err := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . Create ( & a )
2018-05-07 20:20:38 +00:00
if apierr . IsAlreadyExists ( err ) {
// act idempotent if existing spec matches new spec
2018-05-30 20:49:20 +00:00
existing , getErr := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . Get ( a . Name , metav1 . GetOptions { } )
if getErr != nil {
2018-06-27 20:40:32 +00:00
return nil , status . Errorf ( codes . Internal , "unable to check existing application details: %v" , getErr )
2018-05-30 20:49:20 +00:00
}
2018-06-01 00:54:27 +00:00
if q . Upsert != nil && * q . Upsert {
2018-06-12 17:43:16 +00:00
if ! s . enf . EnforceClaims ( ctx . Value ( "claims" ) , "applications" , "update" , appRBACName ( a ) ) {
2018-06-06 04:44:14 +00:00
return nil , grpc . ErrPermissionDenied
}
2018-05-30 20:49:20 +00:00
existing . Spec = a . Spec
out , err = s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . Update ( existing )
} else {
2018-05-07 20:20:38 +00:00
if reflect . DeepEqual ( existing . Spec , a . Spec ) {
return existing , nil
2018-05-30 20:49:20 +00:00
} else {
2018-06-01 00:54:27 +00:00
return nil , status . Errorf ( codes . InvalidArgument , "existing application spec is different, use upsert flag to force update" )
2018-05-07 20:20:38 +00:00
}
}
}
2018-07-24 15:48:13 +00:00
if err == nil {
2018-09-24 15:52:43 +00:00
s . logEvent ( out , ctx , argo . EventReasonResourceCreated , "created application" )
2018-07-24 15:48:13 +00:00
}
2018-09-07 20:51:32 +00:00
hideAppSecrets ( out )
2018-05-07 20:20:38 +00:00
return out , err
2018-02-19 11:39:03 +00:00
}
2018-05-17 23:33:04 +00:00
// GetManifests returns application manifests
2018-06-01 00:21:09 +00:00
func ( s * Server ) GetManifests ( ctx context . Context , q * ApplicationManifestQuery ) ( * repository . ManifestResponse , error ) {
2018-06-12 17:43:16 +00:00
a , err := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . Get ( * q . Name , metav1 . GetOptions { } )
2018-05-17 23:33:04 +00:00
if err != nil {
return nil , err
}
2018-09-21 22:25:08 +00:00
if ! s . enf . EnforceClaims ( ctx . Value ( "claims" ) , "applications" , "get" , appRBACName ( * a ) ) {
2018-06-12 17:43:16 +00:00
return nil , grpc . ErrPermissionDenied
}
repo := s . getRepo ( ctx , a . Spec . Source . RepoURL )
2018-05-17 23:33:04 +00:00
conn , repoClient , err := s . repoClientset . NewRepositoryClient ( )
if err != nil {
return nil , err
}
defer util . Close ( conn )
2018-06-12 17:43:16 +00:00
overrides := make ( [ ] * appv1 . ComponentParameter , len ( a . Spec . Source . ComponentParameterOverrides ) )
if a . Spec . Source . ComponentParameterOverrides != nil {
for i := range a . Spec . Source . ComponentParameterOverrides {
item := a . Spec . Source . ComponentParameterOverrides [ i ]
2018-05-17 23:33:04 +00:00
overrides [ i ] = & item
}
}
2018-06-12 17:43:16 +00:00
revision := a . Spec . Source . TargetRevision
2018-06-01 00:21:09 +00:00
if q . Revision != "" {
revision = q . Revision
2018-05-17 23:33:04 +00:00
}
manifestInfo , err := repoClient . GenerateManifest ( context . Background ( ) , & repository . ManifestRequest {
Repo : repo ,
2018-06-12 17:43:16 +00:00
Environment : a . Spec . Source . Environment ,
Path : a . Spec . Source . Path ,
2018-05-17 23:33:04 +00:00
Revision : revision ,
ComponentParameterOverrides : overrides ,
2018-06-12 17:43:16 +00:00
AppLabel : a . Name ,
2018-07-24 23:37:12 +00:00
ValueFiles : a . Spec . Source . ValuesFiles ,
2018-09-15 07:18:07 +00:00
Namespace : a . Spec . Destination . Namespace ,
2018-05-17 23:33:04 +00:00
} )
if err != nil {
return nil , err
}
return manifestInfo , nil
}
2018-04-20 08:19:53 +00:00
// Get returns an application by name
2018-02-19 11:39:03 +00:00
func ( s * Server ) Get ( ctx context . Context , q * ApplicationQuery ) ( * appv1 . Application , error ) {
2018-06-18 17:22:58 +00:00
appIf := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns )
a , err := appIf . Get ( * q . Name , metav1 . GetOptions { } )
2018-06-12 17:43:16 +00:00
if err != nil {
return nil , err
}
if ! s . enf . EnforceClaims ( ctx . Value ( "claims" ) , "applications" , "get" , appRBACName ( * a ) ) {
2018-06-06 04:44:14 +00:00
return nil , grpc . ErrPermissionDenied
}
2018-06-18 17:22:58 +00:00
if q . Refresh {
_ , err = argoutil . RefreshApp ( appIf , * q . Name )
if err != nil {
return nil , err
}
a , err = argoutil . WaitForRefresh ( appIf , * q . Name , nil )
if err != nil {
return nil , err
}
}
2018-09-07 20:51:32 +00:00
hideAppSecrets ( a )
2018-06-12 17:43:16 +00:00
return a , nil
2018-02-19 11:39:03 +00:00
}
2018-05-30 22:30:58 +00:00
// ListResourceEvents returns a list of event resources
func ( s * Server ) ListResourceEvents ( ctx context . Context , q * ApplicationResourceEventsQuery ) ( * v1 . EventList , error ) {
2018-06-12 17:43:16 +00:00
a , err := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . Get ( * q . Name , metav1 . GetOptions { } )
if err != nil {
return nil , err
}
2018-09-21 22:25:08 +00:00
if ! s . enf . EnforceClaims ( ctx . Value ( "claims" ) , "applications" , "get" , appRBACName ( * a ) ) {
2018-06-06 04:44:14 +00:00
return nil , grpc . ErrPermissionDenied
}
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
2018-07-24 15:48:13 +00:00
if q . ResourceName == "" && q . ResourceUID == "" {
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 {
2018-07-31 21:15:53 +00:00
var config * rest . Config
config , namespace , err = s . getApplicationClusterConfig ( * q . Name )
if err != nil {
return nil , err
}
kubeClientset , err = kubernetes . NewForConfig ( config )
if err != nil {
return nil , err
}
2018-07-24 15:48:13 +00:00
fieldSelector = fields . SelectorFromSet ( map [ string ] string {
"involvedObject.name" : q . ResourceName ,
"involvedObject.uid" : q . ResourceUID ,
"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 }
return kubeClientset . CoreV1 ( ) . Events ( namespace ) . List ( opts )
}
2018-04-20 08:19:53 +00:00
// Update updates an application
2018-05-31 21:21:08 +00:00
func ( s * Server ) Update ( ctx context . Context , q * ApplicationUpdateRequest ) ( * appv1 . Application , error ) {
2018-06-12 17:43:16 +00:00
if ! s . enf . EnforceClaims ( ctx . Value ( "claims" ) , "applications" , "update" , appRBACName ( * q . Application ) ) {
2018-06-06 04:44:14 +00:00
return nil , grpc . ErrPermissionDenied
}
2018-06-22 17:05:57 +00:00
2018-08-28 17:06:01 +00:00
s . projectLock . Lock ( q . Application . Spec . Project )
defer s . projectLock . Unlock ( q . Application . Spec . Project )
2018-06-22 17:05:57 +00:00
2018-05-31 21:21:08 +00:00
a := q . Application
2018-05-03 22:55:01 +00:00
err := s . validateApp ( ctx , & a . Spec )
2018-04-20 08:19:53 +00:00
if err != nil {
return nil , err
}
2018-09-07 20:51:32 +00:00
out , err := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . Update ( a )
if out != nil {
hideAppSecrets ( out )
}
2018-09-24 15:52:43 +00:00
if err == nil {
s . logEvent ( a , ctx , argo . EventReasonResourceUpdated , "updated application" )
}
2018-09-07 20:51:32 +00:00
return out , err
2018-02-19 11:39:03 +00:00
}
2018-07-13 21:59:50 +00:00
// removeInvalidOverrides removes any parameter overrides that are no longer valid
2018-06-22 21:22:30 +00:00
// drops old overrides that are invalid
// throws an error is passed override is invalid
// if passed override and old overrides are invalid, throws error, old overrides not dropped
func ( s * Server ) removeInvalidOverrides ( a * appv1 . Application , q * ApplicationUpdateSpecRequest ) ( * ApplicationUpdateSpecRequest , error ) {
2018-07-24 23:37:12 +00:00
if a . Spec . Source . Environment == "" {
// this method is only valid for ksonnet apps
return q , nil
}
2018-07-13 21:59:50 +00:00
oldParams := argo . ParamToMap ( a . Spec . Source . ComponentParameterOverrides )
2018-06-22 21:22:30 +00:00
validAppSet := argo . ParamToMap ( a . Status . Parameters )
2018-07-13 21:59:50 +00:00
params := make ( [ ] appv1 . ComponentParameter , 0 )
for i := range q . Spec . Source . ComponentParameterOverrides {
param := q . Spec . Source . ComponentParameterOverrides [ i ]
if ! argo . CheckValidParam ( validAppSet , param ) {
alreadySet := argo . CheckValidParam ( oldParams , param )
if ! alreadySet {
2018-07-24 23:37:12 +00:00
return nil , status . Errorf ( codes . InvalidArgument , "Parameter '%s' in '%s' does not exist in ksonnet app" , param . Name , param . Component )
2018-06-22 21:22:30 +00:00
}
2018-07-13 21:59:50 +00:00
} else {
params = append ( params , param )
2018-06-22 21:22:30 +00:00
}
}
2018-07-13 21:59:50 +00:00
q . Spec . Source . ComponentParameterOverrides = params
2018-06-22 21:22:30 +00:00
return q , nil
}
// UpdateSpec updates an application spec and filters out any invalid parameter overrides
2018-06-01 00:54:27 +00:00
func ( s * Server ) UpdateSpec ( ctx context . Context , q * ApplicationUpdateSpecRequest ) ( * appv1 . ApplicationSpec , error ) {
2018-08-28 17:06:01 +00:00
s . projectLock . Lock ( q . Spec . Project )
defer s . projectLock . Unlock ( q . Spec . Project )
2018-06-22 17:05:57 +00:00
2018-06-12 17:43:16 +00:00
a , err := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . Get ( * q . Name , metav1 . GetOptions { } )
if err != nil {
return nil , err
}
if ! s . enf . EnforceClaims ( ctx . Value ( "claims" ) , "applications" , "update" , appRBACName ( * a ) ) {
2018-06-06 04:44:14 +00:00
return nil , grpc . ErrPermissionDenied
}
2018-06-12 17:43:16 +00:00
err = s . validateApp ( ctx , & q . Spec )
2018-05-03 22:55:01 +00:00
if err != nil {
return nil , err
}
2018-06-22 21:22:30 +00:00
q , err = s . removeInvalidOverrides ( a , q )
if err != nil {
return nil , err
}
2018-07-24 23:37:12 +00:00
for {
a . Spec = q . Spec
_ , err = s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . Update ( a )
if err == nil {
2018-09-24 15:52:43 +00:00
s . logEvent ( a , ctx , argo . EventReasonResourceUpdated , "updated application spec" )
2018-07-24 23:37:12 +00:00
return & q . Spec , nil
}
if ! apierr . IsConflict ( err ) {
return nil , err
}
a , err = s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . Get ( * q . Name , metav1 . GetOptions { } )
if err != nil {
return nil , err
}
2018-07-24 15:48:13 +00:00
}
2018-05-03 22:55:01 +00:00
}
2018-03-16 23:17:03 +00:00
// Delete removes an application and all associated resources
2018-05-31 21:21:08 +00:00
func ( s * Server ) Delete ( ctx context . Context , q * ApplicationDeleteRequest ) ( * ApplicationResponse , error ) {
2018-05-16 23:30:28 +00:00
a , err := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . Get ( * q . Name , metav1 . GetOptions { } )
if err != nil && ! apierr . IsNotFound ( err ) {
return nil , err
2018-03-16 23:17:03 +00:00
}
2018-04-06 15:27:51 +00:00
2018-08-28 17:06:01 +00:00
s . projectLock . Lock ( a . Spec . Project )
defer s . projectLock . Unlock ( a . Spec . Project )
2018-06-22 17:05:57 +00:00
2018-06-12 17:43:16 +00:00
if ! s . enf . EnforceClaims ( ctx . Value ( "claims" ) , "applications" , "delete" , appRBACName ( * a ) ) {
return nil , grpc . ErrPermissionDenied
}
2018-07-10 17:50:29 +00:00
patchFinalizer := false
if q . Cascade == nil || * q . Cascade {
if ! a . CascadedDeletion ( ) {
a . SetCascadedDeletion ( true )
patchFinalizer = true
}
} else {
if a . CascadedDeletion ( ) {
a . SetCascadedDeletion ( false )
patchFinalizer = true
}
}
if patchFinalizer {
// Prior to v0.6, the cascaded deletion finalizer was set during app creation.
// For backward compatibility, we always calculate the patch to see if we need to
// set/unset the finalizer (in case we are dealing with an app created prior to v0.6)
2018-06-01 00:54:27 +00:00
patch , err := json . Marshal ( map [ string ] interface { } {
"metadata" : map [ string ] interface { } {
"finalizers" : a . Finalizers ,
} ,
} )
if err != nil {
return nil , err
}
_ , err = s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( a . Namespace ) . Patch ( a . Name , types . MergePatchType , patch )
if err != nil {
return nil , err
2018-03-16 23:17:03 +00:00
}
}
2018-05-16 23:30:28 +00:00
err = s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . Delete ( * q . Name , & metav1 . DeleteOptions { } )
2018-04-06 15:27:51 +00:00
if err != nil && ! apierr . IsNotFound ( err ) {
return nil , err
}
2018-09-24 15:52:43 +00:00
s . logEvent ( a , ctx , argo . EventReasonResourceDeleted , "deleted application" )
2018-03-16 23:17:03 +00:00
return & ApplicationResponse { } , nil
2018-02-19 11:39:03 +00:00
}
2018-02-28 05:37:23 +00:00
func ( s * Server ) Watch ( q * ApplicationQuery , ws ApplicationService_WatchServer ) error {
w , err := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . Watch ( metav1 . ListOptions { } )
if err != nil {
return err
}
2018-06-06 04:44:14 +00:00
claims := ws . Context ( ) . Value ( "claims" )
2018-03-01 17:12:50 +00:00
done := make ( chan bool )
2018-02-28 05:37:23 +00:00
go func ( ) {
for next := range w . ResultChan ( ) {
2018-06-12 17:43:16 +00:00
a := * next . Object . ( * appv1 . Application )
if q . Name == nil || * q . Name == "" || * q . Name == a . Name {
if ! s . enf . EnforceClaims ( claims , "applications" , "get" , appRBACName ( a ) ) {
2018-06-06 04:44:14 +00:00
// do not emit apps user does not have accessing
continue
}
2018-09-07 20:51:32 +00:00
hideAppSecrets ( & a )
2018-02-28 05:37:23 +00:00
err = ws . Send ( & appv1 . ApplicationWatchEvent {
Type : next . Type ,
2018-06-12 17:43:16 +00:00
Application : a ,
2018-02-28 05:37:23 +00:00
} )
if err != nil {
log . Warnf ( "Unable to send stream message: %v" , err )
}
}
}
2018-03-01 17:12:50 +00:00
done <- true
2018-02-28 05:37:23 +00:00
} ( )
select {
case <- ws . Context ( ) . Done ( ) :
w . Stop ( )
2018-03-01 17:12:50 +00:00
case <- done :
2018-07-25 00:23:13 +00:00
w . Stop ( )
2018-02-28 05:37:23 +00:00
}
return nil
}
2018-02-28 11:07:56 +00:00
2018-05-03 22:55:01 +00:00
func ( s * Server ) validateApp ( ctx context . Context , spec * appv1 . ApplicationSpec ) error {
2018-07-09 17:45:03 +00:00
proj , err := argo . GetAppProject ( spec , s . appclientset , s . ns )
2018-04-20 08:19:53 +00:00
if err != nil {
2018-07-10 21:45:18 +00:00
if apierr . IsNotFound ( err ) {
return status . Errorf ( codes . InvalidArgument , "application referencing project %s which does not exist" , spec . Project )
}
2018-04-20 08:19:53 +00:00
return err
}
2018-06-23 18:38:35 +00:00
if ! s . enf . EnforceClaims ( ctx . Value ( "claims" ) , "projects" , "get" , proj . Name ) {
return status . Errorf ( codes . PermissionDenied , "permission denied for project %s" , proj . Name )
}
2018-07-10 21:45:18 +00:00
conditions , err := argo . GetSpecErrors ( ctx , spec , proj , s . repoClientset , s . db )
2018-04-20 08:19:53 +00:00
if err != nil {
return err
}
2018-07-09 17:45:03 +00:00
if len ( conditions ) > 0 {
2018-07-24 23:37:12 +00:00
return status . Errorf ( codes . InvalidArgument , "application spec is invalid: %s" , argo . FormatAppConditions ( conditions ) )
2018-07-09 17:45:03 +00:00
}
2018-04-20 08:19:53 +00:00
return nil
}
2018-04-09 17:39:46 +00:00
func ( s * Server ) getApplicationClusterConfig ( applicationName string ) ( * rest . Config , string , error ) {
server , namespace , err := s . getApplicationDestination ( context . Background ( ) , applicationName )
2018-04-06 20:08:29 +00:00
if err != nil {
2018-04-09 17:39:46 +00:00
return nil , "" , err
2018-04-06 20:08:29 +00:00
}
2018-05-14 18:36:08 +00:00
clst , err := s . db . GetCluster ( context . Background ( ) , server )
2018-04-06 20:08:29 +00:00
if err != nil {
2018-04-09 17:39:46 +00:00
return nil , "" , err
2018-04-06 20:08:29 +00:00
}
config := clst . RESTConfig ( )
2018-04-09 17:39:46 +00:00
return config , namespace , err
}
func ( s * Server ) ensurePodBelongsToApp ( applicationName string , podName , namespace string , kubeClientset * kubernetes . Clientset ) error {
pod , err := kubeClientset . CoreV1 ( ) . Pods ( namespace ) . Get ( podName , metav1 . GetOptions { } )
2018-04-06 20:08:29 +00:00
if err != nil {
return err
}
2018-06-01 00:54:27 +00:00
wrongPodError := status . Errorf ( codes . InvalidArgument , "pod %s does not belong to application %s" , podName , applicationName )
2018-04-06 20:08:29 +00:00
if pod . Labels == nil {
return wrongPodError
}
2018-04-09 17:39:46 +00:00
if value , ok := pod . Labels [ common . LabelApplicationName ] ; ! ok || value != applicationName {
2018-04-06 20:08:29 +00:00
return wrongPodError
}
2018-04-09 17:39:46 +00:00
return nil
}
2018-08-15 22:01:29 +00:00
func ( s * Server ) DeleteResource ( ctx context . Context , q * ApplicationDeleteResourceRequest ) ( * ApplicationResponse , error ) {
2018-06-12 17:43:16 +00:00
a , err := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . Get ( * q . Name , metav1 . GetOptions { } )
if err != nil {
return nil , err
}
2018-09-21 22:25:08 +00:00
if ! s . enf . EnforceClaims ( ctx . Value ( "claims" ) , "applications" , "delete" , appRBACName ( * a ) ) {
2018-06-06 04:44:14 +00:00
return nil , grpc . ErrPermissionDenied
}
2018-08-15 22:01:29 +00:00
found := findResource ( a , q )
if found == nil {
return nil , status . Errorf ( codes . InvalidArgument , "%s %s %s not found as part of application %s" , q . Kind , q . APIVersion , q . ResourceName , * q . Name )
}
2018-06-01 00:21:09 +00:00
config , namespace , err := s . getApplicationClusterConfig ( * q . Name )
2018-04-09 17:39:46 +00:00
if err != nil {
return nil , err
}
2018-09-10 17:14:14 +00:00
err = s . kubectl . DeleteResource ( config , found , namespace )
2018-04-09 17:39:46 +00:00
if err != nil {
return nil , err
}
2018-09-24 15:52:43 +00:00
s . logEvent ( a , ctx , argo . EventReasonResourceDeleted , fmt . Sprintf ( "deleted resource %s/%s '%s'" , q . APIVersion , q . Kind , q . ResourceName ) )
2018-08-15 22:01:29 +00:00
return & ApplicationResponse { } , nil
}
func findResource ( a * appv1 . Application , q * ApplicationDeleteResourceRequest ) * unstructured . Unstructured {
for _ , res := range a . Status . ComparisonResult . Resources {
liveObj , err := res . LiveObject ( )
if err != nil {
log . Warnf ( "Failed to unmarshal live object: %v" , err )
continue
}
if liveObj == nil {
continue
}
if q . ResourceName == liveObj . GetName ( ) && q . APIVersion == liveObj . GetAPIVersion ( ) && q . Kind == liveObj . GetKind ( ) {
return liveObj
}
liveObj = recurseResourceNode ( q . ResourceName , q . APIVersion , q . Kind , res . ChildLiveResources )
if liveObj != nil {
return liveObj
}
2018-04-09 17:39:46 +00:00
}
2018-08-15 22:01:29 +00:00
return nil
}
func recurseResourceNode ( name , apiVersion , kind string , nodes [ ] appv1 . ResourceNode ) * unstructured . Unstructured {
for _ , node := range nodes {
var childObj unstructured . Unstructured
err := json . Unmarshal ( [ ] byte ( node . State ) , & childObj )
if err != nil {
log . Warnf ( "Failed to unmarshal child live object: %v" , err )
continue
}
if name == childObj . GetName ( ) && apiVersion == childObj . GetAPIVersion ( ) && kind == childObj . GetKind ( ) {
return & childObj
}
recurseChildObj := recurseResourceNode ( name , apiVersion , kind , node . Children )
if recurseChildObj != nil {
return recurseChildObj
}
2018-04-09 17:44:00 +00:00
}
2018-08-15 22:01:29 +00:00
return nil
2018-04-09 17:39:46 +00:00
}
2018-06-01 00:21:09 +00:00
func ( s * Server ) PodLogs ( q * ApplicationPodLogsQuery , ws ApplicationService_PodLogsServer ) error {
2018-06-12 17:43:16 +00:00
a , err := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . Get ( * q . Name , metav1 . GetOptions { } )
if err != nil {
return err
}
2018-09-21 22:25:08 +00:00
if ! s . enf . EnforceClaims ( ws . Context ( ) . Value ( "claims" ) , "applications" , "get" , appRBACName ( * a ) ) {
2018-06-06 04:44:14 +00:00
return grpc . ErrPermissionDenied
}
2018-06-01 00:21:09 +00:00
config , namespace , err := s . getApplicationClusterConfig ( * q . Name )
2018-04-09 17:39:46 +00:00
if err != nil {
return err
}
kubeClientset , err := kubernetes . NewForConfig ( config )
if err != nil {
return err
}
2018-06-01 00:21:09 +00:00
err = s . ensurePodBelongsToApp ( * q . Name , * q . PodName , namespace , kubeClientset )
2018-04-09 17:39:46 +00:00
if err != nil {
return err
}
2018-04-06 20:08:29 +00:00
var sinceSeconds , tailLines * int64
if q . SinceSeconds > 0 {
sinceSeconds = & q . SinceSeconds
}
if q . TailLines > 0 {
tailLines = & q . TailLines
}
2018-05-16 23:30:28 +00:00
stream , err := kubeClientset . CoreV1 ( ) . Pods ( namespace ) . GetLogs ( * q . PodName , & v1 . PodLogOptions {
2018-04-06 20:08:29 +00:00
Container : q . Container ,
Follow : q . Follow ,
Timestamps : true ,
SinceSeconds : sinceSeconds ,
SinceTime : q . SinceTime ,
TailLines : tailLines ,
} ) . Stream ( )
if err != nil {
return err
}
done := make ( chan bool )
go func ( ) {
scanner := bufio . NewScanner ( stream )
for scanner . Scan ( ) {
line := scanner . Text ( )
parts := strings . Split ( line , " " )
logTime , err := time . Parse ( time . RFC3339 , parts [ 0 ] )
metaLogTime := metav1 . NewTime ( logTime )
if err == nil {
lines := strings . Join ( parts [ 1 : ] , " " )
for _ , line := range strings . Split ( lines , "\r" ) {
if line != "" {
err = ws . Send ( & LogEntry {
Content : line ,
2018-05-16 23:30:28 +00:00
TimeStamp : metaLogTime ,
2018-04-06 20:08:29 +00:00
} )
if err != nil {
log . Warnf ( "Unable to send stream message: %v" , err )
}
}
}
}
}
done <- true
} ( )
select {
case <- ws . Context ( ) . Done ( ) :
2018-04-09 17:44:00 +00:00
util . Close ( stream )
2018-04-06 20:08:29 +00:00
case <- done :
}
return nil
}
2018-04-06 15:27:51 +00:00
func ( s * Server ) getApplicationDestination ( ctx context . Context , name string ) ( string , string , error ) {
2018-06-12 17:43:16 +00:00
a , err := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . Get ( name , metav1 . GetOptions { } )
2018-04-06 15:27:51 +00:00
if err != nil {
return "" , "" , err
}
2018-06-12 17:43:16 +00:00
server , namespace := a . Spec . Destination . Server , a . Spec . Destination . Namespace
2018-05-08 21:09:33 +00:00
return server , namespace , nil
2018-04-06 15:27:51 +00:00
}
2018-03-16 23:17:03 +00:00
func ( s * Server ) getRepo ( ctx context . Context , repoURL string ) * appv1 . Repository {
2018-05-14 18:36:08 +00:00
repo , err := s . db . GetRepository ( ctx , repoURL )
2018-03-16 23:17:03 +00:00
if err != nil {
// If we couldn't retrieve from the repo service, assume public repositories
repo = & appv1 . Repository { Repo : repoURL }
}
return repo
}
2018-05-11 18:50:32 +00:00
// Sync syncs an application to its target state
func ( s * Server ) Sync ( ctx context . Context , syncReq * ApplicationSyncRequest ) ( * appv1 . Application , error ) {
2018-09-11 21:28:53 +00:00
appIf := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns )
a , err := appIf . Get ( * syncReq . Name , metav1 . GetOptions { } )
2018-06-12 17:43:16 +00:00
if err != nil {
return nil , err
}
if ! s . enf . EnforceClaims ( ctx . Value ( "claims" ) , "applications" , "sync" , appRBACName ( * a ) ) {
2018-06-06 04:44:14 +00:00
return nil , grpc . ErrPermissionDenied
}
2018-09-11 21:28:53 +00:00
if a . Spec . SyncPolicy != nil && a . Spec . SyncPolicy . Automated != nil {
if syncReq . Revision != "" && syncReq . Revision != a . Spec . Source . TargetRevision {
return nil , status . Errorf ( codes . FailedPrecondition , "Cannot sync to %s: auto-sync currently set to %s" , syncReq . Revision , a . Spec . Source . TargetRevision )
}
}
2018-09-24 15:52:43 +00:00
parameterOverrides := make ( appv1 . ParameterOverrides , 0 )
if syncReq . Parameter != nil {
// If parameter overrides are supplied, the caller explicitly states to use the provided
// list of overrides. NOTE: gogo/protobuf cannot currently distinguish between empty arrays
// vs nil arrays, which is why the wrapping syncReq.Parameter is examined for intent.
// See: https://github.com/gogo/protobuf/issues/181
for _ , p := range syncReq . Parameter . Overrides {
parameterOverrides = append ( parameterOverrides , appv1 . ComponentParameter {
Name : p . Name ,
Value : p . Value ,
Component : p . Component ,
} )
}
} else {
// If parameter overrides are omitted completely, we use what is set in the application
if a . Spec . Source . ComponentParameterOverrides != nil {
parameterOverrides = appv1 . ParameterOverrides ( a . Spec . Source . ComponentParameterOverrides )
}
}
2018-09-11 21:28:53 +00:00
op := appv1 . Operation {
Sync : & appv1 . SyncOperation {
2018-09-24 15:52:43 +00:00
Revision : syncReq . Revision ,
Prune : syncReq . Prune ,
DryRun : syncReq . DryRun ,
SyncStrategy : syncReq . Strategy ,
ParameterOverrides : parameterOverrides ,
2018-10-10 17:12:20 +00:00
Resources : syncReq . Resources ,
2018-09-11 21:28:53 +00:00
} ,
}
2018-09-24 15:52:43 +00:00
a , err = argo . SetAppOperation ( ctx , appIf , s . auditLogger , * syncReq . Name , & op )
if err == nil {
rev := syncReq . Revision
if syncReq . Revision == "" {
rev = a . Spec . Source . TargetRevision
}
message := fmt . Sprintf ( "initiated sync to %s" , rev )
s . logEvent ( a , ctx , argo . EventReasonOperationStarted , message )
}
return a , err
2018-05-11 18:50:32 +00:00
}
2018-03-07 22:34:43 +00:00
2018-05-11 18:50:32 +00:00
func ( s * Server ) Rollback ( ctx context . Context , rollbackReq * ApplicationRollbackRequest ) ( * appv1 . Application , error ) {
2018-09-11 21:28:53 +00:00
appIf := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns )
a , err := appIf . Get ( * rollbackReq . Name , metav1 . GetOptions { } )
2018-06-12 17:43:16 +00:00
if err != nil {
return nil , err
}
2018-09-21 22:25:08 +00:00
if ! s . enf . EnforceClaims ( ctx . Value ( "claims" ) , "applications" , "sync" , appRBACName ( * a ) ) {
2018-06-06 04:44:14 +00:00
return nil , grpc . ErrPermissionDenied
}
2018-09-11 21:28:53 +00:00
if a . Spec . SyncPolicy != nil && a . Spec . SyncPolicy . Automated != nil {
return nil , status . Errorf ( codes . FailedPrecondition , "Rollback cannot be initiated when auto-sync is enabled" )
}
op := appv1 . Operation {
Rollback : & appv1 . RollbackOperation {
ID : rollbackReq . ID ,
Prune : rollbackReq . Prune ,
DryRun : rollbackReq . DryRun ,
} ,
2018-02-28 11:07:56 +00:00
}
2018-09-24 15:52:43 +00:00
a , err = argo . SetAppOperation ( ctx , appIf , s . auditLogger , * rollbackReq . Name , & op )
if err == nil {
s . logEvent ( a , ctx , argo . EventReasonOperationStarted , fmt . Sprintf ( "initiated rollback to %d" , rollbackReq . ID ) )
}
return a , err
2018-02-28 11:07:56 +00:00
}
2018-07-14 00:13:31 +00:00
func ( s * Server ) TerminateOperation ( ctx context . Context , termOpReq * OperationTerminateRequest ) ( * OperationTerminateResponse , error ) {
a , err := s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . Get ( * termOpReq . Name , metav1 . GetOptions { } )
if err != nil {
return nil , err
}
2018-09-21 22:25:08 +00:00
if ! s . enf . EnforceClaims ( ctx . Value ( "claims" ) , "applications" , "sync" , appRBACName ( * a ) ) {
2018-07-14 00:13:31 +00:00
return nil , grpc . ErrPermissionDenied
}
for i := 0 ; i < 10 ; i ++ {
if a . Operation == nil || a . Status . OperationState == nil {
return nil , status . Errorf ( codes . InvalidArgument , "Unable to terminate operation. No operation is in progress" )
}
a . Status . OperationState . Phase = appv1 . OperationTerminating
_ , err = s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . Update ( a )
if err == nil {
return & OperationTerminateResponse { } , nil
}
if ! apierr . IsConflict ( err ) {
return nil , err
}
2018-09-27 18:52:08 +00:00
log . Warnf ( "Failed to set operation for app '%s' due to update conflict. Retrying again..." , * termOpReq . Name )
2018-07-14 00:13:31 +00:00
time . Sleep ( 100 * time . Millisecond )
a , err = s . appclientset . ArgoprojV1alpha1 ( ) . Applications ( s . ns ) . Get ( * termOpReq . Name , metav1 . GetOptions { } )
if err != nil {
return nil , err
2018-07-24 15:48:13 +00:00
} else {
2018-09-24 15:52:43 +00:00
s . logEvent ( a , ctx , argo . EventReasonResourceUpdated , "terminated running operation" )
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
func ( s * Server ) logEvent ( a * appv1 . Application , ctx context . Context , reason string , action string ) {
2018-09-24 15:52:43 +00:00
eventInfo := argo . EventInfo { Type : v1 . EventTypeNormal , Reason : reason }
user := session . Username ( ctx )
if user == "" {
user = "Unknown user"
}
message := fmt . Sprintf ( "%s %s" , user , action )
s . auditLogger . LogAppEvent ( a , eventInfo , message )
2018-07-24 15:48:13 +00:00
}