Skip to content

Commit 4f52fae

Browse files
Added unit test and refactored a few things
1 parent b41750c commit 4f52fae

File tree

3 files changed

+111
-39
lines changed

3 files changed

+111
-39
lines changed

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,49 @@ thereby keeping configuration more [DRY](http://en.wikipedia.org/wiki/Don%27t_re
2626

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

29+
### Multiple Private/Authenticated Registries
30+
31+
In some scenarios you need to authenticate between multiple registries from within a single pipeline step/command. Below
32+
are some examples to allow this:
33+
34+
*Scripted*
35+
```groovy
36+
node('docker') {
37+
docker.withRegistry('private-repo1-url', 'id-for-a-docker-cred-repo1') {
38+
docker.withRegistry('private-repo2-url', 'id-for-a-docker-cred-repo2') {
39+
writeFile file: 'Dockerfile', text: '''
40+
FROM private-repo1-url/image
41+
COPY someFile /
42+
ENTRYPOINT /someFile'''
43+
sh 'docker build --tag private-repo2-url/myapp .'
44+
}
45+
}
46+
}
47+
```
48+
49+
*Declarative*
50+
```groovy
51+
pipeline {
52+
agent docker
53+
stages {
54+
stage('foo') {
55+
steps {
56+
docker.withRegistry('private-repo1-url', 'id-for-a-docker-cred-repo1') {
57+
docker.withRegistry('private-repo2-url', 'id-for-a-docker-cred-repo2') {
58+
writeFile file: 'Dockerfile', text: '''
59+
FROM private-repo1-url/image
60+
COPY someFile /
61+
ENTRYPOINT /someFile'''
62+
sh 'docker build --tag private-repo2-url/myapp .'
63+
}
64+
}
65+
}
66+
}
67+
}
68+
}
69+
```
70+
71+
2972
## Declarative pipeline example
3073

3174
An example on how to bind Docker host/daemon credentials in a declarative pipeline:

src/main/java/org/jenkinsci/plugins/docker/commons/impl/RegistryKeyMaterialFactory.java

Lines changed: 29 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.io.IOException;
2929
import java.net.URL;
3030
import java.nio.charset.StandardCharsets;
31+
import java.nio.file.Paths;
3132

3233
import javax.annotation.Nonnull;
3334

@@ -54,9 +55,7 @@ public class RegistryKeyMaterialFactory extends KeyMaterialFactory {
5455
private static final String DOCKER_CONFIG_FILENAME = "config.json";
5556
private static final String BLACKLISTED_PROPERTY_CREDS_STORE = "credsStore";
5657
private static final String BLACKLISTED_PROPERTY_AUTHS = "auths";
57-
private static final String BLACKLISTED_PROPERTY_PROXIES = "proxies";
5858
private static final String[] BLACKLISTED_PROPERTIES = { BLACKLISTED_PROPERTY_AUTHS, BLACKLISTED_PROPERTY_CREDS_STORE };
59-
private static final String[] BLACKLISTED_NESTED_PROPERTIES = { BLACKLISTED_PROPERTY_CREDS_STORE, BLACKLISTED_PROPERTY_PROXIES };
6059

6160
private final @Nonnull String username;
6261
private final @Nonnull String password;
@@ -80,20 +79,25 @@ public RegistryKeyMaterialFactory(@Nonnull String username, @Nonnull String pass
8079
public KeyMaterial materialize() throws IOException, InterruptedException {
8180
FilePath dockerConfig = createSecretsDirectory();
8281

83-
// read the existing docker config file, which might hold some important settings (e.b. proxies)
82+
// read the user's home dir docker config file, which might hold some important settings (e.b. proxies)
8483
FilePath configJsonPath = FilePath.getHomeDirectory(this.launcher.getChannel()).child(".docker").child(DOCKER_CONFIG_FILENAME);
85-
dockerConfig = UpdateDockerConfigFromSource(dockerConfig, configJsonPath, BLACKLISTED_PROPERTIES);
86-
87-
// Read the existing docker config file from a nested config block, will probably hold some previous credentials
88-
String existingDockerSecretConfigPath = this.env.get("DOCKER_CONFIG");
89-
if (StringUtils.isNotBlank(existingDockerSecretConfigPath)) {
90-
// Can't use FilePath(File) yet as not supported till later versions of jenkins..
91-
//FilePath existingDockerConfig = FilePath(new File(existingDockerSecretConfigPath, DOCKER_CONFIG_FILENAME));
92-
FilePath baseDir = getContext().getBaseDir();
93-
// Need to get tmp dir - get base dir length and increase by 1 to include the path separator
94-
String existingTmpConfigDir = existingDockerSecretConfigPath.substring(baseDir.getRemote().length() + 1);
95-
FilePath existingDockerConfigPath = baseDir.child(existingTmpConfigDir).child(DOCKER_CONFIG_FILENAME);
96-
dockerConfig = updateDockerConfigFromSource(dockerConfig, existingDockerConfigPath, BLACKLISTED_NESTED_PROPERTIES);
84+
// read the current docker config which might hold some existing settings (e.b. credentials)
85+
FilePath existingDockerConfigPath = new FilePath(this.launcher.getChannel(),
86+
Paths.get(this.env.get("DOCKER_CONFIG"), DOCKER_CONFIG_FILENAME).toString());
87+
88+
String dockerConfigJson = "";
89+
if (existingDockerConfigPath.exists()) {
90+
this.launcher.getListener().getLogger().print("Reading the existing DOCKER_CONFIG '" +
91+
existingDockerConfigPath + "' docker config file.\n");
92+
dockerConfigJson = existingDockerConfigPath.readToString();
93+
} else if (configJsonPath.exists()) {
94+
this.launcher.getListener().getLogger().print("Reading the existing user's home '" +
95+
configJsonPath + "' docker config file.\n");
96+
dockerConfigJson = removeBlacklistedProperties(configJsonPath.readToString(), BLACKLISTED_PROPERTIES);
97+
}
98+
99+
if (StringUtils.isNotBlank(dockerConfigJson)) {
100+
dockerConfig.child(DOCKER_CONFIG_FILENAME).write(dockerConfigJson, StandardCharsets.UTF_8.name());
97101
}
98102

99103
try {
@@ -114,32 +118,19 @@ public KeyMaterial materialize() throws IOException, InterruptedException {
114118
return new RegistryKeyMaterial(dockerConfig, new EnvVars("DOCKER_CONFIG", dockerConfig.getRemote()));
115119
}
116120

117-
/**
118-
* Copy docker config source data to another docker config
119-
* @param dockerConfig
120-
* @param dockerConfigSourcePath
121-
* @param blacklistedProperties
122-
* @return FilePath dockerConfig
123-
*/
124-
private FilePath updateDockerConfigFromSource(@Nonnull FilePath dockerConfig, @Nonnull FilePath dockerConfigSourcePath, @Nonnull String[] blacklistedProperties) throws IOException, InterruptedException {
125-
// Make sure config exists
126-
if (dockerConfigSourcePath.exists()) {
127-
String configJson = dockerConfigSourcePath.readToString();
128-
if (StringUtils.isNotBlank(configJson)) {
129-
this.launcher.getListener().getLogger().print("Using the existing docker config file.");
130-
131-
JSONObject json = JSONObject.fromObject(configJson);
132-
for (String property : blacklistedProperties) {
133-
Object value = json.remove(property);
134-
if (value != null) {
135-
this.launcher.getListener().getLogger().print("Removing blacklisted property: " + property);
136-
}
121+
private String removeBlacklistedProperties(@Nonnull String json, @Nonnull String[] blacklistedProperties) {
122+
String jsonString = "";
123+
if (StringUtils.isNotBlank(json)) {
124+
JSONObject jsonObject = JSONObject.fromObject(json);
125+
for (String property : blacklistedProperties) {
126+
Object value = jsonObject.remove(property);
127+
if (value != null) {
128+
this.launcher.getListener().getLogger().print("Removing blacklisted property: " + property + "\n");
137129
}
138-
139-
dockerConfig.child(DOCKER_CONFIG_FILENAME).write(json.toString(), StandardCharsets.UTF_8.name());
140130
}
131+
jsonString = jsonObject.toString();
141132
}
142-
return dockerConfig;
133+
return jsonString;
143134
}
144135

145136
private static class RegistryKeyMaterial extends KeyMaterial {

src/test/java/org/jenkinsci/plugins/docker/commons/impl/RegistryKeyMaterialFactoryTest.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import static org.hamcrest.Matchers.arrayContaining;
2828
import static org.hamcrest.Matchers.emptyArray;
2929
import static org.junit.Assert.assertEquals;
30+
import static org.junit.Assert.assertNotEquals;
3031
import static org.junit.Assert.assertNotNull;
3132
import static org.junit.Assert.assertThat;
3233
import static org.junit.Assert.assertTrue;
@@ -41,13 +42,15 @@
4142
import org.jenkinsci.plugins.docker.commons.credentials.KeyMaterialContext;
4243
import org.jenkinsci.plugins.docker.commons.credentials.KeyMaterialFactory;
4344
import org.jenkinsci.plugins.docker.commons.tools.DockerTool;
45+
import org.junit.After;
4446
import org.junit.Before;
4547
import org.junit.Rule;
4648
import org.junit.Test;
4749
import org.junit.rules.TemporaryFolder;
4850
import org.jvnet.hudson.test.FakeLauncher;
4951
import org.jvnet.hudson.test.JenkinsRule;
5052
import org.jvnet.hudson.test.PretendSlave;
53+
import net.sf.json.JSONObject;
5154

5255
import hudson.EnvVars;
5356
import hudson.FilePath;
@@ -66,6 +69,7 @@ public class RegistryKeyMaterialFactoryTest {
6669
@Rule
6770
public TemporaryFolder tempFolder = new TemporaryFolder();
6871

72+
private EnvVars env = new EnvVars();
6973
private KeyMaterialFactory factory;
7074

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

100104
URL endpoint = new DockerRegistryEndpoint(null, null).getEffectiveUrl();
101-
EnvVars env = new EnvVars();
102105
String dockerExecutable = DockerTool.getExecutable(null, null, listener, env);
103106

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

111+
@After
112+
public void reset() {
113+
env = new EnvVars();
114+
}
115+
108116
@Test
109117
public void materialize_userConfigFileNotPresent_notCreated() throws Exception {
110118
// act
@@ -232,4 +240,34 @@ public void materialize_userConfigFileWithCredStoreAndHttpHeaders_createdWithHea
232240
assertEquals("{\"HttpHeaders\":{\"User-Agent\":\"Docker-Client\"}}", FileUtils.readFileToString(jsonFile));
233241
}
234242

243+
@Test
244+
public void materialize_existingConfigFile_updatedWithCredentials() throws Exception {
245+
246+
// arrange
247+
File existingCfgFile = new File(new File(tempFolder.getRoot(), ".docker"), "config.json");
248+
String existingCfgPath = new File(existingCfgFile.getAbsolutePath()).getParent();
249+
String updatedConfigJson = "{" + "\"auths\":{\"localhost2:5001\":{\"auth\":\"whatever2\",\"email\":\"\"}}," +
250+
"\"proxies\":{\"default\":{\"httpProxy\":\"proxy\",\"noProxy\":\"something\"}}" + "}";
251+
FileUtils.write(existingCfgFile, updatedConfigJson);
252+
assertNotNull(existingCfgPath);
253+
env.put("DOCKER_CONFIG", existingCfgPath);
254+
assertNotNull(env.get("DOCKER_CONFIG", null));
255+
256+
// act
257+
KeyMaterial material = factory.materialize();
258+
259+
// assert
260+
String cfgFolderPath = material.env().get("DOCKER_CONFIG", null);
261+
assertNotNull(cfgFolderPath);
262+
assertNotEquals(cfgFolderPath, existingCfgPath);
263+
264+
File dockerCfgFolder = new File(cfgFolderPath);
265+
assertTrue(dockerCfgFolder.exists());
266+
267+
String[] existingFiles = dockerCfgFolder.list();
268+
assertThat(existingFiles, arrayContaining("config.json"));
269+
270+
File jsonFile = new File(dockerCfgFolder, "config.json");
271+
assertEquals(updatedConfigJson, FileUtils.readFileToString(jsonFile));
272+
}
235273
}

0 commit comments

Comments
 (0)