_FILENAME` variable - assuming parameter is named `FILE`, snippet below will give you file with original filename in current workspace:
55 |
56 | ```groovy
57 | node {
58 | unstash 'FILE'
59 | sh 'mv FILE $FILE_FILENAME'
60 | }
61 | ```
62 |
63 | ## Usage in Declarative Pipeline
64 |
65 | You can now declare and use file parameters via Declarative Pipeline syntax:
66 |
67 | ```groovy
68 | pipeline {
69 | agent any
70 | parameters {
71 | base64File 'small'
72 | stashedFile 'large'
73 | }
74 | stages {
75 | stage('Example') {
76 | steps {
77 | withFileParameter('small') {
78 | sh 'cat $small'
79 | }
80 | unstash 'large'
81 | sh 'cat large'
82 | }
83 | }
84 | }
85 | }
86 | ```
87 |
88 | ## Usage with `input`
89 |
90 | You can use Base64 parameters for uploading _small_ files in the middle of the build:
91 |
92 | ```groovy
93 | def fb64 = input message: 'upload', parameters: [base64File('file')]
94 | node {
95 | withEnv(["fb64=$fb64"]) {
96 | sh 'echo $fb64 | base64 -d'
97 | }
98 | }
99 | ```
100 |
101 | Currently there is no mechanism for doing this with stashed files.
102 | Nor can you use the `withFileParameter` wrapper here.
103 |
104 | ## Usage with `build`
105 |
106 | You can use Base64 parameters for passing _small_ files to downstream builds:
107 |
108 | ```groovy
109 | build job: 'downstream', parameters: [base64File(name: 'file', base64: Base64.encoder.encodeToString('hello'.bytes)))]
110 | ```
111 |
112 | ## Usage with HTTP API
113 |
114 | You can pass file parameters to the HTTP API (in the Jenkins UI, this HTTP API is also referred to as “REST API”):
115 |
116 | Curl example:
117 |
118 | ```bash
119 | curl -u $auth -F FILE=@/tmp/f $jenkins/job/myjob/buildWithParameters
120 | ```
121 |
122 | Javascript example:
123 |
124 | ```js
125 | const file = fileInput.files[0]; // a File object
126 | const body = new FormData();
127 | body.append('FILE', file); // will come through to the job as the named file parameter 'FILE'
128 | const request = new Request(`${jobUrl}buildWithParameters`, { method: 'POST', body });
129 | fetch(request); // omitted, API token and other credentials
130 | ```
131 |
132 | ## LICENSE
133 |
134 | Licensed under MIT, see [LICENSE](LICENSE.md)
135 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.jenkins-ci.plugins
6 | plugin
7 | 5.17
8 |
9 |
10 | io.jenkins.plugins
11 | file-parameters
12 | ${changelist}
13 | hpi
14 |
15 | 999999-SNAPSHOT
16 |
17 | 2.479
18 | ${jenkins.baseline}.1
19 | jenkinsci/${project.artifactId}-plugin
20 |
21 | File Parameter Plugin
22 | https://github.com/jenkinsci/${project.artifactId}-plugin
23 |
24 |
25 |
26 |
27 | io.jenkins.tools.bom
28 | bom-${jenkins.baseline}.x
29 | 4228.v0a_71308d905b_
30 | import
31 | pom
32 |
33 |
34 |
35 |
36 |
37 |
38 | org.jenkins-ci.plugins
39 | structs
40 |
41 |
42 | org.jenkins-ci.plugins.workflow
43 | workflow-api
44 |
45 |
46 | org.jenkinsci.plugins
47 | pipeline-model-definition
48 | test
49 |
50 |
51 | org.jenkins-ci.plugins
52 | pipeline-build-step
53 | test
54 |
55 |
56 |
57 |
58 |
59 | MIT License
60 | https://opensource.org/licenses/MIT
61 |
62 |
63 |
64 |
65 | scm:git:https://github.com/${gitHubRepo}.git
66 | scm:git:git@github.com:${gitHubRepo}.git
67 | https://github.com/${gitHubRepo}
68 | ${scmTag}
69 |
70 |
71 |
72 |
73 | repo.jenkins-ci.org
74 | https://repo.jenkins-ci.org/public/
75 |
76 |
77 |
78 |
79 | repo.jenkins-ci.org
80 | https://repo.jenkins-ci.org/public/
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/file_parameters/AbstractFileParameterDefinition.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2020 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package io.jenkins.plugins.file_parameters;
26 |
27 | import hudson.cli.CLICommand;
28 | import hudson.model.Failure;
29 | import hudson.model.ParameterDefinition;
30 | import hudson.model.ParameterValue;
31 | import hudson.util.FormValidation;
32 | import java.io.ByteArrayInputStream;
33 | import java.io.IOException;
34 | import java.io.InputStream;
35 | import java.util.Base64;
36 | import jakarta.servlet.ServletException;
37 | import jenkins.model.Jenkins;
38 | import net.sf.json.JSONObject;
39 | import org.apache.commons.fileupload2.core.FileItem;
40 | import org.apache.commons.fileupload2.core.FileUploadContentTypeException;
41 | import org.kohsuke.stapler.QueryParameter;
42 | import org.kohsuke.stapler.StaplerRequest2;
43 |
44 | abstract class AbstractFileParameterDefinition extends ParameterDefinition {
45 |
46 | protected AbstractFileParameterDefinition(String name) {
47 | super(name);
48 | Jenkins.checkGoodName(name);
49 | }
50 |
51 | protected Object readResolve() {
52 | Jenkins.checkGoodName(getName());
53 | return this;
54 | }
55 |
56 | protected abstract Class extends AbstractFileParameterValue> valueType();
57 |
58 | protected abstract AbstractFileParameterValue createValue(String name, InputStream src) throws IOException;
59 |
60 | @Override public ParameterValue createValue(StaplerRequest2 req, JSONObject jo) {
61 | AbstractFileParameterValue p = req.bindJSON(valueType(), jo);
62 | p.setDescription(getDescription());
63 | return p;
64 | }
65 |
66 | @Override public ParameterValue createValue(StaplerRequest2 req) {
67 | try {
68 | FileItem src;
69 | try {
70 | src = req.getFileItem2(getName());
71 | } catch (ServletException x) {
72 | if (x.getCause() instanceof FileUploadContentTypeException) {
73 | src = null;
74 | } else {
75 | throw x;
76 | }
77 | }
78 | if (src == null) {
79 | return null;
80 | }
81 | AbstractFileParameterValue p;
82 | try (InputStream in = src.getInputStream()) {
83 | p = createValue(getName(), in);
84 | }
85 | src.delete();
86 | p.setDescription(getDescription());
87 | p.setFilename(src.getName());
88 | return p;
89 | } catch (ServletException | IOException x) {
90 | throw new RuntimeException(x);
91 | }
92 | }
93 |
94 | @Override
95 | public ParameterValue createValue(CLICommand command, String value) throws IOException, InterruptedException {
96 | AbstractFileParameterValue p;
97 | if (value.isEmpty()) {
98 | p = createValue(getName(), command.stdin);
99 | } else {
100 | byte[] data = Base64.getDecoder().decode(value);
101 | p = createValue(getName(), new ByteArrayInputStream(data));
102 | }
103 | p.setDescription(getDescription());
104 | return p;
105 | }
106 |
107 | protected static abstract class AbstractFileParameterDefinitionDescriptor extends ParameterDescriptor {
108 |
109 | public FormValidation doCheckName(@QueryParameter String name) {
110 | try {
111 | Jenkins.checkGoodName(name);
112 | return FormValidation.ok();
113 | } catch (Failure x) {
114 | return FormValidation.error(x.getMessage());
115 | }
116 | }
117 |
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/file_parameters/AbstractFileParameterValue.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2020 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package io.jenkins.plugins.file_parameters;
26 |
27 | import edu.umd.cs.findbugs.annotations.CheckForNull;
28 | import edu.umd.cs.findbugs.annotations.NonNull;
29 | import hudson.EnvVars;
30 | import hudson.FilePath;
31 | import hudson.Launcher;
32 | import hudson.Util;
33 | import hudson.model.Failure;
34 | import hudson.model.ParameterValue;
35 | import hudson.model.Run;
36 | import hudson.model.TaskListener;
37 | import java.io.FilterInputStream;
38 | import java.io.IOException;
39 | import java.io.InputStream;
40 | import java.io.OutputStream;
41 | import jenkins.model.Jenkins;
42 | import org.apache.commons.io.IOUtils;
43 | import org.apache.commons.lang.StringUtils;
44 | import org.kohsuke.stapler.AncestorInPath;
45 | import org.kohsuke.stapler.StaplerResponse2;
46 |
47 | /**
48 | * Implement either {@link #open} and/or {@link #createTempFile}.
49 | */
50 | public abstract class AbstractFileParameterValue extends ParameterValue {
51 |
52 | private @CheckForNull String filename;
53 |
54 | protected AbstractFileParameterValue(String name) {
55 | super(name);
56 | }
57 |
58 | public final String getFilename() {
59 | return filename;
60 | }
61 |
62 | final void setFilename(String filename) {
63 | try {
64 | Jenkins.checkGoodName(filename);
65 | this.filename = filename;
66 | } catch (Failure x) {
67 | // Ignore and leave the filename undefined.
68 | // FileItem.getName Javadoc claims Opera might pass a full path.
69 | // This is a best effort anyway (scripts should be written to tolerate an undefined name).
70 | }
71 | }
72 |
73 | protected InputStream open(@CheckForNull Run,?> build) throws IOException, InterruptedException {
74 | assert Util.isOverridden(AbstractFileParameterValue.class, getClass(), "createTempFile", Run.class, FilePath.class, EnvVars.class, Launcher.class, TaskListener.class);
75 | if (build == null) {
76 | throw new IOException("Cannot operate outside of a build context");
77 | }
78 | FilePath tempDir = new FilePath(Util.createTempDir());
79 | FilePath f = createTempFile(build, tempDir, new EnvVars(EnvVars.masterEnvVars), new Launcher.LocalLauncher(TaskListener.NULL), TaskListener.NULL);
80 | return new FilterInputStream(f.read()) {
81 | @Override
82 | public void close() throws IOException {
83 | super.close();
84 | try {
85 | tempDir.deleteRecursive();
86 | } catch (InterruptedException x) {
87 | throw new IOException(x);
88 | }
89 | }
90 | };
91 | }
92 |
93 | protected FilePath createTempFile(@NonNull Run,?> build, @NonNull FilePath tempDir, @NonNull EnvVars env, @NonNull Launcher launcher, @NonNull TaskListener listener) throws IOException, InterruptedException {
94 | assert Util.isOverridden(AbstractFileParameterValue.class, getClass(), "open", Run.class);
95 | FilePath f = tempDir.createTempFile(StringUtils.rightPad(name, 3, 'x'), null);
96 | try (InputStream is = open(build)) {
97 | f.copyFrom(is);
98 | }
99 | return f;
100 | }
101 |
102 | public void doDownload(@AncestorInPath Run,?> build, StaplerResponse2 rsp) throws Exception {
103 | rsp.setContentType("application/octet-stream");
104 | try (InputStream is = open(build); OutputStream os = rsp.getOutputStream()) {
105 | IOUtils.copy(is, os);
106 | }
107 | }
108 |
109 | // TODO equals/hashCode
110 |
111 | @Override public void buildEnvironment(Run, ?> build, EnvVars env) {
112 | super.buildEnvironment(build, env);
113 | String fname = getFilename();
114 | if (fname != null) {
115 | env.put(name + "_FILENAME", fname);
116 | }
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/file_parameters/Base64FileParameterDefinition.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2020 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package io.jenkins.plugins.file_parameters;
26 |
27 | import hudson.Extension;
28 | import java.io.IOException;
29 | import java.io.InputStream;
30 | import org.jenkinsci.Symbol;
31 | import org.kohsuke.stapler.DataBoundConstructor;
32 |
33 | public final class Base64FileParameterDefinition extends AbstractFileParameterDefinition {
34 |
35 | @DataBoundConstructor public Base64FileParameterDefinition(String name) {
36 | super(name);
37 | }
38 |
39 | @Override protected Class extends AbstractFileParameterValue> valueType() {
40 | return Base64FileParameterValue.class;
41 | }
42 |
43 | @Override protected AbstractFileParameterValue createValue(String name, InputStream src) throws IOException {
44 | return new Base64FileParameterValue(name, src);
45 | }
46 |
47 | // TODO equals/hashCode
48 |
49 | @Symbol("base64File")
50 | @Extension public static final class DescriptorImpl extends AbstractFileParameterDefinitionDescriptor {
51 |
52 | @Override public String getDisplayName() {
53 | return "Base64 File Parameter";
54 | }
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/file_parameters/Base64FileParameterValue.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2020 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package io.jenkins.plugins.file_parameters;
26 |
27 | import hudson.EnvVars;
28 | import hudson.model.Run;
29 | import java.io.ByteArrayInputStream;
30 | import java.io.IOException;
31 | import java.io.InputStream;
32 | import java.util.Base64;
33 | import org.apache.commons.fileupload.FileItem;
34 | import org.apache.commons.io.IOUtils;
35 | import org.jenkinsci.Symbol;
36 | import org.kohsuke.stapler.DataBoundConstructor;
37 | import org.kohsuke.stapler.DataBoundSetter;
38 |
39 | @Symbol("base64File")
40 | public final class Base64FileParameterValue extends AbstractFileParameterValue {
41 |
42 | private String base64;
43 |
44 | @DataBoundConstructor public Base64FileParameterValue(String name) throws IOException {
45 | super(name);
46 | }
47 |
48 | @DataBoundSetter public void setFile(FileItem file) throws IOException {
49 | base64 = Base64.getEncoder().encodeToString(IOUtils.toByteArray(file.getInputStream()));
50 | setFilename(file.getName());
51 | file.delete();
52 | }
53 |
54 | Base64FileParameterValue(String name, InputStream src) throws IOException {
55 | super(name);
56 | base64 = Base64.getEncoder().encodeToString(IOUtils.toByteArray(src));
57 | }
58 |
59 | @DataBoundSetter public void setBase64(String base64) throws IOException {
60 | this.base64 = base64;
61 | }
62 |
63 | @Override public void buildEnvironment(Run, ?> build, EnvVars env) {
64 | super.buildEnvironment(build, env);
65 | env.put(name, base64);
66 | }
67 |
68 | // TODO createVariableResolver if desired for freestyle
69 |
70 | @Override public Object getValue() {
71 | return base64;
72 | }
73 |
74 | @Override protected InputStream open(Run, ?> build) throws IOException {
75 | return new ByteArrayInputStream(Base64.getDecoder().decode(base64));
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/file_parameters/FileParameterWrapper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2020 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package io.jenkins.plugins.file_parameters;
26 |
27 | import hudson.AbortException;
28 | import hudson.EnvVars;
29 | import hudson.Extension;
30 | import hudson.FilePath;
31 | import hudson.Launcher;
32 | import hudson.model.AbstractProject;
33 | import hudson.model.ParameterValue;
34 | import hudson.model.ParametersAction;
35 | import hudson.model.Run;
36 | import hudson.model.TaskListener;
37 | import hudson.slaves.WorkspaceList;
38 | import hudson.tasks.BuildWrapperDescriptor;
39 | import java.io.IOException;
40 | import java.io.InputStream;
41 | import jenkins.tasks.SimpleBuildWrapper;
42 | import org.jenkinsci.Symbol;
43 | import org.kohsuke.stapler.DataBoundConstructor;
44 | import org.kohsuke.stapler.DataBoundSetter;
45 |
46 | /**
47 | * Saves a file parameter to a temporary local file.
48 | */
49 | public final class FileParameterWrapper extends SimpleBuildWrapper {
50 |
51 | public final String name;
52 |
53 | private boolean allowNoFile;
54 |
55 | @DataBoundConstructor public FileParameterWrapper(String name) {
56 | this.name = name;
57 | }
58 |
59 | public boolean isAllowNoFile() {
60 | return allowNoFile;
61 | }
62 |
63 | @DataBoundSetter
64 | public void setAllowNoFile(boolean allowNoFile) {
65 | this.allowNoFile = allowNoFile;
66 | }
67 |
68 | @Override public void setUp(Context context, Run, ?> build, FilePath workspace, Launcher launcher, TaskListener listener, EnvVars initialEnvironment) throws IOException, InterruptedException {
69 | ParametersAction pa = build.getAction(ParametersAction.class);
70 | if (pa == null) {
71 | throw new AbortException("No parameters");
72 | }
73 | ParameterValue pv = pa.getParameter(name);
74 | if (pv == null && !allowNoFile) {
75 | throw new AbortException("No parameter named " + name);
76 | }
77 | if (!(pv instanceof AbstractFileParameterValue) && !allowNoFile) {
78 | throw new AbortException("Unsupported parameter type");
79 | }
80 | if (pv == null && allowNoFile) {
81 | listener.getLogger().println("Skip file parameter as there is no parameter with name: '" + name + "'");
82 | return;
83 | }
84 | FilePath tempDir = WorkspaceList.tempDir(workspace);
85 | if (tempDir == null) {
86 | throw new AbortException("Missing workspace or could not make temp dir");
87 | }
88 | tempDir.mkdirs();
89 | FilePath f = ((AbstractFileParameterValue) pv).createTempFile(build, tempDir, initialEnvironment, launcher, listener);
90 | context.env(name, f.getRemote());
91 | context.setDisposer(new Delete(f.getRemote()));
92 | }
93 |
94 | private static class Delete extends Disposer {
95 |
96 | private static final long serialVersionUID = 1;
97 | private final String file;
98 |
99 | Delete(String file) {
100 | this.file = file;
101 | }
102 |
103 | @Override public void tearDown(Run, ?> build, FilePath workspace, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
104 | workspace.child(file).delete();
105 | }
106 |
107 | }
108 |
109 | @Symbol("withFileParameter")
110 | @Extension public static final class DescriptorImpl extends BuildWrapperDescriptor {
111 |
112 | @Override public String getDisplayName() {
113 | return "Bind file parameter";
114 | }
115 |
116 | @Override public boolean isApplicable(AbstractProject, ?> item) {
117 | return true;
118 | }
119 |
120 | }
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/file_parameters/StashedFileParameterDefinition.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2020 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package io.jenkins.plugins.file_parameters;
26 |
27 | import hudson.Extension;
28 | import java.io.IOException;
29 | import java.io.InputStream;
30 | import org.jenkinsci.Symbol;
31 | import org.kohsuke.stapler.DataBoundConstructor;
32 |
33 | public final class StashedFileParameterDefinition extends AbstractFileParameterDefinition {
34 |
35 | @DataBoundConstructor public StashedFileParameterDefinition(String name) {
36 | super(name);
37 | }
38 |
39 | @Override protected Class extends AbstractFileParameterValue> valueType() {
40 | return StashedFileParameterValue.class;
41 | }
42 |
43 | @Override protected AbstractFileParameterValue createValue(String name, InputStream src) throws IOException {
44 | return new StashedFileParameterValue(name, src);
45 | }
46 |
47 | // TODO equals/hashCode
48 |
49 | @Symbol("stashedFile")
50 | @Extension public static final class DescriptorImpl extends AbstractFileParameterDefinitionDescriptor {
51 |
52 | @Override public String getDisplayName() {
53 | return "Stashed File Parameter";
54 | }
55 |
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/io/jenkins/plugins/file_parameters/StashedFileParameterValue.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2020 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package io.jenkins.plugins.file_parameters;
26 |
27 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
28 | import hudson.EnvVars;
29 | import hudson.FilePath;
30 | import hudson.Launcher;
31 | import hudson.Util;
32 | import hudson.model.Run;
33 | import hudson.model.TaskListener;
34 | import java.io.File;
35 | import java.io.IOException;
36 | import java.io.InputStream;
37 | import java.nio.file.Files;
38 |
39 | import org.apache.commons.fileupload.FileItem;
40 | import org.apache.commons.io.FileUtils;
41 | import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner;
42 | import org.jenkinsci.plugins.workflow.flow.StashManager;
43 | import org.kohsuke.stapler.DataBoundConstructor;
44 |
45 | public final class StashedFileParameterValue extends AbstractFileParameterValue {
46 |
47 | private static final long serialVersionUID = 1L;
48 |
49 | @SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED", justification = "Doesn't make sense to persist it")
50 | private transient File tmp;
51 |
52 | @DataBoundConstructor public StashedFileParameterValue(String name, FileItem file) throws IOException {
53 | this(name, file.getInputStream());
54 | setFilename(file.getName());
55 | file.delete();
56 | }
57 |
58 | StashedFileParameterValue(String name, InputStream src) throws IOException {
59 | super(name);
60 | tmp = new File(Util.createTempDir(), name);
61 | tmp.deleteOnExit();
62 | FileUtils.copyInputStreamToFile(src, tmp);
63 | }
64 |
65 | @Override public void buildEnvironment(Run, ?> build, EnvVars env) {
66 | super.buildEnvironment(build, env);
67 | if (tmp != null) {
68 | try {
69 | FlowExecutionOwner feo = build instanceof FlowExecutionOwner.Executable ? ((FlowExecutionOwner.Executable) build).asFlowExecutionOwner() : null;
70 | TaskListener listener = feo != null ? feo.getListener() : TaskListener.NULL;
71 | StashManager.stash(build, name, new FilePath(tmp.getParentFile()),
72 | new Launcher.LocalLauncher(listener), env, listener, tmp.getName(), null, false,
73 | false );
74 | } catch (IOException | InterruptedException x) {
75 | throw new RuntimeException( x );
76 | }
77 | try {
78 | Files.deleteIfExists(tmp.toPath());
79 | tmp = null;
80 | } catch (IOException e) {
81 | throw new RuntimeException(e);
82 | }
83 | }
84 | }
85 |
86 | @Override protected FilePath createTempFile(Run, ?> build, FilePath tempDir, EnvVars env, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
87 | StashManager.unstash(build, name, tempDir, launcher, env, listener);
88 | return tempDir.child(name);
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 | Provides an alternative set of file parameters that work with Pipeline, unlike the type built into Jenkins core.
4 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/file_parameters/AbstractFileParameterDefinition/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/file_parameters/AbstractFileParameterDefinition/help-name.html:
--------------------------------------------------------------------------------
1 |
2 | The name of the parameter.
3 | Depending on the type of parameter, this may or may not be bound as an environment variable during the build.
4 |
5 |
6 | If a local filename was given, an environment variable paramname _FILENAME
will also be set.
7 | If the build is triggered via the CLI, the variable will not be set.
8 |
9 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/file_parameters/AbstractFileParameterDefinition/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/file_parameters/AbstractFileParameterValue/value.jelly:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | ${%download} ${it.filename}
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/file_parameters/Base64FileParameterDefinition/help.html:
--------------------------------------------------------------------------------
1 |
2 | Simple file parameter compatible with Pipeline.
3 | Transmits file contents as an environment variable in Base64 encoding,
4 | so it is best used with fairly small files.
5 | Example usage from Declarative Pipeline:
6 |
7 |
8 | pipeline {
9 | agent any
10 | parameters {
11 | base64File 'FILE'
12 | }
13 | stages {
14 | stage('Example') {
15 | steps {
16 | sh 'echo $FILE | base64 -d > config.yaml'
17 | }
18 | }
19 | }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/file_parameters/FileParameterWrapper/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/file_parameters/FileParameterWrapper/help-allowNoFile.html:
--------------------------------------------------------------------------------
1 |
2 | By default, an error will be thrown if there is no file uploaded to the build.
3 | With this option, the build will proceed, and the environment variable will simply not be defined.
4 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/file_parameters/FileParameterWrapper/help-name.html:
--------------------------------------------------------------------------------
1 |
2 | Name of the parameter.
3 | Within the wrapper, this environment variable will be bound to the path of a temporary file.
4 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/file_parameters/FileParameterWrapper/help.html:
--------------------------------------------------------------------------------
1 |
2 | Binds an (alternative) file parameter to a local file near the workspace for convenience.
3 | Parameters can be retrieved in other ways, depending on the specific parameter type.
4 |
5 |
6 |
How to use it in a declarative pipeline:
7 |
8 | pipeline {
9 | agent any
10 | parameters {
11 | base64File 'THEFILE'
12 | }
13 | stages {
14 | stage('Example') {
15 | steps {
16 | withFileParameter('THEFILE') {
17 | sh 'cat $THEFILE'
18 | }
19 | }
20 | }
21 | }
22 | }
23 |
24 |
25 |
26 | By default, there will be an error if there is no parameter for the build but you can ignore this error using the
27 | parameter attribute
allowNoFile
. In this case your Pipeline must take into account the possibility that the file does not exist:
28 |
29 | pipeline {
30 | agent any
31 | parameters {
32 | base64File 'THEFILE'
33 | }
34 | stages {
35 | stage('Example') {
36 | steps {
37 | withFileParameter(name:'THEFILE', allowNoFile: true) {
38 | sh 'if [ -f "$THEFILE" ]; then cat $THEFILE; fi'
39 | }
40 | }
41 | }
42 | }
43 | }
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/main/resources/io/jenkins/plugins/file_parameters/StashedFileParameterDefinition/help.html:
--------------------------------------------------------------------------------
1 |
2 | File parameter compatible with Pipeline but using the stash system, better suited to large files.
3 | The file will be saved to a stash named like the parameter containing one entry, also named like the parameter.
4 | Example usage from Declarative Pipeline:
5 |
6 |
7 | pipeline {
8 | agent any
9 | parameters {
10 | stashedFile 'assets.zip'
11 | }
12 | stages {
13 | stage('Example') {
14 | steps {
15 | unstash 'assets.zip'
16 | sh 'unzip assets.zip'
17 | }
18 | }
19 | }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/file_parameters/AbstractFileParameterDefinitionTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2020 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package io.jenkins.plugins.file_parameters;
26 |
27 | import hudson.cli.CLICommandInvoker;
28 | import hudson.model.ParametersDefinitionProperty;
29 | import jenkins.model.Jenkins;
30 | import org.apache.commons.io.FileUtils;
31 | import org.htmlunit.FormEncodingType;
32 | import org.htmlunit.HttpMethod;
33 | import org.htmlunit.WebRequest;
34 | import org.htmlunit.html.HtmlFileInput;
35 | import org.htmlunit.html.HtmlForm;
36 | import org.htmlunit.html.HtmlPage;
37 | import org.htmlunit.http.HttpStatus;
38 | import org.htmlunit.util.KeyDataPair;
39 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
40 | import org.jenkinsci.plugins.workflow.job.WorkflowJob;
41 | import org.jenkinsci.plugins.workflow.job.WorkflowRun;
42 | import org.junit.jupiter.api.Test;
43 | import org.junit.jupiter.api.io.TempDir;
44 | import org.jvnet.hudson.test.Issue;
45 | import org.jvnet.hudson.test.JenkinsRule;
46 | import org.jvnet.hudson.test.MockAuthorizationStrategy;
47 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
48 |
49 | import java.io.ByteArrayInputStream;
50 | import java.io.File;
51 | import java.net.URL;
52 | import java.nio.charset.StandardCharsets;
53 | import java.nio.file.Files;
54 | import java.util.Collections;
55 |
56 | import static org.hamcrest.MatcherAssert.assertThat;
57 | import static org.hamcrest.Matchers.is;
58 | import static org.junit.jupiter.api.Assertions.assertNotNull;
59 |
60 | @WithJenkins
61 | class AbstractFileParameterDefinitionTest {
62 |
63 | @TempDir
64 | private File tmp;
65 |
66 | @Test
67 | void gui(JenkinsRule r) throws Exception {
68 | r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
69 | r.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().grant(Jenkins.ADMINISTER).everywhere().to("admin"));
70 | WorkflowJob p = r.createProject(WorkflowJob.class, "myjob");
71 | p.addProperty(new ParametersDefinitionProperty(new Base64FileParameterDefinition("FILE")));
72 | p.setDefinition(new CpsFlowDefinition("echo(/received $FILE_FILENAME: $FILE/)", true));
73 | File f = Files.createFile(new File(tmp, "myfile.txt").toPath()).toFile();
74 | FileUtils.write(f, "uploaded content here", StandardCharsets.UTF_8);
75 | JenkinsRule.WebClient wc = r.createWebClient().login("admin");
76 | wc.setThrowExceptionOnFailingStatusCode(false);
77 | wc.setRedirectEnabled(true);
78 | HtmlPage parametersPage = wc.goTo("job/myjob/build?delay=0sec");
79 | assertThat(parametersPage.getWebResponse().getStatusCode(), is(HttpStatus.METHOD_NOT_ALLOWED_405));
80 | HtmlForm form = parametersPage.getFormByName("parameters");
81 | HtmlFileInput file = form.getInputByName("file");
82 | file.setValue(f.getAbsolutePath());
83 | assertThat(r.submit(form).getWebResponse().getStatusCode(), is(HttpStatus.OK_200)); // 303 myjob/build → myjob/
84 | r.waitUntilNoActivity();
85 | WorkflowRun b = p.getBuildByNumber(1);
86 | assertNotNull(b);
87 | r.assertLogContains("received myfile.txt: dXBsb2FkZWQgY29udGVudCBoZXJl", b);
88 | }
89 |
90 | // adapted from BuildCommandTest.fileParameter
91 | @Test
92 | void cli(JenkinsRule r) throws Exception {
93 | WorkflowJob p = r.createProject(WorkflowJob.class, "myjob");
94 | p.addProperty(new ParametersDefinitionProperty(new Base64FileParameterDefinition("FILE")));
95 | p.setDefinition(new CpsFlowDefinition("echo(/received $env.FILE_FILENAME: $FILE/)", true));
96 | assertThat(new CLICommandInvoker(r, "build").
97 | withStdin(new ByteArrayInputStream("uploaded content here".getBytes())).
98 | invokeWithArgs("-f", "-p", "FILE=", "myjob"),
99 | CLICommandInvoker.Matcher.succeeded());
100 | WorkflowRun b = p.getBuildByNumber(1);
101 | assertNotNull(b);
102 | r.assertLogContains("received null: dXBsb2FkZWQgY29udGVudCBoZXJl", b);
103 | }
104 |
105 | @Test
106 | void rest(JenkinsRule r) throws Exception {
107 | r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
108 | r.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().grant(Jenkins.ADMINISTER).everywhere().to("admin"));
109 | WorkflowJob p = r.createProject(WorkflowJob.class, "myjob");
110 | p.addProperty(new ParametersDefinitionProperty(new Base64FileParameterDefinition("FILE")));
111 | p.setDefinition(new CpsFlowDefinition("echo(/received $FILE_FILENAME: $FILE/)", true));
112 | // Like: curl -u $auth -F FILE=@/tmp/f $jenkins/job/myjob/buildWithParameters
113 | WebRequest req = new WebRequest(new URL(r.getURL() + "job/myjob/buildWithParameters"), HttpMethod.POST);
114 | File f = File.createTempFile("junit", null, tmp);
115 | FileUtils.write(f, "uploaded content here", StandardCharsets.UTF_8);
116 | req.setEncodingType(FormEncodingType.MULTIPART);
117 | req.setRequestParameters(Collections.singletonList(new KeyDataPair("FILE", f, "myfile.txt", "text/plain", StandardCharsets.UTF_8)));
118 | r.createWebClient().withBasicApiToken("admin").getPage(req);
119 | r.waitUntilNoActivity();
120 | WorkflowRun b = p.getBuildByNumber(1);
121 | assertNotNull(b);
122 | r.assertLogContains("received myfile.txt: dXBsb2FkZWQgY29udGVudCBoZXJl", b);
123 | }
124 |
125 | @Issue("https://github.com/jenkinsci/file-parameters-plugin/issues/26")
126 | @Test
127 | void restMissingValue(JenkinsRule r) throws Exception {
128 | r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
129 | r.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().grant(Jenkins.ADMINISTER).everywhere().to("admin"));
130 | WorkflowJob p = r.createProject(WorkflowJob.class, "myjob");
131 | p.addProperty(new ParametersDefinitionProperty(new Base64FileParameterDefinition("FILE")));
132 | p.setDefinition(new CpsFlowDefinition("echo(/received $env.FILE_FILENAME: $env.FILE/)", true));
133 | // Like: curl -u $auth $jenkins/job/myjob/buildWithParameters
134 | WebRequest req = new WebRequest(new URL(r.getURL() + "job/myjob/buildWithParameters"), HttpMethod.POST);
135 | r.createWebClient().withBasicApiToken("admin").getPage(req);
136 | r.waitUntilNoActivity();
137 | WorkflowRun b = p.getBuildByNumber(1);
138 | assertNotNull(b);
139 | r.assertLogContains("received null: null", b);
140 | }
141 |
142 | @Test
143 | void buildStep(JenkinsRule r) throws Exception {
144 | WorkflowJob us = r.createProject(WorkflowJob.class, "us");
145 | us.setDefinition(new CpsFlowDefinition("build job: 'ds', parameters: [base64File(name: 'FILE', base64: Base64.encoder.encodeToString('a message'.bytes))]", true));
146 | WorkflowJob ds = r.createProject(WorkflowJob.class, "ds");
147 | ds.addProperty(new ParametersDefinitionProperty(new Base64FileParameterDefinition("FILE")));
148 | ds.setDefinition(new CpsFlowDefinition("echo(/got ${new String(Base64.decoder.decode(FILE))}/)", true));
149 | r.buildAndAssertSuccess(us);
150 | WorkflowRun b = ds.getBuildByNumber(1);
151 | assertNotNull(b);
152 | r.assertLogContains("got a message", b);
153 | }
154 |
155 | }
156 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/file_parameters/FileParameterWrapperTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2020 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package io.jenkins.plugins.file_parameters;
26 |
27 | import hudson.cli.CLICommandInvoker;
28 | import hudson.model.Failure;
29 | import hudson.model.ParameterDefinition;
30 | import hudson.model.ParametersDefinitionProperty;
31 | import hudson.model.Result;
32 | import org.apache.commons.io.FileUtils;
33 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
34 | import org.jenkinsci.plugins.workflow.job.WorkflowJob;
35 | import org.jenkinsci.plugins.workflow.job.WorkflowRun;
36 | import org.junit.jupiter.api.Test;
37 | import org.jvnet.hudson.test.Issue;
38 | import org.jvnet.hudson.test.JenkinsRule;
39 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
40 |
41 | import java.io.ByteArrayInputStream;
42 | import java.io.File;
43 |
44 | import static org.hamcrest.MatcherAssert.assertThat;
45 | import static org.junit.jupiter.api.Assertions.assertEquals;
46 | import static org.junit.jupiter.api.Assertions.assertFalse;
47 | import static org.junit.jupiter.api.Assertions.assertNotNull;
48 |
49 | @WithJenkins
50 | class FileParameterWrapperTest {
51 |
52 | @Test
53 | void base64(JenkinsRule r) throws Exception {
54 | r.createSlave("remote", null, null);
55 | WorkflowJob p = r.createProject(WorkflowJob.class, "myjob");
56 | p.addProperty(new ParametersDefinitionProperty(new Base64FileParameterDefinition("FILE")));
57 | String pipeline = """
58 | node('remote') {
59 | withFileParameter('FILE') {
60 | echo(/loaded '${readFile(FILE).toUpperCase(Locale.ROOT)}' from $FILE/)
61 | }
62 | }""";
63 | p.setDefinition(new CpsFlowDefinition(pipeline, true));
64 | assertThat(new CLICommandInvoker(r, "build").
65 | withStdin(new ByteArrayInputStream("uploaded content here".getBytes())).
66 | invokeWithArgs("-f", "-p", "FILE=", "myjob"),
67 | CLICommandInvoker.Matcher.succeeded());
68 | WorkflowRun b = p.getBuildByNumber(1);
69 | assertNotNull(b);
70 | r.assertLogContains("loaded 'UPLOADED CONTENT HERE' from ", b);
71 | }
72 |
73 | @Test
74 | void base64UndefinedFail(JenkinsRule r) throws Exception {
75 | r.createSlave("remote", null, null);
76 | WorkflowJob p = r.createProject(WorkflowJob.class, "myjob");
77 | p.addProperty(new ParametersDefinitionProperty(new Base64FileParameterDefinition("FILE")));
78 | String pipeline = """
79 | pipeline {
80 | agent any
81 | parameters {
82 | base64File 'FILE'
83 | }
84 | stages {
85 | stage('Example') {
86 | steps {
87 | withFileParameter('FILE') {
88 | echo('foo')
89 | }
90 | }
91 | }
92 | }
93 | }""";
94 | p.setDefinition(new CpsFlowDefinition(pipeline, true));
95 | WorkflowRun run = p.scheduleBuild2(0).get();
96 | r.waitForCompletion(run);
97 | r.assertBuildStatus(Result.FAILURE, run);
98 | r.assertLogContains("No parameter named FILE", run);
99 | }
100 |
101 | @Test
102 | void base64WithAllowNoFile(JenkinsRule r) throws Exception {
103 | r.createSlave("remote", null, null);
104 | WorkflowJob p = r.createProject(WorkflowJob.class, "myjob");
105 | p.addProperty(new ParametersDefinitionProperty(new Base64FileParameterDefinition("FILE")));
106 | String pipeline = """
107 | pipeline {
108 | agent any
109 | parameters {
110 | base64File 'FILE'
111 | }
112 | stages {
113 | stage('Example') {
114 | steps {
115 | withFileParameter(name:'FILE', allowNoFile: true) {
116 | echo('foo')
117 | }
118 | }
119 | }
120 | }
121 | }""";
122 | p.setDefinition(new CpsFlowDefinition(pipeline, true));
123 | WorkflowRun run = p.scheduleBuild2(0).get();
124 | r.waitForCompletion(run);
125 | r.assertBuildStatus(Result.SUCCESS, run);
126 | r.assertLogContains("foo", run);
127 | r.assertLogContains("Skip file parameter as there is no parameter with name: 'FILE'", run);
128 | }
129 |
130 | @Test
131 | void base64DeclarativeParameterCreated(JenkinsRule r) throws Exception {
132 | r.createSlave("remote", null, null);
133 | WorkflowJob p = r.createProject(WorkflowJob.class, "myjob");
134 |
135 | String pipeline = """
136 | pipeline {
137 | agent any
138 | parameters {
139 | base64File 'FILE'
140 | }
141 | stages {
142 | stage('Example') {
143 | steps {
144 | withFileParameter('FILE') {
145 | echo(/loaded '${readFile(FILE).toUpperCase(Locale.ROOT)}' from $FILE/)
146 | }
147 | }
148 | }
149 | }
150 | }""";
151 |
152 | p.setDefinition(new CpsFlowDefinition(pipeline, true));
153 | WorkflowRun run = p.scheduleBuild2(0).get();
154 | r.waitForCompletion(run);
155 | // definitely will fail but we just ensure parameter has been created
156 | r.assertBuildStatus(Result.FAILURE, run);
157 | ParametersDefinitionProperty pdp = p.getProperty(ParametersDefinitionProperty.class);
158 | assertNotNull(pdp, "parameters definition property is null");
159 | ParameterDefinition pd = pdp.getParameterDefinition( "FILE");
160 | assertNotNull(pd, "parameters definition is null");
161 | assertEquals(Base64FileParameterDefinition.class, pd.getClass(), "parameter not type Base64FileParameterDefinition");
162 |
163 | assertThat(new CLICommandInvoker(r, "build").
164 | withStdin(new ByteArrayInputStream("uploaded content here".getBytes())).
165 | invokeWithArgs("-f", "-p", "FILE=", "myjob"),
166 | CLICommandInvoker.Matcher.succeeded());
167 | WorkflowRun b = p.getBuildByNumber(2);
168 | assertNotNull(b);
169 | r.assertLogContains("loaded 'UPLOADED CONTENT HERE'", b);
170 | }
171 |
172 | @Test
173 | void stashed(JenkinsRule r) throws Exception {
174 | r.createSlave("remote", null, null);
175 | WorkflowJob p = r.createProject(WorkflowJob.class, "myjob");
176 | p.addProperty(new ParametersDefinitionProperty(new StashedFileParameterDefinition("FILE")));
177 | String pipeline = """
178 | node('remote') {
179 | withFileParameter('FILE') {
180 | echo(/loaded '${readFile(FILE).toUpperCase(Locale.ROOT)}' from $FILE/)
181 | }
182 | }""";
183 | p.setDefinition(new CpsFlowDefinition(pipeline, true));
184 | assertThat(new CLICommandInvoker(r, "build").
185 | withStdin(new ByteArrayInputStream("uploaded content here".getBytes())).
186 | invokeWithArgs("-f", "-p", "FILE=", "myjob"),
187 | CLICommandInvoker.Matcher.succeeded());
188 | WorkflowRun b = p.getBuildByNumber(1);
189 | assertNotNull(b);
190 | r.assertLogContains("loaded 'UPLOADED CONTENT HERE'", b);
191 | }
192 |
193 | @Test
194 | void stashedDeclarativeParameterCreated(JenkinsRule r) throws Exception {
195 | r.createSlave("remote", null, null);
196 | WorkflowJob p = r.createProject(WorkflowJob.class, "myjob");
197 |
198 | String pipeline = """
199 | pipeline {
200 | agent any
201 | parameters {
202 | stashedFile 'FILE'
203 | }
204 | stages {
205 | stage('Example') {
206 | steps {
207 | withFileParameter('FILE') {
208 | echo(/loaded '${readFile(FILE).toUpperCase(Locale.ROOT)}'/)
209 | }
210 | }
211 | }
212 | }
213 | }""";
214 |
215 | p.setDefinition(new CpsFlowDefinition(pipeline, true));
216 | WorkflowRun run = p.scheduleBuild2(0).get();
217 | r.waitForCompletion(run);
218 | // definitely will fail but we just ensure parameter has been created
219 | r.assertBuildStatus(Result.FAILURE, run);
220 | ParametersDefinitionProperty pdp = p.getProperty(ParametersDefinitionProperty.class);
221 | assertNotNull(pdp, "parameters definition property is null");
222 | ParameterDefinition pd = pdp.getParameterDefinition("FILE");
223 | assertNotNull(pd, "parameters definition is null");
224 | assertEquals(StashedFileParameterDefinition.class, pd.getClass(), "parameter not type Base64FileParameterDefinition");
225 |
226 | assertThat(new CLICommandInvoker(r, "build").
227 | withStdin(new ByteArrayInputStream("uploaded content here".getBytes())).
228 | invokeWithArgs("-f", "-p", "FILE=", "myjob"),
229 | CLICommandInvoker.Matcher.succeeded());
230 | WorkflowRun b = p.getBuildByNumber(2);
231 | assertNotNull(b);
232 | r.assertLogContains("loaded 'UPLOADED CONTENT HERE'", b);
233 | }
234 |
235 | @Issue("SECURITY-3123")
236 | @Test
237 | void stashMaliciousFilename(JenkinsRule r) throws Exception {
238 | String hack = "../../../../../../../../../../../../../../../../../../../../../tmp/file-parameters-plugin-SECURITY-3123";
239 | File result = new File("/tmp/file-parameters-plugin-SECURITY-3123");
240 | FileUtils.deleteQuietly(result);
241 | WorkflowJob p = r.createProject(WorkflowJob.class, "p");
242 | try {
243 | p.addProperty(new ParametersDefinitionProperty(new StashedFileParameterDefinition(hack)));
244 | } catch (Failure x) {
245 | return; // good
246 | }
247 | p.setDefinition(new CpsFlowDefinition("", true));
248 | assertThat(new CLICommandInvoker(r, "build").
249 | withStdin(new ByteArrayInputStream("malicious content here".getBytes())).
250 | invokeWithArgs("-f", "-p", hack + "=", "p"),
251 | CLICommandInvoker.Matcher.succeeded());
252 | WorkflowRun b = p.getBuildByNumber(1);
253 | assertNotNull(b);
254 | assertFalse(result.isFile());
255 | }
256 |
257 | @Test
258 | void shortParameterName(JenkinsRule r) throws Exception {
259 | r.createSlave("remote", null, null);
260 | WorkflowJob p = r.createProject(WorkflowJob.class, "p");
261 | p.addProperty(new ParametersDefinitionProperty(new Base64FileParameterDefinition("F")));
262 | String pipeline = """
263 | node('remote') {
264 | withFileParameter('F') {
265 | echo(/loaded '${readFile(F).toUpperCase(Locale.ROOT)}' from $F/)
266 | }
267 | }""";
268 | p.setDefinition(new CpsFlowDefinition(pipeline, true));
269 | assertThat(new CLICommandInvoker(r, "build").
270 | withStdin(new ByteArrayInputStream("uploaded content here".getBytes())).
271 | invokeWithArgs("-f", "-p", "F=", "p"),
272 | CLICommandInvoker.Matcher.succeeded());
273 | WorkflowRun b = p.getBuildByNumber(1);
274 | assertNotNull(b);
275 | r.assertLogContains("loaded 'UPLOADED CONTENT HERE' from ", b);
276 | }
277 |
278 | }
279 |
--------------------------------------------------------------------------------
/src/test/java/io/jenkins/plugins/file_parameters/RestartTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2024 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package io.jenkins.plugins.file_parameters;
26 |
27 | import hudson.ExtensionList;
28 | import hudson.model.Node;
29 | import hudson.model.ParametersDefinitionProperty;
30 | import hudson.model.Queue;
31 | import hudson.model.queue.CauseOfBlockage;
32 | import hudson.model.queue.QueueTaskDispatcher;
33 | import jenkins.model.Jenkins;
34 | import org.apache.commons.io.FileUtils;
35 | import org.htmlunit.FormEncodingType;
36 | import org.htmlunit.HttpMethod;
37 | import org.htmlunit.WebRequest;
38 | import org.htmlunit.util.KeyDataPair;
39 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
40 | import org.jenkinsci.plugins.workflow.job.WorkflowJob;
41 | import org.jenkinsci.plugins.workflow.job.WorkflowRun;
42 | import org.junit.ClassRule;
43 | import org.junit.Rule;
44 | import org.junit.Test;
45 | import org.junit.rules.TemporaryFolder;
46 | import org.jvnet.hudson.test.BuildWatcher;
47 | import org.jvnet.hudson.test.Issue;
48 | import org.jvnet.hudson.test.JenkinsSessionRule;
49 | import org.jvnet.hudson.test.MockAuthorizationStrategy;
50 | import org.jvnet.hudson.test.TestExtension;
51 |
52 | import java.io.File;
53 | import java.net.URL;
54 | import java.util.Collections;
55 |
56 | import static org.junit.Assert.assertNotNull;
57 |
58 | @Issue("JENKINS-73161")
59 | public final class RestartTest {
60 |
61 | @ClassRule public static BuildWatcher buildWatcher = new BuildWatcher();
62 |
63 | @Rule public final JenkinsSessionRule rr = new JenkinsSessionRule();
64 |
65 | @Rule public TemporaryFolder tmp = new TemporaryFolder();
66 |
67 | /** @see AbstractFileParameterDefinitionTest#rest */
68 | @Test public void restBase64() throws Throwable {
69 | rr.then(r -> {
70 | r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
71 | r.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().grant(Jenkins.ADMINISTER).everywhere().to("admin"));
72 | WorkflowJob p = r.createProject(WorkflowJob.class, "p");
73 | p.addProperty(new ParametersDefinitionProperty(new Base64FileParameterDefinition("FILE")));
74 | p.setDefinition(new CpsFlowDefinition("echo(/received $FILE_FILENAME: $FILE/)", true));
75 | WebRequest req = new WebRequest(new URL(r.getURL() + "job/p/buildWithParameters"), HttpMethod.POST);
76 | File f = tmp.newFile();
77 | FileUtils.write(f, "uploaded content here", "UTF-8");
78 | req.setEncodingType(FormEncodingType.MULTIPART);
79 | req.setRequestParameters(Collections.singletonList(new KeyDataPair("FILE", f, "myfile.txt", "text/plain", "UTF-8")));
80 | r.createWebClient().withBasicApiToken("admin").getPage(req);
81 | });
82 | rr.then(r -> {
83 | ExtensionList.lookupSingleton(Block.class).ready = true;
84 | WorkflowJob p = r.jenkins.getItemByFullName("p", WorkflowJob.class);
85 | r.waitUntilNoActivity();
86 | WorkflowRun b = p.getBuildByNumber(1);
87 | assertNotNull(b);
88 | r.assertLogContains("received myfile.txt: dXBsb2FkZWQgY29udGVudCBoZXJl", b);
89 | });
90 | }
91 | @TestExtension("restBase64") public static final class Block extends QueueTaskDispatcher {
92 | boolean ready;
93 | @Override public CauseOfBlockage canTake(Node node, Queue.BuildableItem item) {
94 | return ready ? null : new CauseOfBlockage.BecauseNodeIsBusy(node);
95 | }
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------