Skip to content

Commit

Permalink
feat: add support for plugin name in CMPv2 (#11290) (#11341)
Browse files Browse the repository at this point in the history
* feat: add support for plugin name in cmpV2

Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>

* feat: updated e2e test to use name for CMPv2 plugin

Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>

* docs: updated docs

Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>

* fix: check whether final socket path is inside sock dir or not

Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>

Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>
  • Loading branch information
gdsoumya authored Dec 2, 2022
1 parent 9a90bf8 commit 470ac13
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 67 deletions.
11 changes: 7 additions & 4 deletions docs/user-guide/config-management-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,10 @@ If your CMP is defined in the `argocd-cm` ConfigMap, you can create a new Applic
argocd app create <appName> --config-management-plugin <pluginName>
```

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 `<metadata.name>-<spec.version>` if version is mentioned in the `ConfigManagementPlugin` spec or else just `<metadata.name>`. 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
Expand Down Expand Up @@ -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 `<metadata.name>-<spec.version>`
if version was mentioned in the `ConfigManagementPlugin` spec or else just use `<metadata.name>`. You can also remove the name altogether
and let the automatic discovery to identify the plugin.

## Debugging a CMP

Expand Down
34 changes: 21 additions & 13 deletions reposerver/repository/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 `<metadata.name>-<spec.version>` or just `<metadata.name>` 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())
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -1625,15 +1629,15 @@ 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 {
return nil, err
}

// 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
}
Expand Down Expand Up @@ -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)
}
Expand Down
21 changes: 21 additions & 0 deletions test/e2e/custom_tool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <plugin name>-<version>
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"
Expand Down
120 changes: 70 additions & 50 deletions util/app/discovery/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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)
Expand Down Expand Up @@ -77,67 +79,47 @@ 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{
common.SecurityField: common.SecurityLow,
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
Expand All @@ -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
}

0 comments on commit 470ac13

Please sign in to comment.