├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── cd.yaml │ ├── dependabot-automerge.yml │ └── jenkins-security-scan.yml ├── .gitignore ├── .mvn ├── extensions.xml └── maven.config ├── Jenkinsfile ├── LICENSE.md ├── README.md ├── pom.xml └── src ├── main ├── java │ └── io │ │ └── jenkins │ │ └── plugins │ │ └── file_parameters │ │ ├── AbstractFileParameterDefinition.java │ │ ├── AbstractFileParameterValue.java │ │ ├── Base64FileParameterDefinition.java │ │ ├── Base64FileParameterValue.java │ │ ├── FileParameterWrapper.java │ │ ├── StashedFileParameterDefinition.java │ │ └── StashedFileParameterValue.java └── resources │ ├── index.jelly │ └── io │ └── jenkins │ └── plugins │ └── file_parameters │ ├── AbstractFileParameterDefinition │ ├── config.jelly │ ├── help-name.html │ └── index.jelly │ ├── AbstractFileParameterValue │ └── value.jelly │ ├── Base64FileParameterDefinition │ └── help.html │ ├── FileParameterWrapper │ ├── config.jelly │ ├── help-allowNoFile.html │ ├── help-name.html │ └── help.html │ └── StashedFileParameterDefinition │ └── help.html └── test └── java └── io └── jenkins └── plugins └── file_parameters ├── AbstractFileParameterDefinitionTest.java ├── FileParameterWrapperTest.java └── RestartTest.java /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jenkinsci/file-parameters-plugin-developers 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: "maven" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | -------------------------------------------------------------------------------- /.github/workflows/cd.yaml: -------------------------------------------------------------------------------- 1 | # Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins 2 | 3 | name: cd 4 | on: 5 | workflow_dispatch: 6 | check_run: 7 | types: 8 | - completed 9 | 10 | jobs: 11 | maven-cd: 12 | uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@v1 13 | secrets: 14 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} 15 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }} 16 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-automerge.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions#enable-auto-merge-on-a-pull-request 2 | 3 | name: Dependabot auto-merge 4 | on: pull_request 5 | 6 | permissions: 7 | contents: write 8 | pull-requests: write 9 | 10 | jobs: 11 | dependabot: 12 | runs-on: ubuntu-latest 13 | if: ${{ github.actor == 'dependabot[bot]' }} 14 | steps: 15 | - name: Enable auto-merge for Dependabot PRs 16 | run: gh pr merge --auto --merge "$PR_URL" 17 | env: 18 | PR_URL: ${{github.event.pull_request.html_url}} 19 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 20 | -------------------------------------------------------------------------------- /.github/workflows/jenkins-security-scan.yml: -------------------------------------------------------------------------------- 1 | name: Jenkins Security Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: [ opened, synchronize, reopened ] 9 | workflow_dispatch: 10 | 11 | permissions: 12 | security-events: write 13 | contents: read 14 | actions: read 15 | 16 | jobs: 17 | security-scan: 18 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 19 | with: 20 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. 21 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | work 3 | *.iml 4 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.jenkins.tools.incrementals 4 | git-changelist-maven-extension 5 | 1.8 6 | 7 | 8 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Pconsume-incrementals 2 | -Pmight-produce-incrementals 3 | -Dchangelist.format=%d.v%s 4 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | /* 2 | See the documentation for more options: 3 | https://github.com/jenkins-infra/pipeline-library/ 4 | */ 5 | buildPlugin( 6 | forkCount: '1C', // run this number of tests in parallel for faster feedback. If the number terminates with a 'C', the value will be multiplied by the number of available CPU cores 7 | useContainerAgent: true, // Set to `false` if you need to use Docker for containerized tests 8 | configurations: [ 9 | [platform: 'linux', jdk: 21], 10 | [platform: 'windows', jdk: 17], 11 | ]) 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright 2020 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # File Parameters Plugin 2 | 3 | ## Introduction 4 | 5 | Offers alternative types of file parameter that are compatible with Pipeline and do not suffer from the architectural flaws of the type built into Jenkins core. 6 | 7 | See [JENKINS-27413](https://issues.jenkins-ci.org/browse/JENKINS-27413) and [JENKINS-29289](https://issues.jenkins-ci.org/browse/JENKINS-29289) for background. 8 | 9 | ## Minimal usage 10 | 11 | ### Base64 file parameter 12 | 13 | If you defined a Base64 file parameter named `FILE` in the GUI configuration for a Pipeline project, you can access it in a couple of ways - as a Base64-encoded environment variable: 14 | 15 | ```groovy 16 | node { 17 | sh 'echo $FILE | base64 -d' 18 | } 19 | ``` 20 | 21 | Or via a temporary file with the decoded content: 22 | 23 | ``` 24 | node { 25 | withFileParameter('FILE') { 26 | sh 'cat $FILE' 27 | } 28 | } 29 | ``` 30 | 31 | ### Stashed file parameter 32 | 33 | A stashed file parameter can also be accessed in a couple of ways - as a stash of the same name with a single file of the same name: 34 | 35 | ```groovy 36 | node { 37 | unstash 'FILE' 38 | sh 'cat FILE' 39 | } 40 | ``` 41 | 42 | Or via a temporary file: 43 | 44 | ```groovy 45 | node { 46 | withFileParameter('FILE') { 47 | sh 'cat $FILE' 48 | } 49 | } 50 | ``` 51 | 52 | ## Accessing original upload filename 53 | 54 | Original filename will be stored in evironment in `_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 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 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 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 | --------------------------------------------------------------------------------