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
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions docs/STEPS.md
Expand Up @@ -12,6 +12,8 @@
* `readProperties` - Read [java properties](https://docs.oracle.com/javase/7/docs/api/java/util/Properties.html) from files in the workspace or text. ([help](../src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/conf/ReadPropertiesStep/help.html))
* `readManifest` - Read a [Jar Manifest](https://docs.oracle.com/javase/7/docs/technotes/guides/jar/jar.html#JAR_Manifest). ([help](../src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/conf/mf/ReadManifestStep/help.html))
* `readYaml` - Read [YAML](http://yaml.org) from files in the workspace or text. ([help](../src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/conf/ReadYamlStep/help.html))
* `readJSON` - Read [JSON](http://www.json.org/json-it.html) from files in the workspace or text. ([help](../src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/json/ReadJSONStep/help.html))
* `writeJSON` - Write a [JSON](http://www.json.org/json-it.html) object to a files in the workspace. ([help](../src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/json/WriteJSONStep/help.html))

#### Maven Projects
* `readMavenPom` - Read a [Maven Project](https://maven.apache.org/pom.html) into a [Model](http://maven.apache.org/components/ref/3.3.9/maven-model/apidocs/org/apache/maven/model/Model.html) data structure. ([help](../src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/maven/ReadMavenPomStep/help.html))
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -71,7 +71,7 @@
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>script-security</artifactId>
<version>1.17</version>
<version>1.27</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
Expand Down
@@ -0,0 +1,120 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016 Nikolas Falco
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.jenkinsci.plugins.pipeline.utility.steps.json;

import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
import org.jenkinsci.plugins.workflow.steps.Step;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.StaplerRequest;

import hudson.Extension;
import hudson.Util;
import net.sf.json.JSONObject;

/**
* Reads a JSON file from the workspace.
*
* @author Nikolas Falco
*/
public class ReadJSONStep extends AbstractStepImpl {

private String file;
private String text;

@DataBoundConstructor
public ReadJSONStep() {
}

/**
* The path to a file in the workspace to read JSON content from.
*
* @return the file path
*/
public String getFile() {
return file;
}

/**
* The path to a file in the workspace to read JSON content from.
*
* @param file the path to file in the workspace
*/
@DataBoundSetter
public void setFile(String file) {
this.file = Util.fixEmpty(file);
}

/**
* A String containing JSON formatted data.
*
* @return text to parse
*/
public String getText() {
return text;
}

/**
* A String containing JSON formatted data.
*
* @param text to parse
*/
@DataBoundSetter
public void setText(String text) {
this.text = Util.fixEmpty(text);
}

@Extension
public static class DescriptorImpl extends AbstractStepDescriptorImpl {

public DescriptorImpl() {
super(ReadJSONStepExecution.class);
}

@Override
public String getFunctionName() {
return "readJSON";
}

@Override
public String getDisplayName() {
return Messages.ReadJSONStep_DescriptorImpl_displayName();
}

@Override
public Step newInstance(StaplerRequest req, JSONObject formData) throws FormException {
String file = formData.getString("file");
String text = formData.getString("text");
if (StringUtils.isNotBlank(file) && StringUtils.isNotBlank(text)) {
// seems that FormException is not handled correctly, it is not shown at all client side
throw new FormException(Messages.ReadJSONStepExecution_tooManyArguments(getFunctionName()), "text");
}

return super.newInstance(req, formData);
}
}

}
@@ -0,0 +1,85 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016 Nikolas Falco
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package org.jenkinsci.plugins.pipeline.utility.steps.json;

import static org.apache.commons.lang.StringUtils.isBlank;
import static org.apache.commons.lang.StringUtils.isNotBlank;
import java.io.FileNotFoundException;
import java.io.InputStream;
import javax.inject.Inject;

import org.apache.commons.io.IOUtils;
import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousNonBlockingStepExecution;
import org.jenkinsci.plugins.workflow.steps.StepContextParameter;

import hudson.FilePath;
import net.sf.json.JSON;
import net.sf.json.JSONSerializer;

/**
* Execution of {@link ReadJSONStep}.
*
* @author Nikolas Falco
*/
public class ReadJSONStepExecution extends AbstractSynchronousNonBlockingStepExecution<JSON> {

private static final long serialVersionUID = 1L;

@StepContextParameter
private transient FilePath ws;

@Inject
private transient ReadJSONStep step;

@Override
protected JSON run() throws Exception {
String fName = step.getDescriptor().getFunctionName();
if (isNotBlank(step.getFile()) && isNotBlank(step.getText())) {
throw new IllegalArgumentException(Messages.ReadJSONStepExecution_tooManyArguments(fName));
}
if (isBlank(step.getFile()) && isBlank(step.getText())) {
throw new IllegalArgumentException(Messages.ReadJSONStepExecution_missingRequiredArgument(fName));
}

JSON json = null;
if (!isBlank(step.getFile())) {
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

}
} else if (f.isDirectory()) {
throw new IllegalArgumentException(Messages.JSONStepExecution_fileIsDirectory(f.getRemote()));
} else if (!f.exists()) {
throw new FileNotFoundException(Messages.JSONStepExecution_fileNotFound(f.getRemote()));
}
}
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

}

return json;
}
}
@@ -0,0 +1,86 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016 Nikolas Falco
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.jenkinsci.plugins.pipeline.utility.steps.json;

import org.jenkinsci.plugins.workflow.steps.AbstractStepDescriptorImpl;
import org.jenkinsci.plugins.workflow.steps.AbstractStepImpl;
import org.kohsuke.stapler.DataBoundConstructor;
import hudson.Extension;
import hudson.Util;
import net.sf.json.JSON;

/**
* Writes a {@link JSON} object to file in the current working directory.
*
* @author Nikolas Falco
*/
public class WriteJSONStep extends AbstractStepImpl {

private final String file;
private final JSON json;

@DataBoundConstructor
public WriteJSONStep(String file, JSON json) {
this.file = Util.fixNull(file);
this.json = json;
}

/**
* Returns the name of the file to write.
*
* @return the file name
*/
public String getFile() {
return file;
}

/**
* Return the JSON object to save.
*
* @return a {@link JSON} object
*/
public JSON getJson() {
return json;
}

@Extension
public static class DescriptorImpl extends AbstractStepDescriptorImpl {

public DescriptorImpl() {
super(WriteJSONStepExecution.class);
}

@Override
public String getFunctionName() {
return "writeJSON";
}

@Override
public String getDisplayName() {
return Messages.WriteJSONStep_DescriptorImpl_displayName();
}

}

}
@@ -0,0 +1,73 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2016 Nikolas Falco
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package org.jenkinsci.plugins.pipeline.utility.steps.json;

import java.io.FileNotFoundException;
import java.io.OutputStreamWriter;

import javax.inject.Inject;

import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousNonBlockingStepExecution;
import org.jenkinsci.plugins.workflow.steps.StepContextParameter;

import hudson.FilePath;

/**
* Execution of {@link WriteJSONStep}.
*
* @author Nikolas Falco
*/
public class WriteJSONStepExecution extends AbstractSynchronousNonBlockingStepExecution<Void> {

private static final long serialVersionUID = 1L;

@StepContextParameter
private transient FilePath ws;

@Inject
private transient WriteJSONStep step;

@Override
protected Void run() throws Exception {
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.

throw new IllegalArgumentException(Messages.WriteJSONStepExecution_missingFile(step.getDescriptor().getFunctionName()));
}

FilePath path = ws.child(step.getFile());
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

try (OutputStreamWriter writer = new OutputStreamWriter(path.write())) {
step.getJson().write(writer);
}
return null;
}

}