diff --git a/docs/STEPS.md b/docs/STEPS.md index 31f75f05..692f719d 100644 --- a/docs/STEPS.md +++ b/docs/STEPS.md @@ -27,6 +27,8 @@ * `writeJSON` - Write a [JSON](http://www.json.org/json-it.html) object to a file in the workspace, or to a String. ([help](../src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/json/WriteJSONStep/help.html)) * `readCSV` - Read [CSV](https://commons.apache.org/proper/commons-csv/) from files in the workspace or text. ([help](../src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/csv/ReadCSVStep/help.html)) * `writeCSV` - Write a [CSV](https://commons.apache.org/proper/commons-csv/) file from an object. ([help](../src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/csv/WriteCSVStep/help.html)) +* `readTOML` - Read [TOML](https://toml.io) from a file in the workspace or text. ([help](../src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep/help.html)) +* `writeTOML` - Write a [TOML](https://toml.io) file from an object. ([help](../src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/WriteTOMLStep/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)) diff --git a/pom.xml b/pom.xml index ba6086e8..a8fbf58f 100644 --- a/pom.xml +++ b/pom.xml @@ -162,6 +162,11 @@ commons-csv 1.10.0 + + com.fasterxml.jackson.dataformat + jackson-dataformat-toml + 2.17.2 + diff --git a/src/main/java/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep.java b/src/main/java/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep.java new file mode 100644 index 00000000..fb70dd78 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep.java @@ -0,0 +1,64 @@ +/* + * 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.toml; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; +import org.jenkinsci.plugins.pipeline.utility.steps.AbstractFileOrTextStep; +import org.jenkinsci.plugins.pipeline.utility.steps.AbstractFileOrTextStepDescriptorImpl; +import org.jenkinsci.plugins.workflow.steps.StepContext; +import org.jenkinsci.plugins.workflow.steps.StepExecution; +import org.kohsuke.stapler.DataBoundConstructor; + +/** + * Reads a TOML file from the workspace. + * + */ +public class ReadTOMLStep extends AbstractFileOrTextStep { + + @DataBoundConstructor + public ReadTOMLStep() {} + + @Override + public StepExecution start(StepContext context) throws Exception { + return new ReadTOMLStepExecution(this, context); + } + + @Extension + public static class DescriptorImpl extends AbstractFileOrTextStepDescriptorImpl { + + public DescriptorImpl() {} + + @Override + public String getFunctionName() { + return "readTOML"; + } + + @Override + @NonNull + public String getDisplayName() { + return Messages.ReadTOMLStep_DescriptorImpl_displayName(); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStepExecution.java b/src/main/java/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStepExecution.java new file mode 100644 index 00000000..a7619c52 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStepExecution.java @@ -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.toml; + +import static org.apache.commons.lang.StringUtils.isBlank; +import static org.apache.commons.lang.StringUtils.isNotBlank; + +import com.fasterxml.jackson.dataformat.toml.TomlMapper; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.FilePath; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.IOUtils; +import org.jenkinsci.plugins.pipeline.utility.steps.AbstractFileOrTextStepExecution; +import org.jenkinsci.plugins.workflow.steps.StepContext; + +/** + * Execution of {@link ReadTOMLStep}. + * + */ +public class ReadTOMLStepExecution extends AbstractFileOrTextStepExecution { + private static final long serialVersionUID = 1L; + + private transient ReadTOMLStep step; + + protected ReadTOMLStepExecution(@NonNull ReadTOMLStep step, @NonNull StepContext context) { + super(step, context); + this.step = step; + } + + @Override + protected Object doRun() throws Exception { + String fName = step.getDescriptor().getFunctionName(); + if (isNotBlank(step.getFile()) && isNotBlank(step.getText())) { + throw new IllegalArgumentException(Messages.ReadTOMLStepExecution_tooManyArguments(fName)); + } + + Object toml = null; + TomlMapper mapper = new TomlMapper(); + if (!isBlank(step.getFile())) { + FilePath f = ws.child(step.getFile()); + + if (!f.exists()) { + throw new FileNotFoundException(Messages.TOMLStepExecution_fileNotFound(f.getRemote())); + } + + if (f.isDirectory()) { + throw new IllegalArgumentException(Messages.TOMLStepExecution_fileIsDirectory(f.getRemote())); + } + + try (InputStream is = f.read()) { + toml = mapper.readValue(IOUtils.toString(is, StandardCharsets.UTF_8), Object.class); + } + } + + if (!isBlank(step.getText())) { + toml = mapper.readValue(step.getText().trim(), Object.class); + } + + return toml; + } +} diff --git a/src/main/java/org/jenkinsci/plugins/pipeline/utility/steps/toml/WriteTOMLStep.java b/src/main/java/org/jenkinsci/plugins/pipeline/utility/steps/toml/WriteTOMLStep.java new file mode 100644 index 00000000..b2322fba --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/pipeline/utility/steps/toml/WriteTOMLStep.java @@ -0,0 +1,162 @@ +/* + * 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.toml; + +import static org.apache.commons.lang.StringUtils.isBlank; +import static org.apache.commons.lang.StringUtils.isNotBlank; + +import com.fasterxml.jackson.dataformat.toml.TomlMapper; +import com.google.common.collect.ImmutableSet; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; +import hudson.Util; +import hudson.model.TaskListener; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Set; +import org.jenkinsci.plugins.workflow.steps.*; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; + +/** + * Writes a {@link java.util.LinkedHashMap} object to file in the current working directory. + * + */ +public class WriteTOMLStep extends Step { + + private String file; + private final Object toml; + private boolean returnText; + + @DataBoundConstructor + public WriteTOMLStep(Object toml) { + this.toml = toml; + } + + @Deprecated + public WriteTOMLStep(String file, Object toml) { + this.file = Util.fixNull(file); + this.toml = toml; + } + + /** + * Returns the name of the file to write. + * + * @return the file name + */ + public String getFile() { + return file; + } + + @DataBoundSetter + public void setFile(String file) { + this.file = file; + } + + /** + * Return the toml object to save. + * + * @return an object + */ + public Object getTOML() { + return toml; + } + + public boolean isReturnText() { + return returnText; + } + + @DataBoundSetter + public void setReturnText(boolean returnText) { + this.returnText = returnText; + } + + @Override + public StepExecution start(StepContext context) throws Exception { + if (this.toml == null) { + throw new IllegalArgumentException(Messages.WriteTOMLStepExecution_missingTOML( + this.getDescriptor().getFunctionName())); + } + + if (this.returnText) { + if (isNotBlank(this.file)) { + throw new IllegalArgumentException(Messages.WriteTOMLStepExecution_bothReturnTextAndFile( + this.getDescriptor().getFunctionName())); + } + return new ReturnTextExecution(this, context); + } + + if (isBlank(this.file)) { + throw new IllegalArgumentException(Messages.WriteTOMLStepExecution_missingReturnTextAndFile( + this.getDescriptor().getFunctionName())); + } + + return new WriteTOMLStepExecution(this, context); + } + + @Extension + public static class DescriptorImpl extends StepDescriptor { + + public DescriptorImpl() {} + + @Override + public Set> getRequiredContext() { + return ImmutableSet.of(TaskListener.class); + } + + @Override + public String getFunctionName() { + return "writeTOML"; + } + + @Override + @NonNull + public String getDisplayName() { + return Messages.WriteTOMLStep_DescriptorImpl_displayName(); + } + } + + void execute(Writer writer) throws java.io.IOException { + TomlMapper mapper = new TomlMapper(); + mapper.writeValue(writer, toml); + } + + private static class ReturnTextExecution extends SynchronousNonBlockingStepExecution { + private static final long serialVersionUID = 1L; + + private transient WriteTOMLStep step; + + protected ReturnTextExecution(WriteTOMLStep step, @NonNull StepContext context) { + super(context); + this.step = step; + } + + @Override + protected String run() throws Exception { + StringWriter w = new StringWriter(); + this.step.execute(w); + return w.toString(); + } + } +} diff --git a/src/main/java/org/jenkinsci/plugins/pipeline/utility/steps/toml/WriteTOMLStepExecution.java b/src/main/java/org/jenkinsci/plugins/pipeline/utility/steps/toml/WriteTOMLStepExecution.java new file mode 100644 index 00000000..e404d84e --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/pipeline/utility/steps/toml/WriteTOMLStepExecution.java @@ -0,0 +1,68 @@ +/* + * 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.toml; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.FilePath; +import java.io.FileNotFoundException; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import org.jenkinsci.plugins.workflow.steps.MissingContextVariableException; +import org.jenkinsci.plugins.workflow.steps.StepContext; +import org.jenkinsci.plugins.workflow.steps.SynchronousNonBlockingStepExecution; + +/** + * Execution of {@link WriteTOMLStep}. + * + */ +public class WriteTOMLStepExecution extends SynchronousNonBlockingStepExecution { + private static final long serialVersionUID = 1L; + + private transient WriteTOMLStep step; + + protected WriteTOMLStepExecution(@NonNull WriteTOMLStep step, @NonNull StepContext context) { + super(context); + this.step = step; + } + + @Override + protected Void run() throws Exception { + FilePath ws = getContext().get(FilePath.class); + if (ws == null) { + throw new MissingContextVariableException(FilePath.class); + } + assert ws != null; + + FilePath path = ws.child(step.getFile()); + if (path.isDirectory()) { + throw new FileNotFoundException(Messages.TOMLStepExecution_fileIsDirectory(path.getRemote())); + } + + try (OutputStreamWriter writer = new OutputStreamWriter(path.write(), StandardCharsets.UTF_8)) { + step.execute(writer); + } + return null; + } +} diff --git a/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/Messages.properties b/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/Messages.properties new file mode 100644 index 00000000..8814f259 --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/Messages.properties @@ -0,0 +1,30 @@ +# The MIT License +# +# Copyright (c) 2016, CloudBees Inc. +# +# 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. + +ReadTOMLStep.DescriptorImpl.displayName=Read toml from files in the workspace. +ReadTOMLStepExecution.tooManyArguments=At most one of file or text must be provided to {0}. +WriteTOMLStep.DescriptorImpl.displayName=Write toml to a file in the workspace. +WriteTOMLStepExecution_missingTOML=You have to provide a toml object to save for {0}. +WriteTOMLStepExecution_missingReturnTextAndFile=You have to provide either file or returnText to {0}. +WriteTOMLStepExecution_bothReturnTextAndFile=You cannot provide both returnText and file to {0}. +TOMLStepExecution.fileIsDirectory={0} is a directory. +TOMLStepExecution.fileNotFound={0} does not exist. diff --git a/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep/config.jelly b/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep/config.jelly new file mode 100644 index 00000000..f993e51b --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep/config.jelly @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep/config.properties b/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep/config.properties new file mode 100644 index 00000000..618b8b52 --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep/config.properties @@ -0,0 +1,4 @@ +file.title=File +file.description=File and Text are mutually exclusive +text.title=Text +text.description=Text and File are mutually exclusive diff --git a/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep/help-file.html b/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep/help-file.html new file mode 100644 index 00000000..5abb3307 --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep/help-file.html @@ -0,0 +1,30 @@ + +

+ Path to a file in the workspace from which to read the TOML data. + Data could be access as a map. +

+

+ You can only specify file or text, not both in the same invocation. +

diff --git a/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep/help-text.html b/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep/help-text.html new file mode 100644 index 00000000..4d8024a8 --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep/help-text.html @@ -0,0 +1,30 @@ + +

+ A string containing the TOML formatted data. + Data could be access as a map. +

+

+ You can only specify file or text, not both in the same invocation. +

diff --git a/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep/help.html b/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep/help.html new file mode 100644 index 00000000..cb66e1ea --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStep/help.html @@ -0,0 +1,51 @@ + +

+ Reads a file in the current working directory or a String as a plain text + TOML file. + The returned object is a normal Map with String keys or a List of primitives or Map. +

+

+ Example:
+

+def props = readTOML file: 'dir/input.toml'
+assert props['attr1'] == 'One'
+assert props.attr1 == 'One'
+
+def props = readTOML text: 'key = "value"'
+assert props['key'] == 'value'
+assert props.key == 'value'
+
+def props = readTOML text: 'a = [ "a", "b" ]'
+assert props.a[0] == 'a'
+assert props.a[1] == 'b'
+
+def props = readTOML text: 'a.b.c = 1\n[key]\nvalue = ""'
+assert props['key'].value == ""
+assert props.a.b.c == 1
+props.each { key, value ->
+    echo "Walked through key $key and value $value"
+}
+	
+

diff --git a/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/WriteTOMLStep/config.jelly b/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/WriteTOMLStep/config.jelly new file mode 100644 index 00000000..29bf5473 --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/WriteTOMLStep/config.jelly @@ -0,0 +1,30 @@ + + + + + +

This is a special step. No snippet generation available. See inline help for more information.

+
+
diff --git a/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/WriteTOMLStep/help.html b/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/WriteTOMLStep/help.html new file mode 100644 index 00000000..31bdc846 --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/pipeline/utility/steps/toml/WriteTOMLStep/help.html @@ -0,0 +1,80 @@ + + +

+ Write TOML to a file in the + current working directory, or to a String. +

+Fields: +
    +
  • + toml: + The object to write. Can be Map implementation. +
  • +
  • + file (optional): + Optional path to a file in the workspace to write to. + If provided, then returnText must be false or omitted. + It is required that either file is provided, or returnText is true. +
  • +
  • + returnText (optional): + Return the TOML as a string instead of writing it to a file. Defaults to false. + If true, then file must not be provided. + It is required that either file is provided, or returnText is true. +
  • +
+

+ Example:
+ Writing to a file: + +

+        def amap = ['something': 'my datas',
+                    'size': 3,
+                    'isEmpty': false]
+
+        writeTOML file: 'data.toml', toml: amap
+        def read = readTOML file: 'data.toml'
+
+        assert read.something == 'my datas'
+        assert read.size == 3
+        assert read.isEmpty == false
+        
+ + Writing to a string: + +
+        def amap = ['something': 'my datas',
+                    'size': 3,
+                    'isEmpty': false]
+
+        String toml = writeTOML returnText: true, toml: amap
+        def read = readTOML text: toml
+
+        assert read.something == 'my datas'
+        assert read.size == 3
+        assert read.isEmpty == false
+        
+
+

diff --git a/src/test/java/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStepTest.java b/src/test/java/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStepTest.java new file mode 100644 index 00000000..f4d49efa --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/pipeline/utility/steps/toml/ReadTOMLStepTest.java @@ -0,0 +1,217 @@ +/* + * 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.toml; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.jenkinsci.plugins.pipeline.utility.steps.Messages.AbstractFileOrTextStepDescriptorImpl_missingRequiredArgument; + +import com.fasterxml.jackson.dataformat.toml.TomlMapper; +import hudson.model.Result; +import java.io.*; +import java.util.Arrays; +import java.util.LinkedHashMap; +import org.apache.commons.io.IOUtils; +import org.jenkinsci.plugins.pipeline.utility.steps.FilenameTestsUtils; +import org.jenkinsci.plugins.workflow.actions.ArgumentsAction; +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; +import org.jenkinsci.plugins.workflow.graph.FlowNode; +import org.jenkinsci.plugins.workflow.graphanalysis.DepthFirstScanner; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.jvnet.hudson.test.JenkinsRule; + +/** + * Tests for {@link ReadTOMLStep}. + * + */ +public class ReadTOMLStepTest { + private static final String SOME_TOML = "title = \"TOML Example\"\n" + "year = 2024\n" + + "pi = 3.14\n" + + "is_active = true\n" + + "dob = 1992-04-15\n" + + "fruits = [\"apple\", \"banana\"]\n" + + "numbers = [1, 2, 3]\n" + + "# Extra owner information\n" + + "[owner]\n" + + " name = \"Alice\"\n" + + " dob = 1985-06-23\n" + + " [owner.location]\n" + + " city = \"NYC\"\n" + + " country = \"USA\"\n" + + " [owner.contact]\n" + + " location = { street = \"123 Elm St\", zip = \"10001\" }\n" + + "[[products]]\n" + + " name = \"Laptop\"\n" + + " price = 999.99\n" + + "[[products]]\n" + + " name = \"Phone\"\n" + + " price = 499.99\n"; + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void readFile() throws Exception { + String file = writeTOML(getTOML()); + + WorkflowJob p1 = j.jenkins.createProject(WorkflowJob.class, "p1"); + p1.setDefinition(new CpsFlowDefinition( + "node {\n" + " def toml = readTOML file: '" + + file + "'\n" + " assert toml.tags.size() == 3\n" + + " assert toml.tags[0] == 0\n" + + " assert toml.tags[1] == 1\n" + + " assert toml.tags[2] == 2\n" + + " assert toml.key == 'value'\n" + + "}", + true)); + j.assertBuildStatusSuccess(p1.scheduleBuild2(0)); + + file = writeTOML(SOME_TOML); + + WorkflowJob p2 = j.jenkins.createProject(WorkflowJob.class, "p2"); + p2.setDefinition(new CpsFlowDefinition( + "node {\n" + " def toml = readTOML file: '" + + file + "'\n" + " println toml \n" + + " assert toml.title == 'TOML Example'\n" + + " assert toml.year == 2024\n" + + " assert toml.pi == 3.14\n" + + " assert toml.is_active == true\n" + + " assert toml.dob == '1992-04-15'\n" + + " assert toml.fruits.size() == 2\n" + + " assert toml.fruits[0] == 'apple'\n" + + " assert toml.fruits[1] == 'banana'\n" + + " assert toml.numbers.size() == 3\n" + + " assert toml.numbers[0] == 1\n" + + " assert toml.numbers[1] == 2\n" + + " assert toml.numbers[2] == 3\n" + + " assert toml.owner.name == 'Alice'\n" + + " assert toml.owner.dob == '1985-06-23'\n" + + " assert toml.owner.location.city == 'NYC'\n" + + " assert toml.owner.location.country == 'USA'\n" + + " assert toml.owner.contact.location.street == '123 Elm St'\n" + + " assert toml.owner.contact.location.zip == '10001'\n" + + " assert toml.products.size() == 2\n" + + " assert toml.products[0].name == 'Laptop'\n" + + " assert toml.products[0].price == 999.99\n" + + " assert toml.products[1].name == 'Phone'\n" + + " assert toml.products[1].price == 499.99\n" + + "}", + true)); + j.assertBuildStatusSuccess(p2.scheduleBuild2(0)); + } + + @Test + public void readText() throws Exception { + WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); + String groovyArrayOfLines = Arrays.deepToString(Arrays.stream(getTOML().split("\n")) + .map(line -> "\"" + line + "\"") + .toArray()); + + p.setDefinition(new CpsFlowDefinition( + "def toml = readTOML text: " + groovyArrayOfLines + ".join('\\n')\n" + + " assert toml.tags.size() == 3\n" + + " assert toml.tags[0] == 0\n" + + " assert toml.tags[1] == 1\n" + + " assert toml.tags[2] == 2\n" + + " assert toml.key == 'value'\n", + true)); + j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + } + + @Test + public void readDirectText() throws Exception { + WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition( + "def toml = readTOML text: 'a = 1\\nb = [2, 3]\\n[c]\\nd = 4'\n" + "assert toml.a == 1\n" + + "assert toml.b.size() == 2\n" + + "assert toml.b[0] == 2\n" + + "assert toml.b[1] == 3\n" + + "assert toml.c.d == 4\n", + true)); + j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + } + + @Test + public void readNone() throws Exception { + WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition("node {\n" + " def toml = readTOML()\n" + "}", true)); + WorkflowRun run = + j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); + j.assertLogContains(AbstractFileOrTextStepDescriptorImpl_missingRequiredArgument("readTOML"), run); + } + + @Test + public void readFileAndText() throws Exception { + String file = writeTOML(getTOML()); + + WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition( + "node {\n" + " def toml = readTOML( text: 'a = 1', file: '" + file + "' )\n" + "}", true)); + WorkflowRun run = + j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); + j.assertLogContains(Messages.ReadTOMLStepExecution_tooManyArguments("readTOML"), run); + } + + private String getTOML() throws Exception { + LinkedHashMap toml = new LinkedHashMap<>(); + toml.put("tags", Arrays.asList(0, 1, 2)); + toml.put("key", "value"); + + Writer writer = new StringWriter(); + new TomlMapper().writeValue(writer, toml); + + return writer.toString(); + } + + private String writeTOML(String toml) throws IOException { + File file = temp.newFile(); + try (Writer f = new FileWriter(file); + Reader r = new StringReader(toml)) { + IOUtils.copy(r, f); + } + return FilenameTestsUtils.toPath(file); + } + + @Test + public void readTextHideContents() throws Exception { + WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition("readTOML text: 'val = \"s3cr3t\"'", true)); + WorkflowRun b = j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + for (FlowNode n : new DepthFirstScanner().allNodes(b.getExecution())) { + assertThat( + "did not leak secret in " + n + " ~ " + n.getDisplayName(), + ArgumentsAction.getStepArgumentsAsString(n), + not(containsString("s3cr3t"))); + } + } +} diff --git a/src/test/java/org/jenkinsci/plugins/pipeline/utility/steps/toml/WriteTOMLStepTest.java b/src/test/java/org/jenkinsci/plugins/pipeline/utility/steps/toml/WriteTOMLStepTest.java new file mode 100644 index 00000000..0f5ab5bf --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/pipeline/utility/steps/toml/WriteTOMLStepTest.java @@ -0,0 +1,146 @@ +/* + * 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.toml; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import com.fasterxml.jackson.dataformat.toml.TomlMapper; +import hudson.model.Result; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import org.jenkinsci.plugins.pipeline.utility.steps.FilenameTestsUtils; +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.jvnet.hudson.test.JenkinsRule; + +/** + * Tests for {@link WriteTOMLStep}. + * + */ +public class WriteTOMLStepTest { + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void writeFile() throws Exception { + int elements = 3; + String groovyArrayOfLines = + Arrays.deepToString(Arrays.stream(getTOML(elements).split("\n")) + .map(line -> "\"" + line + "\"") + .toArray()); + + File output = temp.newFile(); + + WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition( + "node {\n" + " def toml = readTOML text: " + + groovyArrayOfLines + ".join('\\n')\n" + " toml.array[0] = [:]\n" + + " toml.array[" + + elements + "] = 45\n" + " writeTOML file: '" + + FilenameTestsUtils.toPath(output) + "', toml: toml\n" + "}", + true)); + j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + + // file exists by default so we check that should not be empty + assertThat(output.length(), greaterThan(0L)); + + Object tomlRaw = new TomlMapper().readValue(output, Object.class); + + LinkedHashMap toml = (LinkedHashMap) tomlRaw; + ArrayList tomlArray = (ArrayList) toml.get("array"); + + assertFalse("Saved toml is an empty map", toml.isEmpty()); + assertThat(tomlArray.size(), is(elements + 1)); + assertNotNull("Unexpected element value", tomlArray.get(0)); + assertEquals("Unexpected element value", 45, tomlArray.get(elements)); + } + + @Test + public void returnText() throws Exception { + WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition( + "node {\n" + " String written = writeTOML returnText: true, toml: ['a': 1, 'b': 2] \n" + + " def toml = readTOML text: written \n" + + " assert toml == ['a': 1, 'b': 2] \n" + + "}", + true)); + j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + } + + @Test + public void checkRequiredFileOrReturnText() throws Exception { + WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition( + "node {\n" + " def toml = readTOML text: '\"\" = \"\"'\n" + " writeTOML toml: toml" + "}", true)); + WorkflowRun run = + j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); + j.assertLogContains(Messages.WriteTOMLStepExecution_missingReturnTextAndFile("writeTOML"), run); + } + + @Test + public void checkRequiredTOML() throws Exception { + WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition("node {\n" + " writeTOML file: 'output.toml'" + "}", true)); + WorkflowRun run = + j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); + j.assertLogContains(Messages.WriteTOMLStepExecution_missingTOML("writeTOML"), run); + } + + @Test + public void checkCannotReturnTextAndFile() throws Exception { + WorkflowJob p = j.jenkins.createProject(WorkflowJob.class, "p"); + p.setDefinition(new CpsFlowDefinition( + "node {\n" + " writeTOML returnText: true, file: 'output.toml', toml: [foo: 'bar']" + "}", true)); + WorkflowRun run = + j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); + j.assertLogContains(Messages.WriteTOMLStepExecution_bothReturnTextAndFile("writeTOML"), run); + } + + private String getTOML(int elements) { + StringBuilder stringBuilder = new StringBuilder(); + + for (int i = 0; i < elements; i++) { + stringBuilder + .append("[[array]]") + .append("\n") + .append("index=") + .append(i) + .append("\n"); + } + + return stringBuilder.toString(); + } +}