Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for plugin name in CMPv2 #11341

Merged
merged 6 commits into from
Dec 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"}
}).
crenshaw-dev marked this conversation as resolved.
Show resolved Hide resolved
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)
crenshaw-dev marked this conversation as resolved.
Show resolved Hide resolved
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)
crenshaw-dev marked this conversation as resolved.
Show resolved Hide resolved
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
}