github.com/argoproj/argo-cd/v3@v3.2.1/util/app/discovery/discovery.go (about)

     1  package discovery
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/golang/protobuf/ptypes/empty"
    12  
    13  	"github.com/argoproj/argo-cd/v3/util/io/files"
    14  
    15  	grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/retry"
    16  	log "github.com/sirupsen/logrus"
    17  
    18  	pluginclient "github.com/argoproj/argo-cd/v3/cmpserver/apiclient"
    19  	"github.com/argoproj/argo-cd/v3/common"
    20  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    21  	"github.com/argoproj/argo-cd/v3/util/cmp"
    22  	utilio "github.com/argoproj/argo-cd/v3/util/io"
    23  	"github.com/argoproj/argo-cd/v3/util/kustomize"
    24  )
    25  
    26  func IsManifestGenerationEnabled(sourceType v1alpha1.ApplicationSourceType, enableGenerateManifests map[string]bool) bool {
    27  	if enableGenerateManifests == nil {
    28  		return true
    29  	}
    30  	enabled, ok := enableGenerateManifests[string(sourceType)]
    31  	if !ok {
    32  		return true
    33  	}
    34  	return enabled
    35  }
    36  
    37  func Discover(ctx context.Context, appPath, repoPath string, enableGenerateManifests map[string]bool, tarExcludedGlobs []string, env []string) (map[string]string, error) {
    38  	apps := make(map[string]string)
    39  
    40  	// Check if it is CMP
    41  	conn, _, err := DetectConfigManagementPlugin(ctx, appPath, repoPath, "", env, tarExcludedGlobs)
    42  	if err == nil {
    43  		// Found CMP
    44  		utilio.Close(conn)
    45  
    46  		apps["."] = string(v1alpha1.ApplicationSourceTypePlugin)
    47  		return apps, nil
    48  	}
    49  
    50  	err = filepath.Walk(appPath, func(path string, info os.FileInfo, err error) error {
    51  		if err != nil {
    52  			return err
    53  		}
    54  		if info.IsDir() {
    55  			return nil
    56  		}
    57  		dir, err := filepath.Rel(appPath, filepath.Dir(path))
    58  		if err != nil {
    59  			return err
    60  		}
    61  		base := filepath.Base(path)
    62  		if strings.HasSuffix(base, "Chart.yaml") && IsManifestGenerationEnabled(v1alpha1.ApplicationSourceTypeHelm, enableGenerateManifests) {
    63  			apps[dir] = string(v1alpha1.ApplicationSourceTypeHelm)
    64  		}
    65  		if kustomize.IsKustomization(base) && IsManifestGenerationEnabled(v1alpha1.ApplicationSourceTypeKustomize, enableGenerateManifests) {
    66  			apps[dir] = string(v1alpha1.ApplicationSourceTypeKustomize)
    67  		}
    68  		return nil
    69  	})
    70  	return apps, err
    71  }
    72  
    73  func AppType(ctx context.Context, appPath, repoPath string, enableGenerateManifests map[string]bool, tarExcludedGlobs []string, env []string) (string, error) {
    74  	apps, err := Discover(ctx, appPath, repoPath, enableGenerateManifests, tarExcludedGlobs, env)
    75  	if err != nil {
    76  		return "", err
    77  	}
    78  	appType, ok := apps["."]
    79  	if ok {
    80  		return appType, nil
    81  	}
    82  	return "Directory", nil
    83  }
    84  
    85  // if pluginName is provided setup connection to that cmp-server
    86  // else
    87  // list all plugins in /plugins folder and foreach plugin
    88  // check cmpSupports()
    89  // if supported return conn for the cmp-server
    90  
    91  func DetectConfigManagementPlugin(ctx context.Context, appPath, repoPath, pluginName string, env []string, tarExcludedGlobs []string) (utilio.Closer, pluginclient.ConfigManagementPluginServiceClient, error) {
    92  	var conn utilio.Closer
    93  	var cmpClient pluginclient.ConfigManagementPluginServiceClient
    94  	var connFound bool
    95  
    96  	pluginSockFilePath := common.GetPluginSockFilePath()
    97  	log.WithFields(log.Fields{
    98  		common.SecurityField:    common.SecurityLow,
    99  		common.SecurityCWEField: common.SecurityCWEMissingReleaseOfFileDescriptor,
   100  	}).Debugf("pluginSockFilePath is: %s", pluginSockFilePath)
   101  
   102  	if pluginName != "" {
   103  		// check if the given plugin supports the repo
   104  		conn, cmpClient, connFound = cmpSupports(ctx, pluginSockFilePath, appPath, repoPath, fmt.Sprintf("%v.sock", pluginName), env, tarExcludedGlobs, true)
   105  		if !connFound {
   106  			return nil, nil, fmt.Errorf("could not find cmp-server plugin with name %q supporting the given repository", pluginName)
   107  		}
   108  	} else {
   109  		fileList, err := os.ReadDir(pluginSockFilePath)
   110  		if err != nil {
   111  			return nil, nil, fmt.Errorf("failed to list all plugins in dir: %w", err)
   112  		}
   113  		for _, file := range fileList {
   114  			if file.Type() == os.ModeSocket {
   115  				conn, cmpClient, connFound = cmpSupports(ctx, pluginSockFilePath, appPath, repoPath, file.Name(), env, tarExcludedGlobs, false)
   116  				if connFound {
   117  					break
   118  				}
   119  			}
   120  		}
   121  		if !connFound {
   122  			return nil, nil, errors.New("could not find plugin supporting the given repository")
   123  		}
   124  	}
   125  	return conn, cmpClient, nil
   126  }
   127  
   128  // matchRepositoryCMP will send the repoPath to the cmp-server. The cmp-server will
   129  // inspect the files and return true if the repo is supported for manifest generation.
   130  // Will return false otherwise.
   131  func matchRepositoryCMP(ctx context.Context, appPath, repoPath string, client pluginclient.ConfigManagementPluginServiceClient, env []string, tarExcludedGlobs []string) (bool, bool, error) {
   132  	matchRepoStream, err := client.MatchRepository(ctx, grpc_retry.Disable())
   133  	if err != nil {
   134  		return false, false, fmt.Errorf("error getting stream client: %w", err)
   135  	}
   136  
   137  	err = cmp.SendRepoStream(ctx, appPath, repoPath, matchRepoStream, env, tarExcludedGlobs)
   138  	if err != nil {
   139  		return false, false, fmt.Errorf("error sending stream: %w", err)
   140  	}
   141  	resp, err := matchRepoStream.CloseAndRecv()
   142  	if err != nil {
   143  		return false, false, fmt.Errorf("error receiving stream response: %w", err)
   144  	}
   145  	return resp.GetIsSupported(), resp.GetIsDiscoveryEnabled(), nil
   146  }
   147  
   148  func cmpSupports(ctx context.Context, pluginSockFilePath, appPath, repoPath, fileName string, env []string, tarExcludedGlobs []string, namedPlugin bool) (utilio.Closer, pluginclient.ConfigManagementPluginServiceClient, bool) {
   149  	absPluginSockFilePath, err := filepath.Abs(pluginSockFilePath)
   150  	if err != nil {
   151  		log.Errorf("error getting absolute path for plugin socket dir %v, %v", pluginSockFilePath, err)
   152  		return nil, nil, false
   153  	}
   154  	address := filepath.Join(absPluginSockFilePath, fileName)
   155  	if !files.Inbound(address, absPluginSockFilePath) {
   156  		log.Errorf("invalid socket file path, %v is outside plugin socket dir %v", fileName, pluginSockFilePath)
   157  		return nil, nil, false
   158  	}
   159  
   160  	cmpclientset := pluginclient.NewConfigManagementPluginClientSet(address)
   161  
   162  	conn, cmpClient, err := cmpclientset.NewConfigManagementPluginClient()
   163  	if err != nil {
   164  		log.WithFields(log.Fields{
   165  			common.SecurityField:    common.SecurityMedium,
   166  			common.SecurityCWEField: common.SecurityCWEMissingReleaseOfFileDescriptor,
   167  		}).Errorf("error dialing to cmp-server for plugin %s, %v", fileName, err)
   168  		return nil, nil, false
   169  	}
   170  
   171  	cfg, err := cmpClient.CheckPluginConfiguration(ctx, &empty.Empty{})
   172  	if err != nil {
   173  		log.Errorf("error checking plugin configuration %s, %v", fileName, err)
   174  		utilio.Close(conn)
   175  		return nil, nil, false
   176  	}
   177  
   178  	if !cfg.IsDiscoveryConfigured {
   179  		// If discovery isn't configured but the plugin is named, then the plugin supports the repo.
   180  		if namedPlugin {
   181  			return conn, cmpClient, true
   182  		}
   183  		utilio.Close(conn)
   184  		return nil, nil, false
   185  	}
   186  
   187  	isSupported, isDiscoveryEnabled, err := matchRepositoryCMP(ctx, appPath, repoPath, cmpClient, env, tarExcludedGlobs)
   188  	if err != nil {
   189  		log.WithFields(log.Fields{
   190  			common.SecurityField:    common.SecurityMedium,
   191  			common.SecurityCWEField: common.SecurityCWEMissingReleaseOfFileDescriptor,
   192  		}).Errorf("repository %s is not the match because %v", repoPath, err)
   193  		utilio.Close(conn)
   194  		return nil, nil, false
   195  	}
   196  
   197  	if !isSupported {
   198  		// if discovery is not set and the plugin name is specified, let app use the plugin
   199  		if !isDiscoveryEnabled && namedPlugin {
   200  			return conn, cmpClient, true
   201  		}
   202  		log.WithFields(log.Fields{
   203  			common.SecurityField:    common.SecurityLow,
   204  			common.SecurityCWEField: common.SecurityCWEMissingReleaseOfFileDescriptor,
   205  		}).Debugf("Response from socket file %s does not support %v", fileName, repoPath)
   206  		utilio.Close(conn)
   207  		return nil, nil, false
   208  	}
   209  	return conn, cmpClient, true
   210  }