diff --git a/docs/user-guide/config-management-plugins.md b/docs/user-guide/config-management-plugins.md index b36e2094028b4..0ef56fc2ce600 100644 --- a/docs/user-guide/config-management-plugins.md +++ b/docs/user-guide/config-management-plugins.md @@ -334,8 +334,10 @@ If your CMP is defined in the `argocd-cm` ConfigMap, you can create a new Applic argocd app create --config-management-plugin ``` -If your plugin is defined as a sidecar, you must manually define the Application manifest. Do not configure a `name` field -in the `plugin` section. The plugin will be automatically matched with the Application based on its discovery rules. +If your CMP is defined as a sidecar, you must manually define the Application manifest. You may leave the `name` field +empty in the `plugin` section for the plugin to be automatically matched with the Application based on its discovery rules. If you do mention the name make sure +it is either `-` if version is mentioned in the `ConfigManagementPlugin` spec or else just ``. When name is explicitly +specified only that particular plugin will be used iff it's discovery pattern/command matches the provided application repo. ```yaml apiVersion: argoproj.io/v1alpha1 @@ -373,8 +375,9 @@ If you don't need to set any environment variables, you can set an empty plugin !!! note Each Application can only have one config management plugin configured at a time. If you're converting an existing - plugin configured through the `argocd-cm` ConfigMap to a sidecar, make sure the discovery mechanism only returns - true for Applications that have had their `name` field in the `plugin` section of their spec removed. + plugin configured through the `argocd-cm` ConfigMap to a sidecar, make sure to update the plugin name to either `-` + if version was mentioned in the `ConfigManagementPlugin` spec or else just use ``. You can also remove the name altogether + and let the automatic discovery to identify the plugin. ## Debugging a CMP diff --git a/reposerver/repository/repository.go b/reposerver/repository/repository.go index dae67eb4412e4..ca2059ae8cf08 100644 --- a/reposerver/repository/repository.go +++ b/reposerver/repository/repository.go @@ -68,7 +68,6 @@ import ( const ( cachedManifestGenerationPrefix = "Manifest generation error (cached)" - pluginNotSupported = "config management plugin not supported." helmDepUpMarkerFile = ".argocd-helm-dep-up" allowConcurrencyFile = ".argocd-allow-concurrency" repoSourceFile = ".argocd-source.yaml" @@ -1045,15 +1044,25 @@ func GenerateManifests(ctx context.Context, appPath, repoRoot, revision string, k := kustomize.NewKustomizeApp(appPath, q.Repo.GetGitCreds(gitCredsStore), repoURL, kustomizeBinary) targetObjs, _, err = k.Build(q.ApplicationSource.Kustomize, q.KustomizeOptions, env) case v1alpha1.ApplicationSourceTypePlugin: + var plugin *v1alpha1.ConfigManagementPlugin if q.ApplicationSource.Plugin != nil && q.ApplicationSource.Plugin.Name != "" { + plugin = findPlugin(q.Plugins, q.ApplicationSource.Plugin.Name) + } + if plugin != nil { + // argocd-cm deprecated plugin is being used + targetObjs, err = runConfigManagementPlugin(appPath, repoRoot, env, q, q.Repo.GetGitCreds(gitCredsStore), plugin) log.WithFields(map[string]interface{}{ "application": q.AppName, "plugin": q.ApplicationSource.Plugin.Name, }).Warnf(common.ConfigMapPluginDeprecationWarning) - - targetObjs, err = runConfigManagementPlugin(appPath, repoRoot, env, q, q.Repo.GetGitCreds(gitCredsStore)) } else { - targetObjs, err = runConfigManagementPluginSidecars(ctx, appPath, repoRoot, env, q, q.Repo.GetGitCreds(gitCredsStore), opt.cmpTarDoneCh, opt.cmpTarExcludedGlobs) + // if the named plugin was not found in argocd-cm try sidecar plugin + pluginName := "" + if q.ApplicationSource.Plugin != nil { + pluginName = q.ApplicationSource.Plugin.Name + } + // if pluginName is provided it has to be `-` or just `` if plugin version is empty + targetObjs, err = runConfigManagementPluginSidecars(ctx, appPath, repoRoot, pluginName, env, q, q.Repo.GetGitCreds(gitCredsStore), opt.cmpTarDoneCh, opt.cmpTarExcludedGlobs) if err != nil { err = fmt.Errorf("plugin sidecar failed. %s", err.Error()) } @@ -1537,12 +1546,7 @@ func findPlugin(plugins []*v1alpha1.ConfigManagementPlugin, name string) *v1alph return nil } -func runConfigManagementPlugin(appPath, repoRoot string, envVars *v1alpha1.Env, q *apiclient.ManifestRequest, creds git.Creds) ([]*unstructured.Unstructured, error) { - plugin := findPlugin(q.Plugins, q.ApplicationSource.Plugin.Name) - if plugin == nil { - return nil, fmt.Errorf(pluginNotSupported+" plugin name %s", q.ApplicationSource.Plugin.Name) - } - +func runConfigManagementPlugin(appPath, repoRoot string, envVars *v1alpha1.Env, q *apiclient.ManifestRequest, creds git.Creds, plugin *v1alpha1.ConfigManagementPlugin) ([]*unstructured.Unstructured, error) { // Plugins can request to lock the complete repository when they need to // use git client operations. if plugin.LockRepo { @@ -1625,7 +1629,7 @@ func getPluginParamEnvs(envVars []string, plugin *v1alpha1.ApplicationSourcePlug return env, nil } -func runConfigManagementPluginSidecars(ctx context.Context, appPath, repoPath string, envVars *v1alpha1.Env, q *apiclient.ManifestRequest, creds git.Creds, tarDoneCh chan<- bool, tarExcludedGlobs []string) ([]*unstructured.Unstructured, error) { +func runConfigManagementPluginSidecars(ctx context.Context, appPath, repoPath, pluginName string, envVars *v1alpha1.Env, q *apiclient.ManifestRequest, creds git.Creds, tarDoneCh chan<- bool, tarExcludedGlobs []string) ([]*unstructured.Unstructured, error) { // compute variables. env, err := getPluginEnvs(envVars, q, creds, true) if err != nil { @@ -1633,7 +1637,7 @@ func runConfigManagementPluginSidecars(ctx context.Context, appPath, repoPath st } // detect config management plugin server (sidecar) - conn, cmpClient, err := discovery.DetectConfigManagementPlugin(ctx, appPath, env, tarExcludedGlobs) + conn, cmpClient, err := discovery.DetectConfigManagementPlugin(ctx, appPath, pluginName, env, tarExcludedGlobs) if err != nil { return nil, err } @@ -1880,8 +1884,12 @@ func populatePluginAppDetails(ctx context.Context, res *apiclient.RepoAppDetails return fmt.Errorf("failed to get env vars for plugin: %w", err) } + pluginName := "" + if q.Source != nil && q.Source.Plugin != nil { + pluginName = q.Source.Plugin.Name + } // detect config management plugin server (sidecar) - conn, cmpClient, err := discovery.DetectConfigManagementPlugin(ctx, appPath, env, tarExcludedGlobs) + conn, cmpClient, err := discovery.DetectConfigManagementPlugin(ctx, appPath, pluginName, env, tarExcludedGlobs) if err != nil { return fmt.Errorf("failed to detect CMP for app: %w", err) } diff --git a/test/e2e/custom_tool_test.go b/test/e2e/custom_tool_test.go index 18b9d5ecfb3e1..a5fffcd50ed58 100644 --- a/test/e2e/custom_tool_test.go +++ b/test/e2e/custom_tool_test.go @@ -240,6 +240,27 @@ func TestCMPDiscoverWithFindGlob(t *testing.T) { Expect(HealthIs(health.HealthStatusHealthy)) } +//Discover by Plugin Name +func TestCMPDiscoverWithPluginName(t *testing.T) { + Given(t). + And(func() { + go startCMPServer("./testdata/cmp-find-glob") + time.Sleep(1 * time.Second) + os.Setenv("ARGOCD_BINARY_NAME", "argocd") + }). + Path("guestbook"). + When(). + CreateFromFile(func(app *Application) { + // specifically mention the plugin to use (name is based on - + app.Spec.Source.Plugin = &ApplicationSourcePlugin{Name: "cmp-find-glob-v1.0"} + }). + Sync(). + Then(). + Expect(OperationPhaseIs(OperationSucceeded)). + Expect(SyncStatusIs(SyncStatusCodeSynced)). + Expect(HealthIs(health.HealthStatusHealthy)) +} + //Discover by Find command func TestCMPDiscoverWithFindCommandWithEnv(t *testing.T) { pluginName := "cmp-find-command" diff --git a/util/app/discovery/discovery.go b/util/app/discovery/discovery.go index 86f94a984545a..ec526dc3a93e7 100644 --- a/util/app/discovery/discovery.go +++ b/util/app/discovery/discovery.go @@ -7,6 +7,8 @@ import ( "path/filepath" "strings" + "github.com/argoproj/argo-cd/v2/util/io/files" + grpc_retry "github.com/grpc-ecosystem/go-grpc-middleware/retry" log "github.com/sirupsen/logrus" @@ -33,7 +35,7 @@ func Discover(ctx context.Context, repoPath string, enableGenerateManifests map[ apps := make(map[string]string) // Check if it is CMP - conn, _, err := DetectConfigManagementPlugin(ctx, repoPath, []string{}, tarExcludedGlobs) + conn, _, err := DetectConfigManagementPlugin(ctx, repoPath, "", []string{}, tarExcludedGlobs) if err == nil { // Found CMP io.Close(conn) @@ -77,14 +79,16 @@ func AppType(ctx context.Context, path string, enableGenerateManifests map[strin return "Directory", nil } -// 1. list all plugins in /plugins folder -// 2. foreach plugin setup connection with respective cmp-server -// 3. check isSupported(path)? -// 4.a if no then close connection -// 4.b if yes then return conn for detected plugin -func DetectConfigManagementPlugin(ctx context.Context, repoPath string, env []string, tarExcludedGlobs []string) (io.Closer, pluginclient.ConfigManagementPluginServiceClient, error) { +// if pluginName is provided setup connection to that cmp-server +// else +// list all plugins in /plugins folder and foreach plugin +// check cmpSupports() +// if supported return conn for the cmp-server + +func DetectConfigManagementPlugin(ctx context.Context, repoPath, pluginName string, env []string, tarExcludedGlobs []string) (io.Closer, pluginclient.ConfigManagementPluginServiceClient, error) { var conn io.Closer var cmpClient pluginclient.ConfigManagementPluginServiceClient + var connFound bool pluginSockFilePath := common.GetPluginSockFilePath() log.WithFields(log.Fields{ @@ -92,52 +96,30 @@ func DetectConfigManagementPlugin(ctx context.Context, repoPath string, env []st common.SecurityCWEField: 775, }).Debugf("pluginSockFilePath is: %s", pluginSockFilePath) - files, err := os.ReadDir(pluginSockFilePath) - if err != nil { - return nil, nil, err - } - - var connFound bool - for _, file := range files { - if file.Type() == os.ModeSocket { - address := filepath.Join(pluginSockFilePath, file.Name()) - cmpclientset := pluginclient.NewConfigManagementPluginClientSet(address) - - conn, cmpClient, err = cmpclientset.NewConfigManagementPluginClient() - if err != nil { - log.WithFields(log.Fields{ - common.SecurityField: common.SecurityMedium, - common.SecurityCWEField: 775, - }).Errorf("error dialing to cmp-server for plugin %s, %v", file.Name(), err) - continue - } - - isSupported, err := matchRepositoryCMP(ctx, repoPath, cmpClient, env, tarExcludedGlobs) - if err != nil { - log.WithFields(log.Fields{ - common.SecurityField: common.SecurityMedium, - common.SecurityCWEField: 775, - }).Errorf("repository %s is not the match because %v", repoPath, err) - continue - } - - if !isSupported { - log.WithFields(log.Fields{ - common.SecurityField: common.SecurityLow, - common.SecurityCWEField: 775, - }).Debugf("Reponse from socket file %s is not supported", file.Name()) - io.Close(conn) - } else { - connFound = true - break + if pluginName != "" { + // check if the given plugin supports the repo + conn, cmpClient, connFound = cmpSupports(ctx, pluginSockFilePath, repoPath, fmt.Sprintf("%v.sock", pluginName), env, tarExcludedGlobs) + if !connFound { + return nil, nil, fmt.Errorf("couldn't find cmp-server plugin with name %v supporting the given repository", pluginName) + } + } else { + fileList, err := os.ReadDir(pluginSockFilePath) + if err != nil { + return nil, nil, fmt.Errorf("Failed to list all plugins in dir, error=%w", err) + } + for _, file := range fileList { + if file.Type() == os.ModeSocket { + conn, cmpClient, connFound = cmpSupports(ctx, pluginSockFilePath, repoPath, file.Name(), env, tarExcludedGlobs) + if connFound { + break + } } } + if !connFound { + return nil, nil, fmt.Errorf("could not find plugin supporting the given repository") + } } - - if !connFound { - return nil, nil, fmt.Errorf("could not find plugin supporting the given repository") - } - return conn, cmpClient, err + return conn, cmpClient, nil } // matchRepositoryCMP will send the repoPath to the cmp-server. The cmp-server will @@ -159,3 +141,41 @@ func matchRepositoryCMP(ctx context.Context, repoPath string, client pluginclien } return resp.GetIsSupported(), nil } + +func cmpSupports(ctx context.Context, pluginSockFilePath, repoPath, fileName string, env []string, tarExcludedGlobs []string) (io.Closer, pluginclient.ConfigManagementPluginServiceClient, bool) { + address := filepath.Join(pluginSockFilePath, fileName) + if !files.Inbound(address, pluginSockFilePath) { + log.Errorf("invalid socket file path, %v is outside plugin socket dir %v", fileName, pluginSockFilePath) + return nil, nil, false + } + + cmpclientset := pluginclient.NewConfigManagementPluginClientSet(address) + + conn, cmpClient, err := cmpclientset.NewConfigManagementPluginClient() + if err != nil { + log.WithFields(log.Fields{ + common.SecurityField: common.SecurityMedium, + common.SecurityCWEField: 775, + }).Errorf("error dialing to cmp-server for plugin %s, %v", fileName, err) + return nil, nil, false + } + + isSupported, err := matchRepositoryCMP(ctx, repoPath, cmpClient, env, tarExcludedGlobs) + if err != nil { + log.WithFields(log.Fields{ + common.SecurityField: common.SecurityMedium, + common.SecurityCWEField: 775, + }).Errorf("repository %s is not the match because %v", repoPath, err) + return nil, nil, false + } + + if !isSupported { + log.WithFields(log.Fields{ + common.SecurityField: common.SecurityLow, + common.SecurityCWEField: 775, + }).Debugf("Reponse from socket file %s is not supported", fileName) + io.Close(conn) + return nil, nil, false + } + return conn, cmpClient, true +}