package linaro.pubapi; import java.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; import hudson.AbortException; import hudson.Extension; import hudson.Launcher; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.model.FreeStyleProject; import hudson.model.Item; import hudson.model.ParametersAction; import hudson.model.StringParameterValue; import hudson.security.ACL; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.Builder; import hudson.util.ListBoxModel; import jenkins.model.Jenkins; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.StaplerRequest; import com.cloudbees.jenkins.plugins.sshcredentials.SSHAuthenticator; import com.cloudbees.plugins.credentials.common.StandardCredentials; import com.cloudbees.plugins.credentials.common.StandardListBoxModel; import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; import com.cloudbees.plugins.credentials.domains.SchemeRequirement; import com.cloudbees.plugins.credentials.CredentialsMatcher; import com.cloudbees.plugins.credentials.CredentialsMatchers; import com.cloudbees.plugins.credentials.CredentialsProvider; import net.sf.json.JSONObject; import org.apache.commons.httpclient.*; import org.apache.commons.httpclient.methods.*; import org.apache.commons.httpclient.params.HttpMethodParams; // Solely to filter by "SSH connection" credentials type import com.trilead.ssh2.Connection; /** * @author Paul Sokolovsky */ public class LinaroPubAPIKey extends Builder { private static final SchemeRequirement SCHEME = new SchemeRequirement("pubapi"); private static final CredentialsMatcher CREDENTIALS_MATCHER = CredentialsMatchers.anyOf( CredentialsMatchers.instanceOf(StandardUsernamePasswordCredentials.class) ); public String credentialsId; @DataBoundConstructor public LinaroPubAPIKey(String credentialsId) { this.credentialsId = credentialsId; } @Override public Descriptor getDescriptor() { return (Descriptor) super.getDescriptor(); } @Extension public static class Descriptor extends BuildStepDescriptor { // Using public members alleviates us from creating getters/setters public String defaultNotBefore; public String defaultNotAfter; public Descriptor() { load(); } @Override public boolean configure(StaplerRequest req, JSONObject json) throws FormException { req.bindJSON(this, json.getJSONObject("linaropubapi")); save(); return true; } @Override public boolean isApplicable(Class jobType) { // Go bold and apply to any possible project type return true; //return FreeStyleProject.class.isAssignableFrom(jobType); } @Override public String getDisplayName() { return "Linaro Publishing API Token"; } public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Item project) { return new StandardListBoxModel().withMatching(SSHAuthenticator.matcher(Connection.class), CredentialsProvider.lookupCredentials(StandardCredentials.class, project, ACL.SYSTEM, SCHEME)); } } private static StandardUsernamePasswordCredentials lookupSystemCredentials(String credentialsId) { return CredentialsMatchers.firstOrNull( CredentialsProvider .lookupCredentials(StandardUsernamePasswordCredentials.class, Jenkins.getInstance(), ACL.SYSTEM, SCHEME), CredentialsMatchers.withId(credentialsId) ); } @Override public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { HttpClient client = new HttpClient(); if (credentialsId == null) { credentialsId = "pubapi-snapshots.linaro.org"; } StandardUsernamePasswordCredentials cred = lookupSystemCredentials(credentialsId); if (cred == null) { throw new AbortException("Unable to get publishing credentials with ID: '" + credentialsId + "'"); } String url = cred.getUsername(); listener.getLogger().format("Requesting publishing token for %s%n", url); PostMethod method = new PostMethod(url); method.setRequestHeader("AuthToken", cred.getPassword().getPlainText()); method.addParameter("not_valid_til", Integer.toString(Integer.parseInt(getDescriptor().defaultNotBefore) * 60)); method.addParameter("expires", Integer.toString(Integer.parseInt(getDescriptor().defaultNotAfter) * 60)); // Auto-retry once on some (connection) errors // Note: for some connection issues, there's very long timeouts, so // having anything more than 1 here contradicts "fail fast" approach. // Disable auto-retry altogether for now - note that there's autoretry // by default. method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(0, false)); String token = null; try { int statusCode = client.executeMethod(method); //System.out.println("Status: " + method.getStatusLine()); if (statusCode != 201) { System.err.println("Method failed: " + method.getStatusLine()); throw new AbortException("Unable to get publishing key: " + method.getStatusLine()); } String loc = method.getResponseHeader("Location").getValue(); System.out.println("Token URL: " + loc); token = loc.substring(loc.lastIndexOf("/") + 1); } catch (HttpException e) { System.err.println("Fatal protocol violation: " + e.getMessage()); e.printStackTrace(); throw new AbortException("Unable to get publishing key: " + e.getMessage()); } catch (IOException e) { System.err.println("Fatal transport error for '" + url + "': " + e.getMessage()); e.printStackTrace(); throw new AbortException("Unable to get publishing key: " + e.getMessage()); } finally { // Release the connection method.releaseConnection(); } build.addAction(new ParametersAction(new StringParameterValue("PUBLISH_TOKEN", token))); Matcher m = Pattern.compile("(.+://.+?/)").matcher(url); if (m.find()) { String serverUrl = m.group(1); build.addAction(new ParametersAction(new StringParameterValue("PUBLISH_SERVER", serverUrl))); } return true; } }