Skip to content

Commit

Permalink
Read docker credentials stdout and stderr independently (#8007)
Browse files Browse the repository at this point in the history
Fixes #8006 

---------

Co-authored-by: Eddú Meléndez Gonzales <eddu.melendez@gmail.com>
  • Loading branch information
Smeb and eddumelendez authored Oct 16, 2024
1 parent 3c7714e commit e35705b
Showing 1 changed file with 85 additions and 57 deletions.
142 changes: 85 additions & 57 deletions core/src/main/java/org/testcontainers/utility/RegistryAuthLocator.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.testcontainers.DockerClientFactory;
import org.zeroturnaround.exec.InvalidResultException;
import org.zeroturnaround.exec.ProcessExecutor;
import org.zeroturnaround.exec.stream.LogOutputStream;

import java.io.ByteArrayInputStream;
import java.io.File;
Expand Down Expand Up @@ -273,7 +274,7 @@ private AuthConfig runCredentialProvider(String hostName, String helperOrStoreNa
}

final String credentialProgramName = getCredentialProgramName(helperOrStoreName);
final String data;
final CredentialOutput data;

log.debug(
"Executing docker credential provider: {} to locate auth config for: {}",
Expand All @@ -283,37 +284,38 @@ private AuthConfig runCredentialProvider(String hostName, String helperOrStoreNa

try {
data = runCredentialProgram(hostName, credentialProgramName);
} catch (InvalidResultException e) {
final String responseErrorMsg = extractCredentialProviderErrorMessage(e);

if (!StringUtils.isBlank(responseErrorMsg)) {
String credentialsNotFoundMsg = getGenericCredentialsNotFoundMsg(credentialProgramName);
if (credentialsNotFoundMsg != null && credentialsNotFoundMsg.equals(responseErrorMsg)) {
log.info(
"Credential helper/store ({}) does not have credentials for {}",
if (data.getStderr() != null && !data.getStderr().isEmpty()) {
final String responseErrorMsg = data.getStderr();

if (!StringUtils.isBlank(responseErrorMsg)) {
String credentialsNotFoundMsg = getGenericCredentialsNotFoundMsg(credentialProgramName);
if (credentialsNotFoundMsg != null && credentialsNotFoundMsg.equals(responseErrorMsg)) {
log.info(
"Credential helper/store ({}) does not have credentials for {}",
credentialProgramName,
hostName
);

return null;
}

log.debug(
"Failure running docker credential helper/store ({}) with output '{}'",
credentialProgramName,
hostName
responseErrorMsg
);

return null;
} else {
log.debug("Failure running docker credential helper/store ({})", credentialProgramName);
}

log.debug(
"Failure running docker credential helper/store ({}) with output '{}'",
credentialProgramName,
responseErrorMsg
);
} else {
log.debug("Failure running docker credential helper/store ({})", credentialProgramName);
throw new InvalidResultException(data.getStderr(), null);
}

throw e;
} catch (Exception e) {
log.debug("Failure running docker credential helper/store ({})", credentialProgramName);
throw e;
}

final JsonNode helperResponse = OBJECT_MAPPER.readTree(data);
final JsonNode helperResponse = OBJECT_MAPPER.readTree(data.getStdout());
log.debug("Credential helper/store provided auth config for: {}", hostName);

final String username = helperResponse.at("/Username").asText();
Expand Down Expand Up @@ -363,55 +365,81 @@ private String discoverCredentialsHelperNotFoundMessage(String credentialHelperN

String credentialsNotFoundMsg = null;
try {
runCredentialProgram(notExistentFakeHostName, credentialHelperName);
CredentialOutput data = runCredentialProgram(notExistentFakeHostName, credentialHelperName);

// should not reach here
log.warn(
"Failure running docker credential helper ({}) with fake call, expected 'credentials not found' response",
credentialHelperName
);
} catch (Exception e) {
if (e instanceof InvalidResultException) {
credentialsNotFoundMsg = extractCredentialProviderErrorMessage((InvalidResultException) e);
}
if (data.getStderr() != null && !data.getStderr().isEmpty()) {
credentialsNotFoundMsg = data.getStderr();

if (StringUtils.isBlank(credentialsNotFoundMsg)) {
log.warn(
"Failure running docker credential helper ({}) with fake call, expected 'credentials not found' response. Exception message: {}",
credentialHelperName,
e.getMessage()
);
} else {
log.debug(
"Got credentials not found error message from docker credential helper - {}",
credentialsNotFoundMsg
);
}
} catch (Exception e) {
log.warn(
"Failure running docker credential helper ({}) with fake call, expected 'credentials not found' response. Exception message: {}",
credentialHelperName,
e.getMessage()
);
}

return credentialsNotFoundMsg;
}

private String extractCredentialProviderErrorMessage(InvalidResultException invalidResultEx) {
if (invalidResultEx.getResult() != null && invalidResultEx.getResult().hasOutput()) {
return invalidResultEx.getResult().outputString().trim();
}
return null;
}

private String runCredentialProgram(String hostName, String credentialHelperName)
throws InvalidResultException, InterruptedException, TimeoutException, IOException {
private CredentialOutput runCredentialProgram(String hostName, String credentialHelperName)
throws InterruptedException, TimeoutException, IOException {
String[] command = SystemUtils.IS_OS_WINDOWS
? new String[] { "cmd", "/c", credentialHelperName, "get" }
: new String[] { credentialHelperName, "get" };
return new ProcessExecutor()
.command(command)
.redirectInput(new ByteArrayInputStream(hostName.getBytes()))
.readOutput(true)
.exitValueNormal()
.timeout(30, TimeUnit.SECONDS)
.execute()
.outputUTF8()
.trim();

StringBuffer stdout = new StringBuffer();
StringBuffer stderr = new StringBuffer();

try {
new ProcessExecutor()
.command(command)
.redirectInput(new ByteArrayInputStream(hostName.getBytes()))
.redirectOutput(
new LogOutputStream() {
@Override
protected void processLine(String line) {
stdout.append(line).append(System.lineSeparator());
}
}
)
.redirectError(
new LogOutputStream() {
@Override
protected void processLine(String line) {
stderr.append(line).append(System.lineSeparator());
}
}
)
.exitValueNormal()
.timeout(30, TimeUnit.SECONDS)
.execute();
} catch (InvalidResultException e) {}

return new CredentialOutput(stdout.toString(), stderr.toString());
}

static class CredentialOutput {

private final String stdout;

private final String stderr;

public CredentialOutput(String stdout, String stderr) {
this.stdout = stdout.trim();
this.stderr = stderr.trim();
}

public String getStdout() {
return this.stdout;
}

public String getStderr() {
return this.stderr;
}
}
}

0 comments on commit e35705b

Please sign in to comment.