blob: 98be93b4470dd360d01528a621a511dc0e3c6dbe [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.yarn.util;
import org.apache.commons.io.FileUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.commons.io.IOUtils;
import org.apache.hadoop.io.DataInputByteBuffer;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.security.Credentials;
import org.apache.hadoop.security.token.Token;
import org.apache.hadoop.security.token.TokenIdentifier;
import org.apache.hadoop.yarn.security.DockerCredentialTokenIdentifier;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ObjectNode;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
/**
* Commonly needed actions for handling the Docker client configurations.
*
* Credentials that are used to access private Docker registries are supplied.
* Actions include:
* <ul>
* <li>Read the Docker client configuration json file from a
* {@link FileSystem}.</li>
* <li>Extract the authentication information from the configuration into
* {@link Token} and {@link Credentials} objects.</li>
* <li>Tokens are commonly shipped via the
* {@link org.apache.hadoop.yarn.api.records.ContainerLaunchContext} as a
* {@link ByteBuffer}, extract the {@link Credentials}.</li>
* <li>Write the Docker client configuration json back to the local filesystem
* to be used by the Docker command line.</li>
* </ul>
*/
public final class DockerClientConfigHandler {
private static final org.slf4j.Logger LOG =
LoggerFactory.getLogger(DockerClientConfigHandler.class);
private static final String CONFIG_AUTHS_KEY = "auths";
private static final String CONFIG_AUTH_KEY = "auth";
private DockerClientConfigHandler() { }
/**
* Read the Docker client configuration and extract the auth tokens into
* Credentials.
*
* @param configFile the Path to the Docker client configuration.
* @param conf the Configuration object, needed by the FileSystem.
* @param applicationId the application ID to associate the Credentials with.
* @return the populated Credential object with the Docker Tokens.
* @throws IOException if the file can not be read.
*/
public static Credentials readCredentialsFromConfigFile(Path configFile,
Configuration conf, String applicationId) throws IOException {
// Read the config file
String contents = null;
configFile = new Path(configFile.toUri());
FileSystem fs = configFile.getFileSystem(conf);
if (fs != null) {
FSDataInputStream fileHandle = fs.open(configFile);
if (fileHandle != null) {
contents = IOUtils.toString(fileHandle);
}
}
if (contents == null) {
throw new IOException("Failed to read Docker client configuration: "
+ configFile);
}
// Parse the JSON and create the Tokens/Credentials.
ObjectMapper mapper = new ObjectMapper();
JsonFactory factory = mapper.getJsonFactory();
JsonParser parser = factory.createJsonParser(contents);
JsonNode rootNode = mapper.readTree(parser);
Credentials credentials = new Credentials();
if (rootNode.has(CONFIG_AUTHS_KEY)) {
Iterator<String> iter = rootNode.get(CONFIG_AUTHS_KEY).getFieldNames();
for (; iter.hasNext();) {
String registryUrl = iter.next();
String registryCred = rootNode.get(CONFIG_AUTHS_KEY)
.get(registryUrl)
.get(CONFIG_AUTH_KEY)
.asText();
TokenIdentifier tokenId =
new DockerCredentialTokenIdentifier(registryUrl, applicationId);
Token<DockerCredentialTokenIdentifier> token =
new Token<>(tokenId.getBytes(),
registryCred.getBytes(Charset.forName("UTF-8")),
tokenId.getKind(), new Text(registryUrl));
credentials.addToken(
new Text(registryUrl + "-" + applicationId), token);
if (LOG.isDebugEnabled()) {
LOG.debug("Token read from Docker client configuration file: "
+ token.toString());
}
}
}
return credentials;
}
/**
* Convert the Token ByteBuffer to the appropriate Credentials object.
*
* @param tokens the Tokens from the ContainerLaunchContext.
* @return the Credentials object populated from the Tokens.
*/
public static Credentials getCredentialsFromTokensByteBuffer(
ByteBuffer tokens) throws IOException {
Credentials credentials = new Credentials();
DataInputByteBuffer dibb = new DataInputByteBuffer();
tokens.rewind();
dibb.reset(tokens);
credentials.readTokenStorageStream(dibb);
tokens.rewind();
if (LOG.isDebugEnabled()) {
for (Token token : credentials.getAllTokens()) {
LOG.debug("Token read from token storage: " + token.toString());
}
}
return credentials;
}
/**
* Extract the Docker related tokens from the Credentials and write the Docker
* client configuration to the supplied File.
*
* @param outConfigFile the File to write the Docker client configuration to.
* @param credentials the populated Credentials object.
* @throws IOException if the write fails.
*/
public static void writeDockerCredentialsToPath(File outConfigFile,
Credentials credentials) throws IOException {
ObjectMapper mapper = new ObjectMapper();
ObjectNode rootNode = mapper.createObjectNode();
ObjectNode registryUrlNode = mapper.createObjectNode();
boolean foundDockerCred = false;
if (credentials.numberOfTokens() > 0) {
for (Token<? extends TokenIdentifier> tk : credentials.getAllTokens()) {
if (tk.getKind().equals(DockerCredentialTokenIdentifier.KIND)) {
foundDockerCred = true;
DockerCredentialTokenIdentifier ti =
(DockerCredentialTokenIdentifier) tk.decodeIdentifier();
ObjectNode registryCredNode = mapper.createObjectNode();
registryUrlNode.put(ti.getRegistryUrl(), registryCredNode);
registryCredNode.put(CONFIG_AUTH_KEY,
new String(tk.getPassword(), Charset.forName("UTF-8")));
if (LOG.isDebugEnabled()) {
LOG.debug("Prepared token for write: " + tk.toString());
}
}
}
}
if (foundDockerCred) {
rootNode.put(CONFIG_AUTHS_KEY, registryUrlNode);
String json =
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode);
FileUtils.writeStringToFile(outConfigFile, json, StandardCharsets.UTF_8);
}
}
}