Skip to content
This repository has been archived by the owner on Dec 15, 2021. It is now read-only.

[JENKINS-26135] Allow CPS global lib scripts to define global variables #131

Merged
merged 47 commits into from
Aug 20, 2015

Conversation

kohsuke
Copy link
Member

@kohsuke kohsuke commented May 12, 2015

This allows the release engineering team to define higher-level organization specific workflow DSLs.

This is critical to use workflow as "simplified text-based UX". Such DSLs could be for example:

acmeCorp {
    id = '123'
    devEnvironment = 'qa-svr-1'
}

(where let's say there's an inventory of application ID in the central database describing where the repository is, and 'qa-svr-1' refers to the Tomcat server where the app gets deployed at the end.)

@reviewbybees

This allows the release engineering team to define higher-level
organization specific workflow DSLs.

This is critical to use workflow as "simplified text-based UX".
Such DSLs could be for example:

acmeCorp {
    id = '123'
    devEnvironment = 'qa-svr-1'
}

(where let's say there's an inventory of application ID in the
central database describing where the repository is, and 'qa-svr-1'
refers to the Tomcat server where the app gets deployed at the end.)
@kohsuke
Copy link
Member Author

kohsuke commented May 12, 2015

I could have avoided registering/unregistering a new Extension on the fly if GlobalVariable supports a collection of global variables, not just one.

It looks like GlobalVariable isn't released yet, so maybe you are open to changing it.

@oleg-nenashev
Copy link
Member

  • I'm aware that UserDefinedGlobalVariableList takes all scripts in ${Workspace}/src folder. It means that you cannot place any other Groovy script there, so it may cause a conflict. Probably you could get scripts from somewhere like ${Workspace}/src/globalVars/ or use a predefined wildcard
  • One script can contribute only one variable. I suppose it's not user-friendly in the use-case of "I retrieve an bulk info from a remote component and want to save it to 25 variables" (e.g. SCM stats, Docker server info, etc.)

@jglick
Copy link
Member

jglick commented May 12, 2015

[INFO] Possible null pointer dereference in new org.jenkinsci.plugins.workflow.cps.global.UserDefinedGlobalVariableList() due to return value of called method [org.jenkinsci.plugins.workflow.cps.global.UserDefinedGlobalVariableList, org.jenkinsci.plugins.workflow.cps.global.UserDefinedGlobalVariableList] Dereferenced at UserDefinedGlobalVariableList.java:[line 29]Known null at UserDefinedGlobalVariableList.java:[line 29]
[INFO] Possible null pointer dereference in org.jenkinsci.plugins.workflow.cps.global.UserDefinedGlobalVariableList.init() due to return value of called method [org.jenkinsci.plugins.workflow.cps.global.UserDefinedGlobalVariableList, org.jenkinsci.plugins.workflow.cps.global.UserDefinedGlobalVariableList] Dereferenced at UserDefinedGlobalVariableList.java:[line 69]Known null at UserDefinedGlobalVariableList.java:[line 69]

@jglick
Copy link
Member

jglick commented May 12, 2015

It looks like GlobalVariable isn't released yet

Well, it is in 1.7-alpha-1, but I think it is fine to change its signature if that makes this easier.

* @author Kohsuke Kawaguchi
*/
// not @Extension because these are manually registered
public class UserDefinedGlobalVariable extends GlobalVariable {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need not be public I guess.

@jglick
Copy link
Member

jglick commented May 12, 2015

As with @oleg-nenashev I am a little concerned about overloading $JENKINS_HOME/workflow-libs/src/, which is already used for classes you intend to import, in a package structure. Would seem safer to use a separate subdirectory for this RFE that emphasizes that it should contain files in a flat structure (no packages) and that the file name becomes a variable. IIRC the reason we decided to use the src/ subdirectory in 1.0 for this plugin was exactly so that we could compatibly introduce other subdirectories for different purposes.

@jglick
Copy link
Member

jglick commented May 12, 2015

BTW you should add a CHANGES.md entry.

@@ -0,0 +1,4 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core">
<j:out value="${it.helpHtml}"/>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Careful—this is user content emitted raw. It would be safer to use MarkupFormatter, or restrict to plain text. Admittedly it currently requires RUN_SCRIPTS to push to this repo, but see JENKINS-26538.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All right, I'll do MarkupFormatter and probably name those files as just *.txt.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(resolved)

@jglick
Copy link
Member

jglick commented May 12, 2015

I retrieve an bulk info from a remote component and want to save it to 25 variables

I guess this use case, if it ever comes up, could be treated as an additional feature—so long as we use distinctive subdirectory names.

Other than the comments above this looks good to me.

BTW have you checked that this functionality is not already possible without any change? Since src/ is already in the script “classpath”, it seems that Groovy might have interpreted references to someVar.someMethod as static calls to a class in the default package. But maybe you then run aground trying to access an implicit CpsScript (for steps or other global variables) from a static context.

@jglick
Copy link
Member

jglick commented May 12, 2015

As an aside, the consistent feedback I hear from everyone I talk to about Workflow scripts is that they want to keep all sources in their own Git repositories (or other SCM), and thus would not use this plugin. Not blocking this PR but we should be considering how similar features could be built using external SCMs.

@kohsuke
Copy link
Member Author

kohsuke commented May 12, 2015

