/* * The MIT License * * Copyright (c) 2015, CloudBees, Inc. * * 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 org.jenkinsci.plugins.docker.commons.credentials; import com.cloudbees.plugins.credentials.CredentialsMatchers; import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.common.IdCredentials; import com.cloudbees.plugins.credentials.common.StandardListBoxModel; import com.cloudbees.plugins.credentials.domains.DomainRequirement; import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder; import hudson.Extension; import hudson.FilePath; import hudson.model.AbstractBuild; import hudson.model.AbstractDescribableImpl; import hudson.model.Describable; import hudson.model.Descriptor; import hudson.model.Item; import hudson.remoting.VirtualChannel; import hudson.util.ListBoxModel; import jenkins.authentication.tokens.api.AuthenticationTokens; import jenkins.model.Jenkins; import org.jenkinsci.plugins.docker.commons.impl.ServerHostKeyMaterialFactory; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.IOException; import java.util.List; import static hudson.Util.fixEmpty; /** * Encapsulates the endpoint of Docker daemon and how to interact with it. * * <p> * As {@link Describable} it comes with pre-baked configuration form that you can use in * your builders/publishers/etc that interact with Docker daemon. * * @author Kohsuke Kawaguchi */ public class DockerServerEndpoint extends AbstractDescribableImpl<DockerServerEndpoint> { // TODO once we have a base type to migrate DockerServerCredentials replace with the corresponding interface private static final Class<DockerServerCredentials> BASE_CREDENTIAL_TYPE = DockerServerCredentials.class; private final String uri; private final @CheckForNull String credentialsId; @DataBoundConstructor public DockerServerEndpoint(String uri, String credentialsId) { this.uri = fixEmpty(uri); this.credentialsId = fixEmpty(credentialsId); } /** * Gets the endpoint in URI, such as "unix:///var/run/docker.sock". * * <p> * Null to indicate whatever Docker picks by default. */ public @Nullable String getUri() { return uri; } /** * {@linkplain IdCredentials#getId() ID} of the credentials used to talk to this endpoint. */ public @Nullable String getCredentialsId() { return credentialsId; } /** * Makes the key materials available locally for the on-going build * and returns {@link KeyMaterialFactory} that gives you the parameters needed to access it. */ public KeyMaterialFactory newKeyMaterialFactory(@Nonnull AbstractBuild build) throws IOException, InterruptedException { final FilePath workspace = build.getWorkspace(); if (workspace == null) { throw new IllegalStateException("Build has no workspace"); } return newKeyMaterialFactory(build.getParent(), workspace.getChannel()); } /** * Makes the key materials available locally and returns {@link KeyMaterialFactory} that gives you the parameters * needed to access it. */ public KeyMaterialFactory newKeyMaterialFactory(@Nonnull Item context, @Nonnull VirtualChannel target) throws IOException, InterruptedException { // as a build step, your access to credentials are constrained by what the build // can access, hence Jenkins.getAuthentication() DockerServerCredentials creds=null; if (credentialsId!=null) { List<DomainRequirement> domainRequirements = URIRequirementBuilder.fromUri(getUri()).build(); domainRequirements.add(new DockerServerDomainRequirement()); creds = CredentialsMatchers.firstOrNull( CredentialsProvider.lookupCredentials( DockerServerCredentials.class, context, Jenkins.getAuthentication(), domainRequirements), CredentialsMatchers.withId(credentialsId) ); } // the directory needs to be outside workspace to avoid prying eyes FilePath dotDocker = dotDocker(target); dotDocker.mkdirs(); // ServerKeyMaterialFactory.materialize creates a random subdir if one is needed: return newKeyMaterialFactory(dotDocker, creds); } static FilePath dotDocker(@Nonnull VirtualChannel target) throws IOException, InterruptedException { return FilePath.getHomeDirectory(target).child(".docker"); } /** * Create a {@link KeyMaterialFactory} for connecting to the docker server/host. */ public KeyMaterialFactory newKeyMaterialFactory(FilePath dir, @Nullable DockerServerCredentials credentials) throws IOException, InterruptedException { return (uri == null ? KeyMaterialFactory.NULL : new ServerHostKeyMaterialFactory(uri)) .plus(AuthenticationTokens.convert(KeyMaterialFactory.class, credentials)) .contextualize(new KeyMaterialContext(dir)); } @Override public String toString() { return "DockerServerEndpoint[" + uri + ";credentialsId=" + credentialsId + "]"; } @Override public int hashCode() { int hash = 7; hash = 13 * hash + (this.uri != null ? this.uri.hashCode() : 0); hash = 13 * hash + (this.credentialsId != null ? this.credentialsId.hashCode() : 0); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final DockerServerEndpoint other = (DockerServerEndpoint) obj; if ((this.uri == null) ? (other.uri != null) : !this.uri.equals(other.uri)) { return false; } if ((this.credentialsId == null) ? (other.credentialsId != null) : !this.credentialsId.equals(other.credentialsId)) { return false; } return true; } @Extension public static class DescriptorImpl extends Descriptor<DockerServerEndpoint> { @Override public String getDisplayName() { return "Docker Daemon"; } public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Item item, @QueryParameter String uri) { List<DomainRequirement> domainRequirements = URIRequirementBuilder.fromUri(uri).build(); domainRequirements.add(new DockerServerDomainRequirement()); return new StandardListBoxModel() .withEmptySelection() .withMatching( AuthenticationTokens.matcher(KeyMaterialFactory.class), CredentialsProvider .lookupCredentials(BASE_CREDENTIAL_TYPE, item, null, domainRequirements) ); } } }