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

Changing the mechanism that loads the plugins into memory to consider the type of the plugin in addition to its name #5737

Merged
merged 5 commits into from Jan 30, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -210,4 +210,12 @@ ValidatedPlugin validatePluginByName(String name, PluggableProviderService servi
* @throws ProviderLoaderException
*/
public PluginMetadata getPluginMetadata(String service, String provider) throws ProviderLoaderException;

/**
* Register a plugin into map using type and name as key to load it when requested
* @param type
* @param name
* @param beanName
*/
void registerPlugin(String type, String name, String beanName);
}
9 changes: 6 additions & 3 deletions rundeckapp/grails-app/conf/spring/resources.groovy
Expand Up @@ -31,15 +31,18 @@ import com.dtolabs.rundeck.core.plugins.ScriptPluginScanner
import com.dtolabs.rundeck.core.storage.AuthRundeckStorageTree
import com.dtolabs.rundeck.core.storage.StorageTreeFactory
import com.dtolabs.rundeck.core.utils.GrailsServiceInjectorJobListener
import com.dtolabs.rundeck.plugins.ServiceNameConstants
import com.dtolabs.rundeck.server.plugins.PluginCustomizer
import com.dtolabs.rundeck.server.plugins.RundeckEmbeddedPluginExtractor
import com.dtolabs.rundeck.server.plugins.RundeckPluginRegistry
import com.dtolabs.rundeck.server.plugins.fileupload.FSFileUploadPlugin
import com.dtolabs.rundeck.server.plugins.loader.ApplicationContextPluginFileSource
import com.dtolabs.rundeck.server.plugins.logging.*
import com.dtolabs.rundeck.server.plugins.logs.*
import com.dtolabs.rundeck.server.plugins.logstorage.TreeExecutionFileStoragePlugin
import com.dtolabs.rundeck.server.plugins.logstorage.TreeExecutionFileStoragePluginFactory
import com.dtolabs.rundeck.server.plugins.services.*
import com.dtolabs.rundeck.server.plugins.storage.DbStoragePlugin
import com.dtolabs.rundeck.server.plugins.storage.DbStoragePluginFactory
import grails.plugin.springsecurity.SpringSecurityUtils
import groovy.io.FileType
Expand Down Expand Up @@ -389,15 +392,15 @@ beans={
}
}
dbStoragePluginFactory(DbStoragePluginFactory)
pluginRegistry['db']='dbStoragePluginFactory'
pluginRegistry[ServiceNameConstants.Storage + ':' + DbStoragePlugin.PROVIDER_NAME]='dbStoragePluginFactory'
storageTreeExecutionFileStoragePluginFactory(TreeExecutionFileStoragePluginFactory)
pluginRegistry['storage-tree'] = 'storageTreeExecutionFileStoragePluginFactory'
pluginRegistry[ServiceNameConstants.ExecutionFileStorage + ":" + TreeExecutionFileStoragePlugin.PROVIDER_NAME] = 'storageTreeExecutionFileStoragePluginFactory'

def uploadsDir = new File(varDir, 'upload')
fsFileUploadPlugin(FSFileUploadPlugin) {
basePath = uploadsDir.absolutePath
}
pluginRegistry['filesystem-temp'] = 'fsFileUploadPlugin'
pluginRegistry[ServiceNameConstants.FileUpload + ":" +FSFileUploadPlugin.PROVIDER_NAME] = 'fsFileUploadPlugin'

//list of plugin classes to generate factory beans for
[
Expand Down
Expand Up @@ -24,6 +24,7 @@ import com.dtolabs.rundeck.core.execution.service.ProviderLoaderException
import com.dtolabs.rundeck.core.plugins.CloseableProvider
import com.dtolabs.rundeck.core.plugins.ConfiguredPlugin
import com.dtolabs.rundeck.core.plugins.DescribedPlugin
import com.dtolabs.rundeck.core.plugins.Plugin
import com.dtolabs.rundeck.core.plugins.PluginMetadata
import com.dtolabs.rundeck.core.plugins.PluginRegistry
import com.dtolabs.rundeck.core.plugins.PluginResourceLoader
Expand Down Expand Up @@ -88,6 +89,10 @@ class RundeckPluginRegistry implements ApplicationContextAware, PluginRegistry,
log.debug(it)
}
}

void registerPlugin(String type, String name, String beanName) {
pluginRegistryMap.putIfAbsent(type + ":" + name, beanName)
}

