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 }