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

[JENKINS-42091] Add JSON steps #22

Merged
merged 8 commits into from Mar 7, 2017
Merged

[JENKINS-42091] Add JSON steps #22

merged 8 commits into from Mar 7, 2017

Conversation

nfalco79
Copy link
Member

@nfalco79 nfalco79 commented Feb 22, 2017

The first implementation add a step to read JSON from text or file.
It adds also unit test but with the actual pom.xml there is no way that is work on any OS (Windows 7/MacOS/Linux CentoOS). I have test and write tests with an updated pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.jenkins-ci.plugins</groupId>
        <artifactId>plugin</artifactId>
        <version>2.21</version>
    </parent>
    <artifactId>pipeline-utility-steps</artifactId>
    <packaging>hpi</packaging>
    <version>1.2.3-SNAPSHOT</version>
    <name>Pipeline Utility Steps</name>
    <url>https://wiki.jenkins-ci.org/display/JENKINS/Pipeline+Utility+Steps+Plugin</url>

    <licenses>
        <license>
            <name>MIT License</name>
            <url>http://opensource.org/licenses/MIT</url>
        </license>
    </licenses>

    <developers>
        <developer>
            <id>rsandell</id>
            <name>Robert Sandell</name>
            <email>rsandell@cloudbees.com</email>
            <roles>
                <role>maintainer</role>
            </roles>
        </developer>
    </developers>

    <properties>
        <jenkins.version>1.651.3</jenkins.version>
        <workflow.version>1.11</workflow.version>
        <findbugs.failOnError>false</findbugs.failOnError>
    </properties>

    <repositories>
        <repository>
            <id>repo.jenkins-ci.org</id>
            <url>https://repo.jenkins-ci.org/public/</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>org.jenkins-ci.plugins</groupId>
            <artifactId>script-security</artifactId>
            <version>1.17</version>
        </dependency>
        <dependency>
            <groupId>org.jenkins-ci.plugins.workflow</groupId>
            <artifactId>workflow-step-api</artifactId>
            <version>${workflow.version}</version>
        </dependency>
        <dependency>
            <groupId>org.jenkins-ci.plugins.workflow</groupId>
            <artifactId>workflow-cps</artifactId>
            <version>${workflow.version}</version>
        </dependency>
        <dependency>
            <groupId>org.jenkins-ci.plugins.workflow</groupId>
            <artifactId>workflow-aggregator</artifactId>
            <version>${workflow.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jenkins-ci.plugins.workflow</groupId>
            <artifactId>workflow-aggregator</artifactId>
            <classifier>tests</classifier>
            <version>${workflow.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.jenkins-ci.plugins.workflow</groupId>
            <artifactId>workflow-step-api</artifactId>
            <classifier>tests</classifier>
            <version>${workflow.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-model</artifactId>
            <version>3.3.9</version>
        </dependency>
        <!-- Remove ClassNotFoundException: org.jenkinsci.main.modules.sshd.SshCommandFactory in Junit tests -->
		<dependency>
			<groupId>org.jenkins-ci.modules</groupId>
			<artifactId>sshd</artifactId>
			<version>1.7</version>
			<scope>test</scope>
		</dependency>
    </dependencies>

    <distributionManagement>
        <repository>
            <id>maven.jenkins-ci.org</id>
            <url>https://repo.jenkins-ci.org/releases</url>
        </repository>
    </distributionManagement>

    <scm>
        <connection>scm:git:git://github.com/jenkinsci/pipeline-utility-steps-plugin.git</connection>
        <developerConnection>scm:git:git@github.com:jenkinsci/pipeline-utility-steps-plugin.git</developerConnection>
        <url>https://github.com/jenkinsci/pipeline-utility-steps-plugin</url>
        <tag>HEAD</tag>
    </scm>

    <pluginRepositories>
        <pluginRepository>
            <id>repo.jenkins-ci.org</id>
            <url>https://repo.jenkins-ci.org/public/</url>
        </pluginRepository>
    </pluginRepositories>
</project>

@nfalco79 nfalco79 changed the title [JENKINS-42091] Add JSON steps to read and write [JENKINS-42091] Add step to read JSON Feb 22, 2017
@nfalco79
Copy link
Member Author

Seems that only Cloudbees setup has no problem with current pom.xml and the createOnlineSlave statement of JenkinsRules.
In all environments I have run maven build the creation of slave blocks the maven process indefinitely.

} else if (f.isDirectory()) {
logger.print("warning: ");
logger.print(f.getRemote());
logger.println(" is a directory, omitting from properties gathering");
Copy link
Member

Choose a reason for hiding this comment

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

properties gathering?

Copy link
Member Author

Choose a reason for hiding this comment

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

copy / paste error. Noted

}

@Extension
public static class JSONWhiteLister extends Whitelist {
Copy link
Member

Choose a reason for hiding this comment

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

It's better to contribute this whitelisting to script-security-plugin

Copy link
Member Author

@nfalco79 nfalco79 Feb 22, 2017

Choose a reason for hiding this comment

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

This whitelist only permit the basic isArray and size methods needed to navigate json object in pipeline. The json implementation I decide to use (instead GJSON or similar) is because is shipped by default with Jenkins.
In script-security-plugin I see only JVM or groovy library packages so is the right place for net.sf.json? Question because the step readMavenPOM have a more permissive whitelist. Are you moving all custom whitelist to that plugin?

Copy link
Member

Choose a reason for hiding this comment

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

net.sf.json is part of Jenkins core, that's why they belong there. The whitelisting of the maven parts in this plugin is because it is this plugin that adds the dependency.

Copy link
Member Author

Choose a reason for hiding this comment

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

ok I will create a PR to that plugin refering this discussion.

PrintStream logger = listener.getLogger();
JSON json = new JSONObject();

JsonSlurper jsonSlurper = new JsonSlurper();
Copy link
Member

Choose a reason for hiding this comment

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

Why are you using JsonSlurper? Normally you would just call JSONObject.fromObject(jsonString).

Copy link
Member Author

Choose a reason for hiding this comment

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

because I do not know if json is a json array or a json object.
By default I setup json as empty json object {} than JsonSlurper instantiates the correct JSONObject or JSONArray.
JsonSluper provides a default jsonConfig and parse from inputstream.

content.put("tags", tags);
File file = temp.newFile();
try (FileWriter f = new FileWriter(file)) {
new JsonBuilder(content).writeTo(f);
Copy link
Member

Choose a reason for hiding this comment

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

content.write(f) no need for JsonBuilder.

Copy link
Member Author

Choose a reason for hiding this comment

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

Noted

@nfalco79
Copy link
Member Author

nfalco79 commented Feb 22, 2017

Test will not pass until this pr is accepted and released

@nfalco79
Copy link
Member Author

Since the custom @Extension of Whitelist no longer exists, it makes no sense to test it here.

If you agree I could proceed to implement writeJSON step

@rsandell
Copy link
Member

Regarding the pom file problems; have you setup your settings.xml as described here?

@nfalco79
Copy link
Member Author

nfalco79 commented Feb 23, 2017

No but in the pom.xml there are both repository and pluginrepository so the same of settings.
If you update parent you don't need anymore define local plugins because are already defined in parent 2.21
The main issue is that Jenkins core 1.609 is too old and did not run any test.
With 1.651.3 work only local pipeline node() {}, no jenkins client otherwise test hangs (node('client') {})

@nfalco79
Copy link
Member Author

nfalco79 commented Feb 27, 2017

I've add writeJSON step, remove JsonSlurper as requested.
I've also move validation checks to step executor like does the others utility steps (exect readproperties).
Move some UI messages to the messages.properties to be ready for i18n.

If you agree I could also make a single squash commit

Enable i18n for UI messages.
Move validation checks to StepExecution instead on the step definition.
@rsandell
Copy link
Member

rsandell commented Feb 28, 2017

script-security-plugin #109 was released in script-security-1.27 so you can probably bump the dependency to make the tests pass If you want to tests those bits.

if (step.getJson() == null) {
throw new IllegalArgumentException(Messages.WriteJSONStepExecution_missingJSON(step.getDescriptor().getFunctionName()));
}
if (StringUtils.isBlank(step.getFile())) {
Copy link
Member

Choose a reason for hiding this comment

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

Since you do require both arguments to be set they should both be in the @DataboundConstructor.

Copy link
Member Author

Choose a reason for hiding this comment

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

right, I miss due I've add json later.

/*
* The MIT License (MIT)
*
* Copyright (c) 2016 CloudBees Inc.
Copy link
Member

Choose a reason for hiding this comment

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

Do you work for CloudBees? If not you shouldn't claim their copyright.

Copy link
Member

Choose a reason for hiding this comment

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

I didn't comment on the copyright of the other step's files since they where about 90% copy & paste so I deemed that to be correct.

Copy link
Member Author

Choose a reason for hiding this comment

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

I do not work for CloudBees so what I could wrote? Should I have to remove Copyright row?

Copy link
Member

Choose a reason for hiding this comment

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

Either you own the copyright or the company you work for.

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<f:block>
<p>This is a special step. No snippet generation available. See inline help for more information.</p>
Copy link
Member

Choose a reason for hiding this comment

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

I think you can make the snippet generator work here since you are using standard data bound fields.

Copy link
Member Author

Choose a reason for hiding this comment

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

this means that I can remove both config.jelly and help.html files?

Copy link
Member

Choose a reason for hiding this comment

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

No, you should change config.jelly to contain a proper config page for the databound fields. And change the help file to more explain to a human what the step actually does.

Copy link
Member Author

Choose a reason for hiding this comment

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

Snippet generator is not able to handle correctly complex type like net.sf.json.JSON (interface) in data bound fields. So I leave the same as maven step does where the help explain how to use by example.

Instead readJSON config.jelly was changed to support snippet generator also with optional fields. The same could be do for readProperties and readManifest.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, sorry, I was stupid.

WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition(
"node {\n" +
" def json = readJSON file: '" + separatorsToSystemEscaped(file.getAbsolutePath()) + "'\n" +
Copy link
Member

Choose a reason for hiding this comment

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

I think we need to test that non system escaped separators works. Java should translate some/path to some\path on windows without us needing to do anything. If that doesn't work then we need to fix that in the step, the users shouldn't have to do that.

It's one of the main points of this plugin to be platform agnostic.

Copy link
Member Author

Choose a reason for hiding this comment

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

ok, consider that all yaml, properties, maven, manifest (where I copy test methodology) use same escape function. I will change it

Copy link
Member

Choose a reason for hiding this comment

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

Yes I was a bit out of touch when I approved those changes. I think the problem they solved was that on windows the path would be 'some\path' when calling file.getAbsolutePath() and that caused issues.
What I should have argued in that case was to make sure the format was always `some/path'.
So keep it like it is in here and I'll fix it in another PR at a later date.

Move pipeline-security version to 1.27.
Mark fields of writeJSON step as required.
Reduce use of real file in test case.
@nfalco79
Copy link
Member Author

Fix copyright header.
Move pipeline-security version to 1.27.
Mark fields of writeJSON step as required.
Reduce use of real file in test case and use path separator /.
Remove config.jelly to use automatic ?snippet generator?

Let me squash commits if pull request could be considered approved by you

@nfalco79 nfalco79 changed the title [JENKINS-42091] Add step to read JSON [JENKINS-42091] Add JSON steps Mar 1, 2017
Copy link
Member

@rsandell rsandell left a comment

Choose a reason for hiding this comment

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

Missing config.jelly with data bindings fir writeJSON

@nfalco79
Copy link
Member Author

nfalco79 commented Mar 5, 2017

The Snippet generator does not handle correctly any FormException("message", "fieldName"), the only checked exception of newInstance(StaplerRequest req, JSONObject formData).

I would notice that in Jenkins 1.x any exception server side is showed in the snippet text area as trackstrace instead in Jenkins 2.x no error is shown at all. I would expect that form exception message was placed near the form exception field.

@rsandell
Copy link
Member

rsandell commented Mar 6, 2017

Ah, yes. I had a temporary lapse in stupidness, sorry :)

Copy link
Member

@rsandell rsandell left a comment

Choose a reason for hiding this comment

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

Seems to suffer from more copy paste problems.

FilePath f = ws.child(step.getFile());
if (f.exists() && !f.isDirectory()) {
try (InputStream is = f.read()) {
json = JSONSerializer.toJSON(IOUtils.toString(is));
Copy link
Member

Choose a reason for hiding this comment

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

You can return the object here directly, since you don't seem to try to do any override (even if your help text below at times indicates you do)

Copy link
Member Author

Choose a reason for hiding this comment

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

I could but personally I do not like too much exit point, when is possible I prefer this way

SonarQube open a violation when >= 5 return in a method

Copy link
Member

Choose a reason for hiding this comment

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

OK, it's just that this behaves a bit differently then from what is stated, if both file and text is set (which is blocked in newInstance but can happen by other means) it would read the file and then overwrite the val with what is in text. Nothing wrong with that, just that the code doesn't fully reflect what is expected.

Copy link
Member Author

@nfalco79 nfalco79 Mar 7, 2017

Choose a reason for hiding this comment

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

If both fields are set an IllegalArgumentException will be throw at the begin of this method

}
}
if (!isBlank(step.getText())) {
json = JSONSerializer.toJSON(step.getText().trim());
Copy link
Member

Choose a reason for hiding this comment

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

Same here

if (path.isDirectory()) {
throw new FileNotFoundException(Messages.JSONStepExecution_fileIsDirectory(path.getRemote()));
}

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps a nice thing to do would be to warn if you are overwriting an existing file.

Copy link
Member Author

Choose a reason for hiding this comment

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

Typicall scenario of writeJSON is read json file and manipulate its data, add other data and update the same file (like maven step does).
For example autoincrement version: beta3 -> beta4 of a package.json
If I put a warn it's very annoing in console log.

I could add a boolean property override to step, but this could be a show stopper in case devops change the pipeline and/or developer would provide a base JSON file to be enhance with writeJSON step, etc etc.

Copy link
Member

Choose a reason for hiding this comment

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

ok

~ SOFTWARE.
-->
<p>
Path to a file in the workspace to read the properties from.
Copy link
Member

Choose a reason for hiding this comment

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

Properties?

-->
<p>
Path to a file in the workspace to read the properties from.
<em>These are added to the resulting map after the defaults and so will overwrite any key/value pairs already present.</em>
Copy link
Member

Choose a reason for hiding this comment

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

map?

~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
~ SOFTWARE.
-->
<p>A JSON object data, written as name/value pairs. Values must be
Copy link
Member

Choose a reason for hiding this comment

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

JSON data is normally structured and I don't see any indications of you limiting that structure to be name/value pairs.
Shouldn't just "A string containing JSON formatted data" or similar be enough?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes

<p>
<strong>Example:</strong><br/>
<code><pre>
def props = readJSON file: 'dir/input.json', text: '{ other: Override }'
Copy link
Member

Choose a reason for hiding this comment

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

In help-text.html and help-file.html you state that file or text can be used, not both at the same time. Yet this example uses both.
Also I don't think the text will parse since Override isn't surrounded by quotes.

The use of Override is also a bit confusing, since the other steps in this plugin that uses it to exeplify how to override data in the file. So it's better to use some other string to not further confuse the user.

Copy link
Member Author

Choose a reason for hiding this comment

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

the example is wrong, too many copy/paste in late hours

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<f:block>
<p>This is a special step. No snippet generation available. See inline help for more information.</p>
Copy link
Member

Choose a reason for hiding this comment

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

Yes, sorry, I was stupid.

@rsandell rsandell merged commit 9e19d2d into jenkinsci:master Mar 7, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants