Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Merge pull request #44 from ikedam/feature/JENKINS-24626_DownstreamOf
[JENKINS-24626] Added a selector "Downstream of"
- Loading branch information
Showing
with
1,000 additions
and 0 deletions.
- +259 −0 src/main/java/hudson/plugins/copyartifact/DownstreamBuildSelector.java
- +6 −0 src/main/resources/hudson/plugins/copyartifact/BuildSelectorParameter/help.html
- +7 −0 src/main/resources/hudson/plugins/copyartifact/BuildSelectorParameter/help_de.html
- +5 −0 src/main/resources/hudson/plugins/copyartifact/BuildSelectorParameter/help_ja.html
- +7 −0 src/main/resources/hudson/plugins/copyartifact/CopyArtifact/help-parameters.html
- +32 −0 src/main/resources/hudson/plugins/copyartifact/DownstreamBuildSelector/config.jelly
- +24 −0 src/main/resources/hudson/plugins/copyartifact/DownstreamBuildSelector/config_ja.properties
- +5 −0 src/main/resources/hudson/plugins/copyartifact/DownstreamBuildSelector/help-upstreamBuildNumber.html
- +5 −0 ...in/resources/hudson/plugins/copyartifact/DownstreamBuildSelector/help-upstreamBuildNumber_ja.html
- +11 −0 src/main/resources/hudson/plugins/copyartifact/DownstreamBuildSelector/help-upstreamProjectName.html
- +10 −0 ...in/resources/hudson/plugins/copyartifact/DownstreamBuildSelector/help-upstreamProjectName_ja.html
- +5 −0 src/main/resources/hudson/plugins/copyartifact/Messages.properties
- +10 −0 src/main/resources/hudson/plugins/copyartifact/Messages_ja.properties
- +614 −0 src/test/java/hudson/plugins/copyartifact/DownstreamBuildSelectorTest.java
@@ -0,0 +1,259 @@ | ||
/* | ||
* The MIT License | ||
* | ||
* Copyright (c) 2014 IKEDA Yasuyuki | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* of this software and associated documentation files (the "Software"), to deal | ||
* in the Software without restriction, including without limitation the rights | ||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
* copies of the Software, and to permit persons to whom the Software is | ||
* furnished to do so, subject to the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be included in | ||
* all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
* THE SOFTWARE. | ||
*/ | ||
|
||
package hudson.plugins.copyartifact; | ||
|
||
import java.util.logging.Logger; | ||
|
||
import jenkins.model.Jenkins; | ||
|
||
import org.apache.commons.lang.StringUtils; | ||
import org.kohsuke.stapler.AncestorInPath; | ||
import org.kohsuke.stapler.DataBoundConstructor; | ||
import org.kohsuke.stapler.QueryParameter; | ||
|
||
import hudson.EnvVars; | ||
import hudson.Extension; | ||
import hudson.model.Item; | ||
import hudson.model.AbstractBuild; | ||
import hudson.model.AbstractProject; | ||
import hudson.model.Descriptor; | ||
import hudson.model.Job; | ||
import hudson.model.Run; | ||
import hudson.util.FormValidation; | ||
|
||
/** | ||
* Select a build which is a downstream of a specified build. | ||
*/ | ||
public class DownstreamBuildSelector extends BuildSelector { | ||
private static final Logger LOGGER = Logger.getLogger(DownstreamBuildSelector.class.getName()); | ||
private static final String COPIER_PROJECT_KEY = "___COPIER_PROJECT_KEY___"; | ||
private final String upstreamProjectName; | ||
private final String upstreamBuildNumber; | ||
|
||
/** | ||
* @param upstreamProjectName | ||
* @param upstreamBuildNumber | ||
*/ | ||
@DataBoundConstructor | ||
public DownstreamBuildSelector(String upstreamProjectName, String upstreamBuildNumber) { | ||
this.upstreamProjectName = StringUtils.trim(upstreamProjectName); | ||
this.upstreamBuildNumber = StringUtils.trim(upstreamBuildNumber); | ||
} | ||
|
||
/** | ||
* @return upstream project name. May include variable expression. | ||
*/ | ||
public String getUpstreamProjectName() { | ||
return upstreamProjectName; | ||
} | ||
|
||
/** | ||
* @return upstream build number. May include variable expression. | ||
*/ | ||
public String getUpstreamBuildNumber() { | ||
return upstreamBuildNumber; | ||
} | ||
|
||
@Override | ||
public Run<?, ?> getBuild(Job<?, ?> job, EnvVars env, BuildFilter filter, Run<?, ?> parent) { | ||
EnvVars extendedEnv = new EnvVars(env); | ||
// Workaround to pass who is copier to isSelectable(). | ||
extendedEnv.put(COPIER_PROJECT_KEY, parent.getParent().getFullName()); | ||
return super.getBuild(job, extendedEnv, filter, parent); | ||
} | ||
|
||
@Override | ||
protected boolean isSelectable(Run<?, ?> run, EnvVars env) { | ||
if (!(run instanceof AbstractBuild<?,?>)) { | ||
LOGGER.warning(String.format("Only applicable to AbstractBuild: but is %s.", run.getClass().getName())); | ||
return false; | ||
} | ||
|
||
// Workaround to retrieve who is copying. | ||
Job<?,?> copier = Jenkins.getInstance().getItemByFullName(env.get(COPIER_PROJECT_KEY), Job.class); | ||
if (copier != null && (copier instanceof AbstractProject<?,?>)) { | ||
copier = ((AbstractProject<?,?>)copier).getRootProject(); | ||
} | ||
|
||
String projectName = env.expand(getUpstreamProjectName()); | ||
String buildNumber = env.expand(getUpstreamBuildNumber()); | ||
|
||
if (StringUtils.isBlank(projectName)) { | ||
LOGGER.warning("Upstream project name gets empty."); | ||
return false; | ||
} | ||
|
||
if (StringUtils.isBlank(buildNumber)) { | ||
LOGGER.warning("Upstream build number gets empty."); | ||
return false; | ||
} | ||
|
||
AbstractProject<?,?> upstreamProject = Jenkins.getInstance().getItem( | ||
projectName, | ||
copier, | ||
AbstractProject.class | ||
); | ||
if (upstreamProject == null || !upstreamProject.hasPermission(Item.READ)) { | ||
LOGGER.warning(String.format("Upstream project '%s' is not found.", projectName)); | ||
return false; | ||
} | ||
AbstractBuild<?,?> upstreamBuild = ((AbstractBuild<?,?>)run).getUpstreamRelationshipBuild(upstreamProject); | ||
if (upstreamBuild == null || !upstreamBuild.hasPermission(Item.READ)) { | ||
LOGGER.fine(String.format("No upstream build of project '%s' is found for build %s-%s.", upstreamProject.getFullName(), run.getParent().getFullName(), run.getDisplayName())); | ||
return false; | ||
} | ||
|
||
try { | ||
int number = Integer.parseInt(buildNumber); | ||
if (number == upstreamBuild.getNumber()) { | ||
// build number matches. | ||
return true; | ||
} | ||
} catch (NumberFormatException e) { | ||
// Ignore. Nothing to do. | ||
} | ||
|
||
if (buildNumber.equals(upstreamBuild.getId()) || buildNumber.equals(upstreamBuild.getDisplayName())) { | ||
// id or display name matches. | ||
return true; | ||
} | ||
|
||
LOGGER.fine(String.format("build %s-%s doesn't match %s.", run.getParent().getFullName(), run.getDisplayName(), buildNumber)); | ||
return false; | ||
} | ||
|
||
@Extension | ||
public static final class DescriptorImpl extends Descriptor<BuildSelector> { | ||
@Override | ||
public String getDisplayName() { | ||
return Messages.DownstreamBuildSelector_DisplayName(); | ||
} | ||
|
||
/** | ||
* @param str | ||
* @return whether a value contains variable expressions. | ||
*/ | ||
protected boolean containsVariable(String str) { | ||
return !StringUtils.isBlank(str) && str.indexOf('$') >= 0; | ||
} | ||
|
||
/** | ||
* Validates a form input to "Upstream Project Name" | ||
* | ||
* @return | ||
*/ | ||
public FormValidation doCheckUpstreamProjectName( | ||
@AncestorInPath AbstractProject<?,?> project, | ||
@QueryParameter String upstreamProjectName | ||
) { | ||
upstreamProjectName = StringUtils.trim(upstreamProjectName); | ||
if (StringUtils.isBlank(upstreamProjectName)) { | ||
return FormValidation.error(Messages.DownstreamBuildSelector_UpstreamProjectName_Required()); | ||
} | ||
|
||
if (containsVariable(upstreamProjectName)) { | ||
return FormValidation.ok(); | ||
} | ||
|
||
AbstractProject<?,?> upstreamProject = Jenkins.getInstance().getItem( | ||
upstreamProjectName, project.getRootProject(), AbstractProject.class | ||
); | ||
if (upstreamProject == null || !upstreamProject.hasPermission(Item.READ)) { | ||
return FormValidation.error(Messages.DownstreamBuildSelector_UpstreamProjectName_NotFound()); | ||
} | ||
return FormValidation.ok(); | ||
} | ||
|
||
/** | ||
* Validates a form input to "Upstream Build Number" | ||
* | ||
* @return | ||
*/ | ||
public FormValidation doCheckUpstreamBuildNumber( | ||
@AncestorInPath AbstractProject<?,?> project, | ||
@QueryParameter String upstreamProjectName, | ||
@QueryParameter String upstreamBuildNumber | ||
) { | ||
// This is useless in almost all cases as this is usually specified with variables. | ||
|
||
upstreamProjectName = StringUtils.trim(upstreamProjectName); | ||
upstreamBuildNumber = StringUtils.trim(upstreamBuildNumber); | ||
|
||
if (StringUtils.isBlank(upstreamProjectName) || containsVariable(upstreamProjectName)) { | ||
// skip validation | ||
return FormValidation.ok(); | ||
} | ||
|
||
if (StringUtils.isBlank(upstreamBuildNumber)) { | ||
return FormValidation.error(Messages.DownstreamBuildSelector_UpstreamBuildNumber_Required()); | ||
} | ||
|
||
if (containsVariable(upstreamBuildNumber)) { | ||
return FormValidation.ok(); | ||
} | ||
|
||
AbstractProject<?,?> upstreamProject = Jenkins.getInstance().getItem( | ||
upstreamProjectName, project.getRootProject(), AbstractProject.class | ||
); | ||
if (upstreamProject == null || !upstreamProject.hasPermission(Item.READ)) { | ||
return FormValidation.ok(); | ||
} | ||
|
||
try { | ||
int number = Integer.parseInt(upstreamBuildNumber); | ||
AbstractBuild<?,?> upstreamBuild = upstreamProject.getBuildByNumber(number); | ||
if (upstreamBuild != null && upstreamBuild.hasPermission(Item.READ)) { | ||
// build number matches. | ||
return FormValidation.ok(); | ||
} | ||
} catch (NumberFormatException e) { | ||
// Ignore. Nothing to do. | ||
} | ||
|
||
{ | ||
AbstractBuild<?,?> upstreamBuild = upstreamProject.getBuild(upstreamBuildNumber); | ||
if (upstreamBuild != null && upstreamBuild.hasPermission(Item.READ)) { | ||
// build id matches. | ||
return FormValidation.ok(); | ||
} | ||
} | ||
|
||
{ | ||
for( | ||
AbstractBuild<?,?> upstreamBuild = upstreamProject.getLastCompletedBuild(); | ||
upstreamBuild != null; | ||
upstreamBuild = upstreamBuild.getPreviousCompletedBuild() | ||
) { | ||
if (upstreamBuild.getDisplayName().equals(upstreamBuildNumber)) { | ||
// display name matches. | ||
return FormValidation.ok(); | ||
} | ||
} | ||
} | ||
|
||
return FormValidation.error(Messages.DownstreamBuildSelector_UpstreamBuildNumber_NotFound()); | ||
} | ||
} | ||
} |
@@ -1,6 +1,12 @@ | ||
<div> | ||
<p> | ||
Defines a parameter that specifies how a Copy Artifact build step should select which | ||
build to copy from. Note that this parameter type is easier to use when starting the | ||
build from a browser; to specify a value via direct HTTP POST or the CLI, valid XML | ||
must be given. | ||
</p> | ||
<p> | ||
Be aware that this string value is encoded selector configuration, | ||
and not compatible with different plugin versions. | ||
</p> | ||
</div> |
@@ -1,6 +1,13 @@ | ||
<div> | ||
<p> | ||
Definiert ein Parameter, der angibt wie ein Artefaktkopie-Buildschritt den Build auswählt | ||
von dem kopiert werden soll. Bemerkung: Dieser Parametertyp ist einfacher zu benutzen, wenn der | ||
Build von einem Browser gestartet wird; um einen Wert über einen direkten HTTP POST oder | ||
das CLI anzugeben, muss valides XML übergeben werden. | ||
</p> | ||
<p> | ||
(Translation required) | ||
Be aware that this string value is encoded selector configuration, | ||
and not compatible with different plugin versions. | ||
</p> | ||
</div> |
@@ -1,4 +1,9 @@ | ||
<div> | ||
<p> | ||
Copy Artifactビルドステップ("他プロジェクトから成果物をコピー") が、どのようにビルドのコピー元を選択するかを指定するパラメータを定義します。 | ||
ビルド開始時にブラウザからこのパラメータを指定するのは簡単ですが、直接HTTPのPOSTやCLIから値を指定するには、正しいXMLを指定する必要があります。 | ||
</p> | ||
<p> | ||
このパラメーターの文字列としての値はエンコードされた設定データであり、プラグインをアップグレードした場合に互換性を失うことがあります。 | ||
</p> | ||
</div> |
@@ -0,0 +1,32 @@ | ||
<!-- | ||
The MIT License | ||
Copyright (c) 2014 IKEDA Yasuyuki | ||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. | ||
--> | ||
<?jelly escape-by-default='true'?> | ||
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"> | ||
<f:entry title="${%Upstream Project Name}" field="upstreamProjectName"> | ||
<f:textbox /> | ||
</f:entry> | ||
<f:entry title="${%Upstream Build Number}" field="upstreamBuildNumber"> | ||
<f:textbox /> | ||
</f:entry> | ||
</j:jelly> |
@@ -0,0 +1,24 @@ | ||
# The MIT License | ||
# | ||
# Copyright (c) 2014 IKEDA Yasuyuki | ||
# | ||
# Permission is hereby granted, free of charge, to any person obtaining a copy | ||
# of this software and associated documentation files (the "Software"), to deal | ||
# in the Software without restriction, including without limitation the rights | ||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
# copies of the Software, and to permit persons to whom the Software is | ||
# furnished to do so, subject to the following conditions: | ||
# | ||
# The above copyright notice and this permission notice shall be included in | ||
# all copies or substantial portions of the Software. | ||
# | ||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
# THE SOFTWARE. | ||
|
||
Upstream\ Project\ Name=\u4e0a\u6d41\u306e\u30d7\u30ed\u30b8\u30a7\u30af\u30c8\u540d | ||
Upstream\ Build\ Number=\u4e0a\u6d41\u306e\u30d3\u30eb\u30c9\u756a\u53f7 |
@@ -0,0 +1,5 @@ | ||
<div> | ||
The number of the build to find its downstream build. | ||
You can also specify display names. | ||
You can use variable expressions. | ||
</div> |
@@ -0,0 +1,5 @@ | ||
<div> | ||
下流ビルドを探すビルドのビルド番号を指定します。 | ||
表示名での指定も可能です。 | ||
変数を利用可能です。 | ||
</div> |
@@ -0,0 +1,11 @@ | ||
<div> | ||
<p> | ||
Copy artifacts from a build that is a downstream of a build of the specified project. | ||
You can use variable expressions. | ||
</p> | ||
<p> | ||
Downstream builds are found using fingerprints of files. | ||
That is, a build that is triggerd from a build isn't always considered downstream, | ||
but you need to fingerprint files used in builds to let Jenkins track them. | ||
</p> | ||
</div> |
Oops, something went wrong.