  • I thought about possibility of name conflicts, and putting those global variables into a fixed package or a separate directory (like ${globalLibs}/vars or something like that.) But a full day conversations with users the other day made me feel that this is probably the most common use case for CPS global libs that I will be pushing to users, so occupying the root package felt justifiable. It also felt natural; if you write org.acme.foo then it maps to src/org/acme/foo.groovy, so if it's just acme, one would expect that it would map to src/foo.groovy. I'll wait for others to chime in.
  • GlobalVariable is unsuitable for dynamically listed variables like that, but I don't think that's an issue --- certainly not a problem in this PR. It's not really meant to be used for simple values, it's meant to be used more like a namespace.

@jglick
Copy link
Member

jglick commented May 12, 2015

It also felt natural; if you write org.acme.foo then it maps to src/org/acme/foo.groovy, so if it's just acme, one would expect that it would map to src/foo.groovy.

Yet these behave in subtly different ways. One creates a class definition, one a variable. Just worried that this is adding yet another mine to the field; see the explanation of explicit vs. implicit classes in cps-global-lib/README.md. (BTW this PR should include a patch to that page to document the new feature.)

Do not feel strongly about it, but I still find it clearer to use a separate directory namespace for a distinct feature.

It's not really meant to be used for simple values, it's meant to be used more like a namespace.

Not sure I followed that. Anyway this PR should do something to clean up the interaction with GlobalVariable—whether changing its API, or introducing a new “friend API” in workflow-cps that GlobalVariable would implement as a special case. Dynamic modifications to ExtensionList (and new @Initializers) are never acceptable unless there is no compatible alternative.

@kohsuke
Copy link
Member Author

kohsuke commented May 12, 2015

they want to keep all sources in their own Git repositories (or other SCM)

People using another Git repository as the source of truth is anticipated and well supported. They just need to "deploy" the changes by pushing it into this repo, which is the same thing Heroku and others do.

Using other SCMs would indeed go beyond the capability of this repository. Maybe we can split cps-globa-libs into the part that just assumes populated $JENKINS_HOME/workflowLibs and another that makes that a Git repository. In that way we leave options open for a future expansion that does SCM.checkout().

@jglick
Copy link
Member

jglick commented May 12, 2015

In that way we leave options open for a future expansion that does SCM.checkout().

Better to do nothing until we implement that future expansion and see exactly what it requires. I suspect that it should work like, or even as a part of, CpsScmScriptDefinition: when the build starts, multiple repos are checked out and added to the “classpath”.

@jglick
Copy link
Member

jglick commented May 13, 2015

Are you still working on this?

@kohsuke
Copy link
Member Author

kohsuke commented May 14, 2015

For the past two days I've been away. My understanding based on the conversation here is that the following tasks remain:

