Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,49 @@ thereby keeping configuration more [DRY](http://en.wikipedia.org/wiki/Don%27t_re

See [Docker Pipeline Plugin](https://plugins.jenkins.io/docker-workflow) for the typical usage.

### Multiple Private/Authenticated Registries

In some scenarios you need to authenticate between multiple registries from within a single pipeline step/command. Below
are some examples to allow this:

*Scripted*
```groovy
node('docker') {
docker.withRegistry('private-repo1-url', 'id-for-a-docker-cred-repo1') {
docker.withRegistry('private-repo2-url', 'id-for-a-docker-cred-repo2') {
writeFile file: 'Dockerfile', text: '''
FROM private-repo1-url/image
COPY someFile /
ENTRYPOINT /someFile'''
sh 'docker build --tag private-repo2-url/myapp .'
}
}
}
```

*Declarative*
```groovy
pipeline {
agent docker
stages {
stage('foo') {
steps {
docker.withRegistry('private-repo1-url', 'id-for-a-docker-cred-repo1') {
docker.withRegistry('private-repo2-url', 'id-for-a-docker-cred-repo2') {
writeFile file: 'Dockerfile', text: '''
FROM private-repo1-url/image
COPY someFile /
ENTRYPOINT /someFile'''
sh 'docker build --tag private-repo2-url/myapp .'
}
}
}
}
}
}
```


## Declarative pipeline example

An example on how to bind Docker host/daemon credentials in a declarative pipeline:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@

package org.jenkinsci.plugins.docker.commons.impl;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;

import javax.annotation.Nonnull;

Expand All @@ -51,7 +53,9 @@
public class RegistryKeyMaterialFactory extends KeyMaterialFactory {

private static final String DOCKER_CONFIG_FILENAME = "config.json";
private static final String[] BLACKLISTED_PROPERTIES = { "auths", "credsStore" };
private static final String BLACKLISTED_PROPERTY_CREDS_STORE = "credsStore";
private static final String BLACKLISTED_PROPERTY_AUTHS = "auths";
private static final String[] BLACKLISTED_PROPERTIES = { BLACKLISTED_PROPERTY_AUTHS, BLACKLISTED_PROPERTY_CREDS_STORE };

private final @Nonnull String username;
private final @Nonnull String password;
Expand All @@ -75,23 +79,25 @@ public RegistryKeyMaterialFactory(@Nonnull String username, @Nonnull String pass
public KeyMaterial materialize() throws IOException, InterruptedException {
FilePath dockerConfig = createSecretsDirectory();

// read the existing docker config file, which might hold some important settings (e.b. proxies)
// read the user's home dir docker config file, which might hold some important settings (e.b. proxies)
FilePath configJsonPath = FilePath.getHomeDirectory(this.launcher.getChannel()).child(".docker").child(DOCKER_CONFIG_FILENAME);
if (configJsonPath.exists()) {
String configJson = configJsonPath.readToString();
if (StringUtils.isNotBlank(configJson)) {
launcher.getListener().getLogger().print("Using the existing docker config file.");

JSONObject json = JSONObject.fromObject(configJson);
for (String property : BLACKLISTED_PROPERTIES) {
Object value = json.remove(property);
if (value != null) {
launcher.getListener().getLogger().print("Removing blacklisted property: " + property);
}
}
// read the current docker config which might hold some existing settings (e.b. credentials)
FilePath existingDockerConfigPath = new FilePath(this.launcher.getChannel(),
Paths.get(this.env.get("DOCKER_CONFIG"), DOCKER_CONFIG_FILENAME).toString());

String dockerConfigJson = "";
if (existingDockerConfigPath.exists()) {
this.launcher.getListener().getLogger().print("Reading the existing DOCKER_CONFIG '" +
existingDockerConfigPath + "' docker config file.\n");
dockerConfigJson = existingDockerConfigPath.readToString();
} else if (configJsonPath.exists()) {
this.launcher.getListener().getLogger().print("Reading the existing user's home '" +
configJsonPath + "' docker config file.\n");
dockerConfigJson = removeBlacklistedProperties(configJsonPath.readToString(), BLACKLISTED_PROPERTIES);
}

dockerConfig.child(DOCKER_CONFIG_FILENAME).write(json.toString(), StandardCharsets.UTF_8.name());
}
if (StringUtils.isNotBlank(dockerConfigJson)) {
dockerConfig.child(DOCKER_CONFIG_FILENAME).write(dockerConfigJson, StandardCharsets.UTF_8.name());
}

try {
Expand All @@ -112,6 +118,21 @@ public KeyMaterial materialize() throws IOException, InterruptedException {
return new RegistryKeyMaterial(dockerConfig, new EnvVars("DOCKER_CONFIG", dockerConfig.getRemote()));
}

private String removeBlacklistedProperties(@Nonnull String json, @Nonnull String[] blacklistedProperties) {
String jsonString = "";
if (StringUtils.isNotBlank(json)) {
JSONObject jsonObject = JSONObject.fromObject(json);
for (String property : blacklistedProperties) {
Object value = jsonObject.remove(property);
if (value != null) {
this.launcher.getListener().getLogger().print("Removing blacklisted property: " + property + "\n");
}
}
jsonString = jsonObject.toString();
}
return jsonString;
}

private static class RegistryKeyMaterial extends KeyMaterial {

private final FilePath dockerConfig;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.emptyArray;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
Expand All @@ -41,13 +42,15 @@
import org.jenkinsci.plugins.docker.commons.credentials.KeyMaterialContext;
import org.jenkinsci.plugins.docker.commons.credentials.KeyMaterialFactory;
import org.jenkinsci.plugins.docker.commons.tools.DockerTool;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.FakeLauncher;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.PretendSlave;
import net.sf.json.JSONObject;

import hudson.EnvVars;
import hudson.FilePath;
Expand All @@ -66,6 +69,7 @@ public class RegistryKeyMaterialFactoryTest {
@Rule
public TemporaryFolder tempFolder = new TemporaryFolder();

private EnvVars env = new EnvVars();
private KeyMaterialFactory factory;

@Before
Expand Down Expand Up @@ -98,13 +102,17 @@ public <V, T extends Throwable> V call(final Callable<V, T> callable) throws T {
};

URL endpoint = new DockerRegistryEndpoint(null, null).getEffectiveUrl();
EnvVars env = new EnvVars();
String dockerExecutable = DockerTool.getExecutable(null, null, listener, env);

factory = new RegistryKeyMaterialFactory("username", "password", endpoint, launcher, env, listener,
dockerExecutable).contextualize(new KeyMaterialContext(new FilePath(tempFolder.newFolder())));
}

@After
public void reset() {
env = new EnvVars();
}

@Test
public void materialize_userConfigFileNotPresent_notCreated() throws Exception {
// act
Expand Down Expand Up @@ -232,4 +240,34 @@ public void materialize_userConfigFileWithCredStoreAndHttpHeaders_createdWithHea
assertEquals("{\"HttpHeaders\":{\"User-Agent\":\"Docker-Client\"}}", FileUtils.readFileToString(jsonFile));
}

@Test
public void materialize_existingConfigFile_updatedWithCredentials() throws Exception {

// arrange
File existingCfgFile = new File(new File(tempFolder.getRoot(), ".docker"), "config.json");
String existingCfgPath = new File(existingCfgFile.getAbsolutePath()).getParent();
String updatedConfigJson = "{" + "\"auths\":{\"localhost2:5001\":{\"auth\":\"whatever2\",\"email\":\"\"}}," +
"\"proxies\":{\"default\":{\"httpProxy\":\"proxy\",\"noProxy\":\"something\"}}" + "}";
FileUtils.write(existingCfgFile, updatedConfigJson);
assertNotNull(existingCfgPath);
env.put("DOCKER_CONFIG", existingCfgPath);
assertNotNull(env.get("DOCKER_CONFIG", null));

// act
KeyMaterial material = factory.materialize();

// assert
String cfgFolderPath = material.env().get("DOCKER_CONFIG", null);
assertNotNull(cfgFolderPath);
assertNotEquals(cfgFolderPath, existingCfgPath);

File dockerCfgFolder = new File(cfgFolderPath);
assertTrue(dockerCfgFolder.exists());

String[] existingFiles = dockerCfgFolder.list();
assertThat(existingFiles, arrayContaining("config.json"));

File jsonFile = new File(dockerCfgFolder, "config.json");
assertEquals(updatedConfigJson, FileUtils.readFileToString(jsonFile));
}
}