I want to use the contents of a config file in several ways, including in integration tests and in my BootStrap. If my config file is under src/groovy and is called "com.corp.MyConfig.groovy", what should I pass to the ConfigSlurper parse method?
I guess what happens is that your Groovy file gets compiled and ends up being a class in your binary directory (classpath). Instead of trying to load it via the URL try to load the script class.
Class scriptClass = getClass().classLoader.loadClass('com.corp.MyConfig')
ConfigObject config = new ConfigSlurper().parse(scriptClass)
If your config file is available on the classpath, I would suggest using ClassLoader.getResource() to get it:
URL url = MyClass.class.getClassLoader().getResource("com/corp/MyConfig.groovy");
config = new ConfigSlurper().parse(url);
From a POGO you can also use:
import grails.util.Holders
class Foo {
def bar() {
println(Holders.config.grails.serverURL)
}
}
From: How do I get at the goodies in my Grails Config.groovy at runtime?
Related
I am trying to writing a Jenkins Shared Library for my CI process. I'd like to reference a class that is in the \src folder inside a global function defined in the \vars folder, since it would allow me to put most of the logic in classes instead of in the global functions. I am following the repository structure documented on the official Jenkins documentation:
Jenkins Shared Library structure
Here's a simplified example of what I have:
/src/com/example/SrcClass.groovy
package com.example
class SrcClass {
def aFunction() {
return "Hello from src folder!"
}
}
/vars/classFromVars.groovy
import com.example.SrcClass
def call(args) {
def sc = new SrcClass()
return sc.aFunction()
}
Jenkinsfile
#Library('<lib-name>') _
pipeline {
...
post {
always {
classFromVars()
}
}
}
My goal was for the global classes in the /vars folder to act as a sort of public facade and to use it in my Jenkinsfile as a custom step without having to instantiate a class in a script block (making it compatible with declarative pipelines). It all seems pretty straightforward to me, but I am getting this error when running the classFromVars file:
<root>\vars\classFromVars.groovy: 1: unable to resolve class com.example.SrcClass
# line 1, column 1.
import com.example.SrcClass
^
1 error
I tried running the classFromVars class directly with the groovy CLI locally and on the Jenkins server and I have the same error on both environments. I also tried specifying the classpath when running the /vars script, getting the same error, with the following command:
<root>>groovy -cp <root>\src\com\example vars\classFromVars.groovy
Is what I'm trying to achieve possible? Or should I simply put all of my logic in the /vars class and avoid using the /src folder?
I have found several repositories on GitHub that seem to indicate this is possible, for example this one: https://github.com/fabric8io/fabric8-pipeline-library, which uses the classes in the /src folder in many of the classes in the /vars folder.
As #Szymon Stepniak pointed out, the -cp parameter in my groovy command was incorrect. It now works locally and on the Jenkins server. I have yet to explain why it wasn't working on the Jenkins server though.
I found that when I wanted to import a class from the shared library I have, to a script step in the /vars I needed to do it like this:
//thanks to '_', the classes are imported automatically.
// MUST have the '#' at the beginning, other wise it will not work.
// when not using "#BRANCH" it will use default branch from git repo.
#Library('my-shared-library#BRANCH') _
// only by calling them you can tell if they exist or not.
def exampleObject = new example.GlobalVars()
// then call methods or attributes from the class.
exampleObject.runExample()
in /vars for the folder I have AaConfig.groovy.
In the job's Groovy script I have:
#!groovy
#Library('globals') _
Class config = AaConfig
I can use config instead of AaConfig with no problems in subsequent code.
This is useful because I can call different configuration files: AaConfig, BbConfig, etc. without changing anything because i'm calling them through config
So now I got greedy :-), and wanted to specify the config file externally:
#!groovy
#Library('globals') _
String configName = "AaConfig"
Class config = Class.forName(configName)
Now I get:
java.lang.ClassNotFoundException: AaConfig
Any ideas how to solve this?
If you want to load AaConfig class via reflection then you have to use the same classloader that your pipeline script uses. Class.forName() in this cases uses ClassLoaderForClassArtifacts while Groovy CPS plugin uses CpsGroovyShell$CleanGroovyClassLoader.
Below you can find an example of loading class using pipeline current classloader:
String configName = 'AaConfig'
Class config = this.getClass().getClassLoader().loadClass(configName)
I am using Json views plugin of grails. Which works in development, but when I run it as a jar file, it is not able to find the templates/gson files for rendering. I get the following error
//code
def template = jsonViewTemplateEngine.resolveTemplate(<path to template>)
def writable = template.make(kase: kase)
//exception
Cannot invoke method make() on null object. Stacktrace follows:
java.lang.NullPointerException: Cannot invoke method make() on null object
Json Views we are using are a part of a inline plugin we are developing. Jar we create also runs with that inline plugin (implemented using gradle wrapper)
Any ideas/suggestions?
Environment:
Grails - 3.2.0
Groovy - 2.4.7
Json-Views plugin - 1.1.1
Views are pre-compiled into classes for deployment via Gradle and the compileGsonViews task. This is done using the project.name setting by default.
You will notice in the build/main/gson-classes directory the classes that are produced. For example if your application name is foo you will have classes like foo_book_show_gson.class where the foo_ part is considered the "package" name.
At runtime. The package name to use to resolve views calculated from the info.app.name setting in grails-app/conf/application.yml.
What this means is that if in Gradle your project.name evaluates to foo and the setting in application.yml is also foo then all is well. This is the most common case as typically your application name is the same in both places.
If info.app.name and the Gradle project.name don't match up you can get the problem where views don't resolve.
You have two options to fix this. One is to modify build.gradle to explicitly specify the package name:
compileGsonViews.packageName = 'foo'
Then make sure info.app.name matches that value.
The second option is rename your project directory so that info.app.name and project.name align.
Json View behavior is different in dev mode and war mode.
WritableScriptTemplate template
if (Environment.isDevelopmentEnvironmentAvailable()) {
template = attemptResolvePath(path)
if (template == null) {
template = attemptResolveClass(path)
}
} else {
template = attemptResolveClass(path)
if (template == null) {
template = attemptResolvePath(path)
}
}
if (template == null) {
template = NULL_ENTRY
}
In development mode grails will load your template by file path first then load by class name.
If you are working on case insensitive file system,template file like
'your_template_name' and 'YOUR_TEMPLATE_NAME' will be treated same.
But class name is case sensitive,'your_template_name_class' and 'YOUR_TEMPLATE_NAME_CLASS' is different;
Maybe your template file name is incorrect.
For example, if your classname is:
QueryResult
Your template file should located in:
/views/queryResult/_queryResult.gson
Json View will search class like:
<Project-Name>_queryResult__queryResult_gson
As I am trying to write a Grails Plugin, I stumbled upon two problems:
how do I modify one of the configuration files like Config.groovy or DataSource.groovy from witin the _install.groovy script? It is easy to append something to those files, but how do I modify it in a clean way? text.replaceAll()? Or should I create a new config file?
how do I get the name of the current application into which the plugin will be installed? I tried to use app.name and appName but both do not work.
Is there maybe somewhere a good tutorial on creating plugins which I haven't found yet?
Here is an example of editing configuration files from scripts/_Install.groovy.
My plugin copies three files to the target directory.
.hgignore is used for version control,
DataSource.groovy replaces the default version, and
SecurityConfig.groovy contains extra settings.
I prefer to edit the application's files as little as possible, especially because I expect to change the security setup a few years down the road. I also need to use properties from a jcc-server-config.properties file which is customized for each application server in our system.
Copying the files is easy.
println ('* copying .hgignore ')
ant.copy(file: "${pluginBasedir}/src/samples/.hgignore",
todir: "${basedir}")
println ('* copying SecurityConfig.groovy')
ant.copy(file: "${pluginBasedir}/src/samples/SecurityConfig.groovy",
todir: "${basedir}/grails-app/conf")
println ('* copying DataSource.groovy')
ant.copy(file: "${pluginBasedir}/src/samples/DataSource.groovy",
todir: "${basedir}/grails-app/conf")
The hard part is getting Grails to pick up the new configuration file. To do this, I have to edit the application's grails-app/conf/Config.groovy. I will add two configuration files to be found on the classpath.
println ('* Adding configuration files to grails.config.locations');
// Add configuration files to grails.config.locations.
def newConfigFiles = ["classpath:jcc-server-config.properties",
"classpath:SecurityConfig.groovy"]
// Get the application's Config.groovy file
def cfg = new File("${basedir}/grails-app/conf/Config.groovy");
def cfgText = cfg.text
def appendedText = new StringWriter()
appendedText.println ""
appendedText.println ("// Added by edu-sunyjcc-addons plugin");
// Slurp the configuration so we can look at grails.config.locations.
def config = new ConfigSlurper().parse(cfg.toURL());
// If it isn't defined, create it as a list.
if (config.grails.config.locations.getClass() == groovy.util.ConfigObject) {
appendedText.println('grails.config.locations = []');
} else {
// Don't add configuration files that are already on the list.
newConfigFiles = newConfigFiles.grep {
!config.grails.config.locations.contains(it)
};
}
// Add each surviving location to the list.
newConfigFiles.each {
// The name will have quotes around it...
appendedText.println "grails.config.locations << \"$it\"";
}
// Write the new configuration code to the end of Config.groovy.
cfg.append(appendedText.toString());
The only problem is adding SecurityConfig.groovy to the classpath. I found that you can do that by creating the following event in the plugin's /scripts/Events.groovy.
eventCompileEnd = {
ant.copy(todir:classesDirPath) {
fileset(file:"${basedir}/grails-app/conf/SecurityConfig.groovy")
}
}
Ed.
You might try changing the configuration within the MyNiftyPlugin.groovy file (assuming that your plugin is named my-nifty). I've found that I can change the configuration values within the doWithApplicationContext closure. Here's an example.
def doWithApplicationContext = { applicationContext ->
def config = application.config;
config.edu.mycollege.server.name = 'http://localhost:8080'
config.edu.mycollege.server.instance = 'pprd'
}
The values you enter here do show up in the grailsApplication.config variable at run time. If it works for you, it will be a neater solution, because it doesn't require changes to the client project.
I must qualify that with the fact that I wasn't able to get Spring Security to work by this technique. I believe that my plugin (which depends on Spring Security) was loaded after the security was initialized. I decided to add an extra file to the grails-app/conf directory.
HTH.
For modifying configuration files, you should use ConfigSlurper:
def configParser = new ConfigSlurper(grailsSettings.grailsEnv)
configParser.binding = [userHome: userHome]
def config = configParser.parse(new URL("file:./grails-app/conf/Config.groovy"))
If you need to get application name from script, try:
metadata.'app.name'
Grails 1.3.7
I have some configuration located in an external config file. One of the entires looks like this:
site.maintenance.mode = false
I have a filter which checks for certain config settings for specific URLs. When I do a run-app or deploy a WAR into Tomcat and do:
boolean maintenanceMode = grailsApplication.config.site.maintenance.mode
maintenanceMode is coming back true. If I look at the config object in debug mode, this is what I get:
site={maintenance={mode=false, message="<p>Our trail guides are working hard to get the system back on track.</p><p>We're sorry, the account system is down for maintenance at the moment. We'll get it back online as quickly as we can. Thanks for your patience.</p>"}}
I have a controller that I use to reload this config file dynamically and hitting this controller will fix the issue. But I'm curious as to why it is incorrect on first runs and why the discrepency in what is getting put in the maintenanceMode variable vs what is actually in the config object.
Are you using a Java properties file or a Groovy file? If you're using a properties file then I believe Grails will interpret site.maintenance.mode=false the same way as site.maintenance.mode='false' and since Groovy will interpret:
"false".asBoolean() == true
then that would explain why you would see that initial true value.
I just ran a simple test locally to verify this behavior. When I externalize my properties in a file called test.properties then site.maintenance.mode=false initially gets a boolean value of true, when I use a file called test.groovy then it interprets the boolean value of site.maintenance.mode=false as false. I believe this is because when you use a Groovy file Grails uses ConfigurationSlurper to process it but when you use a properties file Grails interprets everything as String name/value pairs.
What I do is to have an external Config.groovy file, for instance: MyConfig.groovy
At the end of the standard grails Config.groovy file, I have the following:
def ENV_NAME = "MY_EXTERNAL_CONFIG"
if(!grails.config.locations || !(grails.config.locations instanceof List)) {
grails.config.locations = []
}
if(System.getenv(ENV_NAME)) {
grails.config.locations << "file:" + System.getenv(ENV_NAME)
} else if(System.getProperty(ENV_NAME)) {
grails.config.locations << "file:" + System.getProperty(ENV_NAME)
} else {
println "No external Configs found."
}
So now you can have a MyConfig.groovy file anywhere in production environment (for example) and then set an Environment system variable to point to this file (or pass it as parameter to startup.sh), before you start tomcat:
MY_EXTERNAL_CONFIG="/home/tomcat/configs/MyConfig.groovy"
export MY_EXTERNAL_CONFIG
That's it. Now you have an external MyConfig.groovy file. The properties in it are accessible from your grails app as they were part of the standard Config.groovy
import org.codehaus.groovy.grails.commons.*
//...
ConfigurationHolder.config.foo.bar.hello