package hudson.plugins.promoted_builds.conditions; import hudson.EnvVars; import hudson.Extension; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.Descriptor; import hudson.model.Hudson; import hudson.model.InvisibleAction; import hudson.model.SimpleParameterDefinition; import hudson.model.ParameterDefinition; import hudson.model.ParameterValue; import hudson.model.User; import hudson.plugins.promoted_builds.PromotionBadge; import hudson.plugins.promoted_builds.PromotionCondition; import hudson.plugins.promoted_builds.PromotionConditionDescriptor; import hudson.plugins.promoted_builds.Promotion; import hudson.plugins.promoted_builds.PromotionProcess; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.servlet.ServletException; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.acegisecurity.GrantedAuthority; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.export.Exported; /** * {@link PromotionCondition} that requires manual promotion. * * @author Kohsuke Kawaguchi * @author Peter Hayes */ public class ManualCondition extends PromotionCondition { private String users; private List<ParameterDefinition> parameterDefinitions = new ArrayList<ParameterDefinition>(); /** * */ public final static String MISSING_USER_ID_DISPLAY_STRING = "N/A"; public ManualCondition() { } /* * Restrict the Condition to specific user(s) * @since 2.24 */ public String getUsers() { return users; } public void setUsers(String users) { this.users = users; } public List<ParameterDefinition> getParameterDefinitions() { return parameterDefinitions; } /** * Gets the {@link ParameterDefinition} of the given name, if any. */ @CheckForNull public ParameterDefinition getParameterDefinition(String name) { if (parameterDefinitions == null) { return null; } for (ParameterDefinition pd : parameterDefinitions) if (pd.getName().equals(name)) return pd; return null; } public Set<String> getUsersAsSet() { if (users == null || users.equals("")) return Collections.emptySet(); Set<String> set = new HashSet<String>(); for (String user : users.split(",")) { user = user.trim(); if (user.trim().length() > 0) set.add(user); } return set; } @Override public PromotionBadge isMet(PromotionProcess promotionProcess, AbstractBuild<?,?> build) { List<ManualApproval> approvals = build.getActions(ManualApproval.class); for (ManualApproval approval : approvals) { if (approval.name.equals(promotionProcess.getName())) return approval.badge; } return null; } /** * Verifies that the currently logged in user (or anonymous) has permission * to approve the promotion and that the promotion has not already been * approved. */ public boolean canApprove(PromotionProcess promotionProcess, AbstractBuild<?,?> build) { if (!getUsersAsSet().isEmpty() && !isInUsersList() && !isInGroupList()) { return false; } List<ManualApproval> approvals = build.getActions(ManualApproval.class); // For now, only allow approvals if this wasn't already approved for (ManualApproval approval : approvals) { if (approval.name.equals(promotionProcess.getName())) return false; } return true; } //TODO: updated the access level to public for reuse in another class /* * Check if user is listed in user list as a specific user * @since 2.24 */ public boolean isInUsersList() { // Current user must be in users list or users list is empty Set<String> usersSet = getUsersAsSet(); return usersSet.contains(Hudson.getAuthentication().getName()); } /* * Check if user is a member of a groups as listed in the user / group field */ public boolean isInGroupList() { Set<String> groups = getUsersAsSet(); GrantedAuthority[] authorities = Hudson.getAuthentication().getAuthorities(); for (GrantedAuthority authority : authorities) { if (groups.contains(authority.getAuthority())) return true; } return false; } @CheckForNull public Future<Promotion> approve(AbstractBuild<?,?> build, PromotionProcess promotionProcess, List<ParameterValue> paramValues) throws IOException{ if (canApprove(promotionProcess, build)) { // add approval to build ManualApproval approval=new ManualApproval(promotionProcess.getName(), paramValues); build.addAction(approval); build.save(); // check for promotion return promotionProcess.considerPromotion2(build, approval); } return null; } public List<ParameterValue> createDefaultValues(){ List<ParameterValue> paramValues = new ArrayList<ParameterValue>(); if (parameterDefinitions != null && !parameterDefinitions.isEmpty()) { for (ParameterDefinition d:parameterDefinitions){ paramValues.add(d.getDefaultParameterValue()); } } return paramValues; } public Future<Promotion> approve(AbstractBuild<?,?> build, PromotionProcess promotionProcess) throws IOException{ List<ParameterValue> paramValues = createDefaultValues(); return approve(build, promotionProcess, paramValues); } /** * Web method to handle the approval action submitted by the user. */ public void doApprove(StaplerRequest req, StaplerResponse rsp, @AncestorInPath PromotionProcess promotionProcess, @AncestorInPath AbstractBuild<?,?> build) throws IOException, ServletException { JSONObject formData = req.getSubmittedForm(); if (canApprove(promotionProcess, build)) { List<ParameterValue> paramValues = new ArrayList<ParameterValue>(); if (parameterDefinitions != null && !parameterDefinitions.isEmpty()) { JSONArray a = JSONArray.fromObject(formData.get("parameter")); for (Object o : a) { JSONObject jo = (JSONObject) o; String name = jo.getString("name"); ParameterDefinition d = getParameterDefinition(name); if (d==null) throw new IllegalArgumentException("No such parameter definition: " + name); paramValues.add(d.createValue(req, jo)); } } approve(build, promotionProcess, paramValues); } rsp.sendRedirect2("../../../.."); } /* * Used to annotate the build to indicate that it was manually approved. This * is then looked for in the isMet method. */ public static final class ManualApproval extends InvisibleAction { public String name; public Badge badge; public ManualApproval(String name, List<ParameterValue> values) { this.name = name; badge = new Badge(values); } } public static final class Badge extends PromotionBadge { public String authenticationName; private final List<ParameterValue> values; public Badge(List<ParameterValue> values) { this.authenticationName = Hudson.getAuthentication().getName(); this.values = values; } /** * Gets name of the user, who has promoted the build. * @return User name or {@link #MISSING_USER_ID_DISPLAY_STRING} if the user cannot be determined */ @Exported @Nonnull public String getUserName() { if (authenticationName == null) return MISSING_USER_ID_DISPLAY_STRING; User u = User.get(authenticationName, false, null); return u != null ? u.getDisplayName() : authenticationName; } /** * Gets ID of the user, who has promoted the build. * @return User id or {@link #MISSING_USER_ID_DISPLAY_STRING} if the user cannot be determined */ @Exported public String getUserId() { if (authenticationName == null) { return MISSING_USER_ID_DISPLAY_STRING; } return authenticationName; } @Exported public List<ParameterValue> getParameterValues() { return values != null ? values : Collections.<ParameterValue>emptyList(); } @Override public void buildEnvVars(AbstractBuild<?, ?> build, EnvVars env) { if (!(build instanceof Promotion)) { throw new IllegalStateException ("Wrong build type. Expected a Promotion, but got "+build.getClass()); } List<ParameterValue> params = ((Promotion) build).getParameterValues(); if (params != null) { for (ParameterValue value : params) { value.buildEnvVars(build, env); } } } } @Extension public static final class DescriptorImpl extends PromotionConditionDescriptor { public boolean isApplicable(AbstractProject<?,?> item) { return true; } public String getDisplayName() { return Messages.ManualCondition_DisplayName(); } @Override public ManualCondition newInstance(StaplerRequest req, JSONObject formData) throws FormException { ManualCondition instance = new ManualCondition(); instance.users = formData.getString("users"); instance.parameterDefinitions = Descriptor.newInstancesFromHeteroList(req, formData, "parameters", ParameterDefinition.all()); return instance; } } }