String createServiceName(final String simpleName) {
if (simpleName.endsWith("Plugin")) {
Expand Down Expand Up @@ -337,7 +342,7 @@ class RundeckPluginRegistry implements ApplicationContextAware, PluginRegistry,
PluggableProviderService<T> service
)
{
DescribedPlugin<T> beanPlugin = loadBeanDescriptor(name)
DescribedPlugin<T> beanPlugin = loadBeanDescriptor(name, service.name)
if (null != beanPlugin) {
return new CloseableDescribedPlugin<T>(beanPlugin)
}
Expand Down Expand Up @@ -374,7 +379,7 @@ class RundeckPluginRegistry implements ApplicationContextAware, PluginRegistry,
* @return DescribedPlugin, or null if it cannot be loaded
*/
public <T> DescribedPlugin<T> loadPluginDescriptorByName(String name, PluggableProviderService<T> service) {
DescribedPlugin<T> beanPlugin = loadBeanDescriptor(name)
DescribedPlugin<T> beanPlugin = loadBeanDescriptor(name, service.name)
if (null != beanPlugin) {
return beanPlugin
}
Expand Down Expand Up @@ -405,15 +410,20 @@ class RundeckPluginRegistry implements ApplicationContextAware, PluginRegistry,
null
}

private <T> DescribedPlugin<T> loadBeanDescriptor(String name) {
private <T> DescribedPlugin<T> loadBeanDescriptor(String name, String type = null) {
try {
def beanName = pluginRegistryMap[name]
def beanName = pluginRegistryMap["${type}:${name}"] ?: pluginRegistryMap[name]
if (beanName) {
def bean = findBean(beanName)
if (bean instanceof PluginBuilder) {
bean = ((PluginBuilder) bean).buildPlugin()
}
//try to check annotations

final Plugin annotation1 = bean.getClass().getAnnotation(Plugin.class);
if (type && annotation1 && annotation1.service() != type) {
return null
}

Description desc = null
if (bean instanceof Describable) {
desc = ((Describable) bean).description
Expand All @@ -425,7 +435,7 @@ class RundeckPluginRegistry implements ApplicationContextAware, PluginRegistry,
} catch (NoSuchBeanDefinitionException e) {
log.error("plugin Spring bean does not exist: ${name}")
}
null
return null
}

def registerDynamicPluginBean(String beanName, ApplicationContext context){
Expand Down
Expand Up @@ -31,11 +31,12 @@ import java.nio.file.Files
* @author greg
* @since 2/15/17
*/
@Plugin(name = 'filesystem-temp', service = ServiceNameConstants.FileUpload)
@Plugin(name = FSFileUploadPlugin.PROVIDER_NAME, service = ServiceNameConstants.FileUpload)
@PluginDescription(title = 'Temporary File',
description = 'Stores uploaded files temporarily on the file system for the duration of the execution.')
@ToString(includeNames = true)
class FSFileUploadPlugin implements FileUploadPlugin {
static final String PROVIDER_NAME = 'filesystem-temp'

@PluginProperty(
title = 'Base Path',
Expand Down
Expand Up @@ -48,7 +48,7 @@ class PluginFactoryBean<T> implements FactoryBean<T>, InitializingBean, BeanName
void afterPropertiesSet() throws Exception {
def annotation = objectType.getAnnotation(Plugin)
if (annotation) {
rundeckPluginRegistry.pluginRegistryMap.putIfAbsent(annotation.name(), beanName)
rundeckPluginRegistry.registerPlugin(annotation.service(), annotation.name(), beanName)
}
}

Expand Down
Expand Up @@ -40,12 +40,14 @@ import org.rundeck.storage.data.DataUtil
* @since 2/14/17
*/

@Plugin(name = 'storage-tree', service = ServiceNameConstants.ExecutionFileStorage)
@Plugin(name = TreeExecutionFileStoragePlugin.PROVIDER_NAME, service = ServiceNameConstants.ExecutionFileStorage)
@PluginDescription(title = 'Tree Storage',
description = 'Uses the configured Tree Storage backend for log file storage.')
@ToString(includeNames = true)
class TreeExecutionFileStoragePlugin
implements ExecutionFileStoragePlugin, ExecutionMultiFileStorage, ExecutionFileStorageOptions {
static final String PROVIDER_NAME = 'storage-tree'

@PluginProperty(title = 'Base Path', description = 'Top-level root path for storage', defaultValue = '/logs')
String basePath = '/logs';

Expand Down
Expand Up @@ -32,9 +32,11 @@ import org.rundeck.storage.impl.DelegateTree
* @author Greg Schueler <a href="mailto:greg@simplifyops.com">greg@simplifyops.com</a>
* @since 2014-04-03
*/
@Plugin(name = 'db', service = ServiceNameConstants.Storage)
@Plugin(name = DbStoragePlugin.PROVIDER_NAME, service = ServiceNameConstants.Storage)
@PluginDescription(title = 'DB Storage', description = 'Uses DB as storage layer.')
class DbStoragePlugin extends DelegateTree<ResourceMeta> implements StoragePlugin{
static final PROVIDER_NAME = 'db'

@PluginProperty(title = 'Namespace', description = 'Namespace for storage')
String namespace;

Expand Down
Expand Up @@ -7,6 +7,7 @@ import com.dtolabs.rundeck.core.common.ProjectManager
import com.dtolabs.rundeck.core.common.PropertyRetriever
import com.dtolabs.rundeck.core.execution.service.NodeExecutor
import com.dtolabs.rundeck.core.plugins.PluggableProviderService
import com.dtolabs.rundeck.core.plugins.Plugin
import com.dtolabs.rundeck.core.plugins.ServiceProviderLoader
import com.dtolabs.rundeck.core.plugins.configuration.Configurable
import com.dtolabs.rundeck.core.plugins.configuration.ConfigurationException
Expand Down Expand Up @@ -61,31 +62,6 @@ class RundeckPluginRegistrySpec extends Specification implements GrailsUnitTest

}

static class TestPlugin2 implements Configurable, Describable {
Properties configuration
Description description

@Override
void configure(final Properties configuration) throws ConfigurationException {
this.configuration = configuration
}
}

static class TestBuilder implements PluginBuilder<TestPlugin2> {
TestPlugin2 instance

@Override
TestPlugin2 buildPlugin() {
return instance
}


@Override
Class<TestPlugin2> getPluginClass() {
TestPlugin2
}
}

@Unroll
def "configure plugin by name with different property scopes"() {
given:
Expand Down Expand Up @@ -137,6 +113,78 @@ class RundeckPluginRegistrySpec extends Specification implements GrailsUnitTest

}

@Unroll
def "load plugin by name"() {
given:
def description1 = DescriptionBuilder.builder()
.name('plugin1')
.property(PropertyBuilder.builder().string('prop1').build())
.property(PropertyBuilder.builder().string('prop2').build())
.build()

def testPlugin1 = new TestPluginWithAnnotation()
testPlugin1.description = description1

def description2 = DescriptionBuilder.builder()
.name('plugin2')
.property(PropertyBuilder.builder().string('prop1').build())
.property(PropertyBuilder.builder().string('prop2').build())
.build()

def testPlugin2 = new TestPluginWithAnnotation()
testPlugin2.description = description2

def description3 = DescriptionBuilder.builder()
.name('plugin1')
.property(PropertyBuilder.builder().string('prop1').build())
.property(PropertyBuilder.builder().string('prop2').build())
.build()

def testPlugin3 = new TestPluginWithAnnotation2()
testPlugin3.description = description3

Map pluginsMap = [:]
pluginsMap[testPlugin1.description.name] = testPlugin1
pluginsMap[testPlugin2.description.name] = testPlugin2
pluginsMap['plugin3'] = testPlugin3

def beanBuilder1 = new TestBuilder2(instance: testPlugin1)
def beanBuilder3 = new TestBuilder2(instance: testPlugin3)

defineBeans {
testBeanBuilder(InstanceFactoryBean, beanBuilder1)
testBeanBuilder3(InstanceFactoryBean, beanBuilder3)
}
def sut = new RundeckPluginRegistry()
sut.pluginDirectory = File.createTempDir('test', 'dir')
sut.applicationContext = applicationContext
sut.pluginRegistryMap = ['plugin1': 'testBeanBuilder', 'otherservice:plugin1': 'testBeanBuilder3']
sut.rundeckServerServiceProviderLoader = Mock(ServiceProviderLoader)

def svc = Mock(PluggableProviderService) {
getName() >> servicename
providerOfType("plugin2") >> testPlugin2
}

when:
def result = sut.loadPluginDescriptorByName(pluginname, svc)

then:
result
result.name == pluginname
result.instance == pluginsMap[pluginKey]

where:
provider = 'aplugin'
project = 'AProject'

pluginname | servicename | pluginKey
'plugin1' | 'aservicename' | 'plugin1'
'plugin2' | 'otherservicename' | 'plugin2'
'plugin1' | 'otherservice' | 'plugin3'

}

@Unroll
def "validate plugin by name with different property scopes"() {
given:
Expand Down Expand Up @@ -195,4 +243,66 @@ class RundeckPluginRegistrySpec extends Specification implements GrailsUnitTest
return map.get(name);
}
}

static class TestPlugin2 implements Configurable, Describable {
Properties configuration
Description description

@Override
void configure(final Properties configuration) throws ConfigurationException {
this.configuration = configuration
}
}

static class TestBuilder implements PluginBuilder<TestPlugin2> {
TestPlugin2 instance

@Override
TestPlugin2 buildPlugin() {
return instance
}


@Override
Class<TestPlugin2> getPluginClass() {
TestPlugin2
}
}

@Plugin(service = "aservicename", name = 'providername')
static class TestPluginWithAnnotation implements Configurable, Describable {
Properties configuration
Description description

@Override
void configure(final Properties configuration) throws ConfigurationException {
this.configuration = configuration
}
}

@Plugin(service = "otherservice", name = 'providername')
static class TestPluginWithAnnotation2 extends TestPluginWithAnnotation implements Configurable, Describable {
Properties configuration
Description description

@Override
void configure(final Properties configuration) throws ConfigurationException {
this.configuration = configuration
}
}

static class TestBuilder2 implements PluginBuilder<TestPluginWithAnnotation> {
TestPluginWithAnnotation instance

@Override
TestPluginWithAnnotation buildPlugin() {
return instance
}


@Override
Class<TestPluginWithAnnotation> getPluginClass() {
TestPluginWithAnnotation
}
}
}