feat: Add ability to upload a plugin via URL [JENKINS-4814] (#5862)

Co-authored-by: Tim Jacomb <21194782+timja@users.noreply.github.com>
This commit is contained in:
Alex EarlandTim Jacomb authored and GitHub committed 2021-11-08 20:34:28 +00:00
1 parent 3e8a11aa95
commit 57737eec99
26 files changed
+140 -49

No files matched your search

+80 -5
View File
@@ -76,6 +76,7 @@ import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -1734,6 +1735,51 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
return new HttpRedirect("advanced");
}
interface PluginCopier {
void copy(File target) throws Exception;
void cleanup();
}
class FileUploadPluginCopier implements PluginCopier {
private FileItem fileItem;
FileUploadPluginCopier(FileItem fileItem) {
this.fileItem = fileItem;
}
@Override
public void copy(File target) throws Exception {
fileItem.write(target);
}
@Override
public void cleanup() {
fileItem.delete();
}
}
class UrlPluginCopier implements PluginCopier {
private String url;
UrlPluginCopier(String url) {
this.url = url;
}
@Override
public void copy(File target) throws Exception {
try(InputStream input = ProxyConfiguration.getInputStream(new URL(url))) {
Files.copy(input, target.toPath());
} catch(Exception e) {
throw e;
}
}
@Override
public void cleanup() {
}
}
/**
* Uploads a plugin.
*/
@@ -1742,11 +1788,21 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
try {
Jenkins.get().checkPermission(Jenkins.ADMINISTER);
String fileName = "";
PluginCopier copier;
ServletFileUpload upload = new ServletFileUpload(new DiskFileItemFactory());
List<FileItem> items = upload.parseRequest(req);
if(StringUtils.isNotBlank(items.get(1).getString())) {
// this is a URL deployment
fileName = items.get(1).getString();
copier = new UrlPluginCopier(fileName);
} else {
// this is a file upload
FileItem fileItem = items.get(0);
fileName = Util.getFileName(fileItem.getName());
copier = new FileUploadPluginCopier(fileItem);
}
// Parse the request
FileItem fileItem = upload.parseRequest(req).get(0);
String fileName = Util.getFileName(fileItem.getName());
if("".equals(fileName)){
return new HttpRedirect("advanced");
}
@@ -1762,12 +1818,12 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
// TODO Remove this workaround after FILEUPLOAD-293 is resolved.
t.delete();
fileItem.write(t);
copier.copy(t);
} catch (Exception e) {
// Exception thrown is too generic so at least limit the scope where it can occur
throw new ServletException(e);
}
fileItem.delete();
copier.cleanup();
final String baseName = identifyPluginShortName(t);
@@ -1810,6 +1866,25 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
}
}
@Restricted(NoExternalUse.class)
@RequirePOST public FormValidation doCheckPluginUrl(StaplerRequest request, @QueryParameter String value) throws IOException {
if(StringUtils.isNotBlank(value)) {
try {
URL url = new URL(value);
if(!url.getProtocol().startsWith("http")) {
return FormValidation.error(Messages.PluginManager_invalidUrl());
}
if(!url.getProtocol().equals("https")) {
return FormValidation.warning(Messages.PluginManager_insecureUrl());
}
} catch(MalformedURLException e) {
return FormValidation.error(e.getMessage());
}
}
return FormValidation.ok();
}
@Restricted(NoExternalUse.class)
@RequirePOST public HttpResponse doCheckUpdatesServer() throws IOException {
Jenkins.get().checkPermission(Jenkins.SYSTEM_READ);
@@ -106,6 +106,11 @@ PluginManager.newerVersionEntry=\
See the <a href="{0}" rel="noopener noreferrer" target="_blank">plugin documentation</a> for information about its requirements.
PluginManager.unavailable=Unavailable
PluginManager.deprecationWarning=<strong>This plugin is deprecated.</strong> In general, this means that it is either obsolete, no longer being developed, or may no longer work. <a href="{0}" rel="noopener noreferrer" target="_blank">Learn more.</a>
PluginManager.insecureUrl=\
You are using an insecure URL to download the plugin, use at your own risk!
PluginManager.invalidUrl=\
You are using an invalid URL to download the plugin, only https and http (not recommended) are supported.
AboutJenkins.DisplayName=About Jenkins
AboutJenkins.Description=See the version and license information.
@@ -53,21 +53,20 @@ THE SOFTWARE.
<l:isAdmin>
<section>
<h2>${%Upload Plugin}</h2>
<h2>${%Deploy Plugin}</h2>
<f:form method="post" action="uploadPlugin" name="uploadPlugin" enctype="multipart/form-data">
<f:block>
<div style="margin-bottom: 1em;">
${%uploadtext}
</div>
</f:block>
<f:block>
<!-- @size is for other browsers, @style is for IE -->
${%File}:
<input type="file" name="name" class="setting-input" style="width:80%" size='40'/>
</f:block>
<f:block>
<f:submit value="${%Upload}"/>
</f:block>
<div style="margin-bottom: 1em;">
${%deploytext}
</div>
<f:entry title="${%File}">
<input type="file" name="name" class="setting-input" accept=".hpi,.jpi"/>
</f:entry>
<p>${%Or}</p>
<f:entry title="${%URL}">
<f:textbox name="pluginUrl"
class="setting-input validated" checkUrl="'checkPluginUrl?value='+this.value" />
</f:entry>
<f:submit value="${%Deploy}"/>
</f:form>
</section>
</l:isAdmin>
@@ -20,5 +20,6 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
uploadtext=\
You can upload an .hpi file to install a plugin from outside the central plugin repository.
deploytext=\
You can select a plugin file from your local system or provide a URL to install a plugin from outside the central \
plugin repository.
@@ -30,9 +30,6 @@ Upload=\
\u041A\u0430\u0447\u0432\u0430\u043D\u0435
Upload\ Plugin=\
\u041A\u0430\u0447\u0432\u0430\u043D\u0435 \u043D\u0430 \u043F\u0440\u0438\u0441\u0442\u0430\u0432\u043A\u0430
uploadtext=\
\u041C\u043E\u0436\u0435 \u0434\u0430 \u043A\u0430\u0447\u0438\u0442\u0435 \u0444\u0430\u0439\u043B \u0432\u044A\u0432 \u0444\u043E\u0440\u043C\u0430\u0442 \u201E.hpi\u201C, \u0437\u0430 \u0434\u0430 \u0438\u043D\u0441\u0442\u0430\u043B\u0438\u0440\u0430\u0442\u0435 \u043F\u0440\u0438\u0441\u0442\u0430\u0432\u043A\u0430 \u0438\u0437\u0432\u044A\u043D\
\u043E\u0444\u0438\u0446\u0438\u0430\u043B\u043D\u043E\u0442\u043E \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435.
Other\ Sites=\
\u0414\u0440\u0443\u0433\u0438 \u0441\u0430\u0439\u0442\u043E\u0432\u0435
Update\ Center=\
@@ -27,7 +27,6 @@ User\ name=Brugernavn
File=Fil
URL=URL
lastUpdated=Opdateringsinformation hentet: {0} dage siden
uploadtext=Du kan l\u00E6gge en .hpi fil op for at installere en plugin fra udenfor det centrale plugin lager.
Port=Port
HTTP\ Proxy\ Configuration=HTTP proxykonfiguration
Server=Server
@@ -24,7 +24,6 @@ HTTP\ Proxy\ Configuration=HTTP-Proxy Konfiguration
Upload\ Plugin=Plugin hochladen
File=Datei
Upload=Hochladen
uploadtext=Geben Sie hier den Pfad zu einer lokalen Plugin-Datei (Dateiendung .hpi) an, die installiert werden soll.
Update\ Site=Aktualisierungsserver
URL=URL
Other\ Sites=Andere Aktualisierungsserver
@@ -21,8 +21,6 @@
# THE SOFTWARE.
lastUpdated=Informaci\u00F3n de actualizaci\u00F3n es de hace {0}.
uploadtext=\
Puedes subir un fichero .hpi para instalar un plugin que no est\u00E9 en el repositorio central.
File=Archivo
Upload\ Plugin=Subir un plugin
Upload=Subir
@@ -31,4 +31,3 @@ Upload=Lataa
Upload\ Plugin=Lataa liit\u00E4nn\u00E4inen
User\ name=K\u00E4ytt\u00E4j\u00E4
lastUpdated=P\u00E4ivitystiedot haettu: {0} sitten
uploadtext=Voit asentaa liit\u00E4nn\u00E4isi\u00E4 my\u00F6s lataamalla .hpi-tiedoston.
@@ -26,7 +26,6 @@ File=Fichier
Update\ Site=Site de mise \u00E0 jour
Upload=Soumettre
lastUpdated=Derni\u00E8re mise \u00E0 jour\u00A0: il y a {0}
uploadtext=Vous pouvez t\u00E9l\u00E9verser un fichier .hpi pour installer un plugin ext\u00E9rieur au d\u00E9p\u00F4t centralis\u00E9 de plugin.
Proxy\ Needs\ Authorization=Le proxy n\u00E9cessite une authentification
Server=Serveur
User\ name=Nom d''utilisateur
@@ -6,4 +6,3 @@ Update\ Site=Friss\u00EDt\u00E9si Oldal
Upload=Felt\u00F6lt
Upload\ Plugin=Be\u00E9p\u00FCl\u0151 Felt\u00F6lt\u00E9se
lastUpdated=Friss\u00EDt\u00E9si inform\u00E1ci\u00F3k utolj\u00E1ra lek\u00E9rve: {0} ezel\u0151tt
uploadtext=Felt\u00F6lthet egy .hpi \u00E1llom\u00E1nyt egy be\u00E9p\u00FCl\u0151 telep\u00EDt\u00E9s\u00E9hez, ami nem tal\u00E1lhat\u00F3 meg a k\u00F6zponti be\u00E9p\u00FCl\u0151 gy\u0171jtem\u00E9nyben.
@@ -25,8 +25,6 @@ File=File
HTTP\ Proxy\ Configuration=Configurazione proxy HTTP
Other\ Sites=Altri siti
Upload=Carica
uploadtext=È possibile caricare un file .hpi per installare un plugin non \
incluso nel repository plugin centrale.
Update\ Center=Centro aggiornamenti
Update\ Site=Sito aggiornamenti
Upload\ Plugin=Carica plugin
@@ -24,8 +24,6 @@ HTTP\ Proxy\ Configuration=HTTP Proxy\u306E\u8A2D\u5B9A
Upload\ Plugin=\u30D7\u30E9\u30B0\u30A4\u30F3\u306E\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9
Upload=\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9
File=\u30D5\u30A1\u30A4\u30EB
uploadtext=\
.hpi\u30D5\u30A1\u30A4\u30EB\u3092\u30A2\u30C3\u30D7\u30ED\u30FC\u30C9\u3057\u3066\u3001\u30D7\u30E9\u30B0\u30A4\u30F3\u30EA\u30DD\u30B8\u30C8\u30EA\u4EE5\u5916\u304B\u3089\u30D7\u30E9\u30B0\u30A4\u30F3\u3092\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3067\u304D\u307E\u3059\u3002
Update\ Site=\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8\u30B5\u30A4\u30C8
URL=URL
Update\ Center=\u30A2\u30C3\u30D7\u30C7\u30FC\u30C8\u30BB\u30F3\u30BF\u30FC
@@ -31,4 +31,3 @@ Upload=\uC62C\uB9AC\uAE30
Upload\ Plugin=\uD50C\uB7EC\uADF8\uC778 \uC62C\uB9AC\uAE30
User\ name=\uC0AC\uC6A9\uC790\uBA85
lastUpdated=\uBCC0\uACBD\uC815\uBCF4 \uD68D\uB4DD\uC2DC\uAE30 : {0} \uC804
uploadtext=\uC911\uC559 \uD50C\uB7EC\uADF8\uC778 \uC800\uC7A5\uC18C \uC678\uC758 \uD50C\uB7EC\uADF8\uC778\uC744 \uC124\uCE58\uD558\uAE30\uC704\uD574\uC11C .hpi \uD30C\uC77C\uC744 \uC62C\uB9B4 \uC218 \uC788\uC2B5\uB2C8\uB2E4.
@@ -6,4 +6,3 @@ Update\ Site=Aug\u0161upl\u0101des vietne
Upload=Aug\u0161upl\u0101d\u0113t
Upload\ Plugin=Aug\u0161upl\u0101d\u0113t spraudni
lastUpdated=Atjaunin\u0101jumu inform\u0101cija ieg\u016Bta: pirms {0}
uploadtext=J\u016Bs varat aug\u0161upl\u0101d\u0113t .hpi failu lai uzst\u0101d\u012Btu spraudni \u0101rpus centr\u0101l\u0101 spraud\u0146u repozitorija.
@@ -29,4 +29,3 @@ Upload=Last opp
Upload\ Plugin=Last opp programtillegg
User\ name=Brukernavn
lastUpdated=Oppdateringsinformasjon mottatt for: {0} siden
uploadtext=Du kan laste opp en .hpi fil for \u00E5 installere programtillegg som ikke ligger i sentral programtillegg katalog.
@@ -21,7 +21,6 @@
# THE SOFTWARE.
lastUpdated=Laatst geactualiseerd: {0} geleden
uploadtext=U kunt een .hpi bestand uploaden om een plugin te installeren die niet aanwezig is in de centrale pluginlijst.
Password=Wachtwoord
Server=Server
Port=Poortnummer
@@ -25,5 +25,4 @@ URL=Adres URL
Update\ Site=Strona z aktualizacjami
Upload=Prze\u015Blij
Upload\ Plugin=Prze\u015Blij wtyczk\u0119
uploadtext=Mo\u017Cesz przes\u0142a\u0107 plik .hpi \u017Ceby zainstalowa\u0107 wtyczk\u0119 z poza centralnego repozytorium wtyczek
Update\ Center=Zarz\u0105dzaj Jenkinsem
@@ -21,7 +21,6 @@
# THE SOFTWARE.
lastUpdated=Informa\u00E7\u00E3o de atualiza\u00E7\u00E3o obtida: {0} atr\u00E1s
uploadtext=Voc\u00EA pode fazer o upload de um arquivo .hpi para instalar um plugin fora do reposit\u00F3rio central.
Update\ Site=Site de atualiza\u00E7\u00E3o
File=Arquivo
Upload\ Plugin=Atualizar plugin
@@ -5,4 +5,3 @@ Update\ Site=Atualizar Site
Upload=Enviar
Upload\ Plugin=Enviar Plugin
lastUpdated=Verifica\u00E7\u00E3o de atualiza\u00E7\u00E3o obtida h\u00E1: {0}
uploadtext=Tu podes enviar um ficheiro .hpi para instalar um plugin que n\u00E3o esteja no reposit\u00F3rio de plugins central
@@ -31,4 +31,3 @@ Upload=\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C
Upload\ Plugin=\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u043F\u043B\u0430\u0433\u0438\u043D
User\ name=\u0418\u043C\u044F \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044F
lastUpdated=\u0418\u043D\u0444\u043E\u0440\u043C\u0430\u0446\u0438\u044F \u043E \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0445 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F\u0445 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0430: {0} \u043D\u0430\u0437\u0430\u0434
uploadtext=\u0417\u0434\u0435\u0441\u044C \u0412\u044B \u043C\u043E\u0436\u0435\u0442\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C \u043F\u043B\u0430\u0433\u0438\u043D \u043D\u0435 \u0438\u0437 \u0446\u0435\u043D\u0442\u0440\u0430\u043B\u044C\u043D\u043E\u0433\u043E \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F, \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0432 .hpi-\u0444\u0430\u0439\u043B
@@ -3,8 +3,6 @@
Update\ Center=\u0426\u0435\u043D\u0442\u0430\u0440 \u0437\u0430 \u0430\u0436\u0443\u0440\u0438\u0440\u0430\u045A\u0435
HTTP\ Proxy\ Configuration=\u041F\u043E\u0441\u0442\u0430\u0432\u0459\u0430\u045A\u0435 HTTP Proxy
Upload\ Plugin=\u041E\u0442\u043F\u0440\u0435\u043C\u0438 \u043C\u043E\u0434\u0443\u043B\u0443
uploadtext=\u041C\u043E\u0436\u0435\u0442\u0435 \u043E\u0442\u043F\u0440\u0435\u043C\u0438\u0442\u0438 \u0434\u0430\u0442\u043E\u0442\u0435\u043A\u0443 \u0443 \u0444\u043E\u0440\u043C\u0430\u0442\u0443 ".hpi", \u0434\u0430 \u0431\u0438\u0441\u0442\u0435 \u0438\u043D\u0441\u0442\u0430\u043B\u0438\u0440\u0430\u043B\u0438 \u043C\u043E\u0434\u0443\u043B\u0443 \u0432\u0430\u043D\
\u0446\u0435\u043D\u0442\u0440\u0430\u043B\u043D\u043E\u0433 \u0438\u0437\u0432\u043E\u0440\u0430 \u0437\u0430 \u043C\u043E\u0434\u0443\u043B\u0435.
File=\u0414\u0430\u0442\u043E\u0442\u0435\u043A\u0430
Upload=\u041E\u0442\u043F\u0440\u0435\u043C\u0438
Update\ Site=\u0410\u0436\u0443\u0440\u0438\u0440\u0430\u0458 \u0441\u0442\u0440\u0430\u043D\u0443
@@ -31,4 +31,3 @@ Upload=Ladda upp
Upload\ Plugin=Ladda upp insticksmodul
User\ name=Anv\u00E4ndarnamn
lastUpdated=Information uppdaterad {0} sedan
uploadtext=Du kan ladda upp en .hpi-fil f\u00F6r att installera en insticksmodul som ligger utanf\u00F6r den centrala instickskatalogen.
@@ -22,8 +22,6 @@
HTTP\ Proxy\ Configuration=HTTP Proxy Konfig\u00FCrasyonu
Upload\ Plugin=Eklenti Y\u00FCkle
uploadtext=\
Merkezi eklenti repository''si d\u0131\u015F\u0131nda bir eklenti eklemek i\u00E7in .hpi dosyas\u0131n\u0131 y\u00FCklemeniz yeterli olacakt\u0131r.
File=Dosya
Update\ Site=G\u00FCncelleme sitesi
Upload=Y\u00FCkle
@@ -26,7 +26,6 @@ Update\ Center=\u66F4\u65B0\u4E2D\u5FC3
HTTP\ Proxy\ Configuration=HTTP Proxy\u8A2D\u5B9A
Upload\ Plugin=\u4E0A\u50B3\u5916\u639B\u7A0B\u5F0F
uploadtext=\u60A8\u53EF\u4EE5\u624B\u52D5\u4E0A\u50B3 .hpi \u6A94\u6848\u4F86\u5B89\u88DD\u4E0D\u5728\u4E2D\u592E\u5132\u5B58\u5EAB\u4E0A\u7684\u5916\u639B\u7A0B\u5F0F\u3002
File=\u6A94\u6848
Upload=\u4E0A\u50B3
@@ -35,6 +35,7 @@ import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import hudson.PluginManager.UberClassLoader;
import hudson.model.Hudson;
import hudson.model.RootAction;
import hudson.model.UpdateCenter;
import hudson.model.UpdateCenter.UpdateCenterJob;
import hudson.model.UpdateSite;
@@ -52,6 +53,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Future;
import javax.servlet.ServletException;
import jenkins.ClassLoaderReflectionToolkit;
import jenkins.RestartRequiredException;
import jenkins.model.GlobalConfiguration;
@@ -67,9 +69,12 @@ import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.MockAuthorizationStrategy;
import org.jvnet.hudson.test.TestExtension;
import org.jvnet.hudson.test.Url;
import org.jvnet.hudson.test.recipes.WithPlugin;
import org.jvnet.hudson.test.recipes.WithPluginManager;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
/**
* @author Kohsuke Kawaguchi
@@ -109,6 +114,40 @@ public class PluginManagerTest {
// uploaded legacy plugins get renamed to *.jpi
assertTrue( new File(r.jenkins.getRootDir(),"plugins/legacy.jpi").exists() );
}
@Test public void deployJpiFromUrl() throws Exception {
HtmlPage page = r.createWebClient().goTo("pluginManager/advanced");
HtmlForm f = page.getFormByName("uploadPlugin");
f.getInputByName("pluginUrl").setValueAttribute(Jenkins.get().getRootUrl() + "pluginManagerGetPlugin/htmlpublisher.jpi");
r.submit(f);
assertTrue( new File(r.jenkins.getRootDir(),"plugins/htmlpublisher.jpi").exists() );
}
@TestExtension("deployJpiFromUrl")
public static final class ReturnPluginJpiAction implements RootAction {
@Override
public String getIconFileName() {
return "gear2.png";
}
@Override
public String getDisplayName() {
return "URL to retrieve a plugin jpi";
}
@Override
public String getUrlName() {
return "pluginManagerGetPlugin";
}
public void doDynamic(StaplerRequest staplerRequest, StaplerResponse staplerResponse) throws ServletException, IOException {
staplerResponse.setContentType("application/octet");
staplerResponse.setStatus(200);
staplerResponse.serveFile(staplerRequest, PluginManagerTest.class.getClassLoader().getResource("plugins/htmlpublisher.jpi"));
}
}
/**
* Tests the effect of {@link WithPlugin}.