  • Define top-level vars dir and pick scripts from there, not from src.
  • Tweak GlobalVariable API to get rid of dynamic extension list manipulation.
  • Doc updates.

@jglick
Copy link
Member

jglick commented May 14, 2015

Define top-level vars dir

Your judgment call; I am only -0 on the current placement.

@jglick
Copy link
Member

jglick commented May 14, 2015

BTW I took the liberty of moving your list into a GH tasklist for easier tracking.

@jglick
Copy link
Member

jglick commented May 15, 2015

Just remembered JENKINS-26135, which requests automatically loaded global functions (opposed to variables) here. Seems like it would not be hard to generalize this a bit to allow you to hook into CpsScript.invokeMethod as well as .getProperty, right? Or perhaps you can just define a variable whose value is actually a Closure; Groovy would then let you call it like a function, I think (but needs to be proven in a test).

@kohsuke kohsuke changed the title Allow CPS global lib scripts to define global variables. [WiP] Allow CPS global lib scripts to define global variables. May 22, 2015
@kohsuke
Copy link
Member Author

kohsuke commented May 22, 2015

Good point about JENKINS-26135. I think I should be able to solve it at the same time.

… changing list of GlobalVariables.

To make the common case easy, GlobalVariable is still left as a separate extension point. To reduce the error prone-ness of listing all variables,
I added GlobalVariable.ALL.
... to prevent XSS vulnerability from attackers who can define scripts.
MarkupFormatterDescriptor should declare the desired extension, so that
if Jenkins is configured for a specific MarkupFormatter, such as
MarkDown, then we can support it.

Until that day, better to stick to plain text.
Global variable should be usable as a function, just like in Groovy
Closure property can be called like a function.
@jglick
Copy link
Member

jglick commented Aug 19, 2015

🐝

z.checkOutFrom(repo)
```

### Defining global functions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the use cases of this seem to be a friendlier syntax for workflow-compatible but not explicitly workflow-supporting plugins that would use generic steps. Maybe have a specific example of that here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, for me the syntax simplification for the generic step is a problem on its own. You are right that this could be a workaround while we solve that, but not sure if we want to advertise it in README.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right that is a separate (filed) issue, not really related to this PR.

@daniel-beck
Copy link
Member

What happens when a file name such as foo.bar.groovy is used? It defines a variable that is listed but cannot actually be accessed?

@daniel-beck
Copy link
Member

🐛 until the foo.bar question is answered, and the documentation for the help file is improved.

@kohsuke
Copy link
Member Author

kohsuke commented Aug 19, 2015

foo.bar is an invalid class name, and so you will not be able to load a script from foo.bar.groovy.

@jglick
Copy link
Member

jglick commented Aug 20, 2015

@kohsuke you closed the repository for com.cloudbees.groovy-cps:1.5 but you did not release it (I did so just now), so I cannot yet push the change that switches to non-SNAPSHOT dependencies. Will do that before merge, once it makes its way to Central. (BTW it would be great if the CI builder could pick up https://oss.sonatype.org/content/groups/public/ so that we do not need to wait several hours for Central propagation after releasing libraries.)

@daniel-beck
Copy link
Member

foo.bar is an invalid class name, and so you will not be able to load a script from foo.bar.groovy.

@kohsuke It looks like it would show up in the list though when it should not.


The `vars` directory hosts scripts that define global variables accessible from
workflow scripts.
The basename of each `*.groovy` file should be a Groovy (~ Java) identifier, conventionally `camelCased`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

must, if you want it to work.

@jglick
Copy link
Member

jglick commented Aug 20, 2015

It looks like it would show up in the [list?]

It would, but oh well. Just do not do that.

@daniel-beck
Copy link
Member

Right. Good enough I suppose.

🐝

The `vars` directory hosts scripts that define global variables accessible from
workflow scripts.
The basename of each `*.groovy` file should be a Groovy (~ Java) identifier, conventionally `camelCased`.
The matching `*.txt`, if present, can contain documentation, processed through the system’s configured markup formatter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems bad - as this can be changed independently and then this would affect the help rendering here.
I guess no different to all the job descriptions though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the same problem applies to any saved markup I am afraid.

The forced *.txt extension is ugly but MarkupFormatterDescriptor lacks an API for specifying a conventional file extension.

@jtnord
Copy link
Member

jtnord commented Aug 20, 2015

🐝 couldn't see anything obviously wrong.

File help = source(".txt");
if (!help.exists()) return null;

return Jenkins.getActiveInstance().getMarkupFormatter().translate(FileUtils.readFileToString(help, Charsets.UTF_8));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume there's a default Markup Formatter ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is @Nonnull.

@tfennelly
Copy link
Member

🐝

Insofar as I get it. Only potential problem I saw was the potential NPE in UserDefinedGlobalVariable, which I'm betting is not really possible anyway.

jglick added a commit that referenced this pull request Aug 20, 2015
[JENKINS-26135] Allow CPS global lib scripts to define global variables
@jglick jglick merged commit 28c9600 into master Aug 20, 2015
@jglick jglick deleted the user-defined-global-libs branch August 20, 2015 14:01
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants