Skip to content

Commit

Permalink
Handle legacy SC CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
alexh-sauce committed Jan 16, 2025
1 parent 6fbf108 commit 0faf1b3
Show file tree
Hide file tree
Showing 4 changed files with 264 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ protected void incrementProcessCountForUser(TunnelInformation name, PrintStream
* @param printStream the output stream to send log messages
* @param sauceConnectPath if defined, Sauce Connect will be launched from the specified path and
* won't be extracted from the jar file
* @param legacy options uses SC4 style CLI
* @return new ProcessBuilder instance which will launch Sauce Connect
* @throws SauceConnectException thrown if an error occurs launching the Sauce Connect process
*/
Expand All @@ -292,7 +293,8 @@ protected abstract Process prepAndCreateProcess(
File sauceConnectJar,
String options,
PrintStream printStream,
String sauceConnectPath)
String sauceConnectPath,
boolean legacy)
throws SauceConnectException;

/**
Expand Down Expand Up @@ -546,7 +548,158 @@ public Process openConnection(
}
final Process process =
prepAndCreateProcess(
username, apiKey, port, sauceConnectJar, options, printStream, sauceConnectPath);
username, apiKey, port, sauceConnectJar, options, printStream, sauceConnectPath, false);

// Print sauceconnect process stdout/stderr
new ProcessOutputPrinter(process.getInputStream(), (String x) -> logMessage(printStream, x)).start();
new ProcessOutputPrinter(process.getErrorStream(), (String x) -> logErrorMessage(printStream, x)).start();

List<Process> openedProcesses = this.openedProcesses.get(name);
try {
Semaphore semaphore = new Semaphore(1);
semaphore.acquire();

SCMonitor scMonitor;
if ( this.scMonitor != null ) {
scMonitor = this.scMonitor;
} else {
scMonitor = new SCMonitor("SCMonitor", port, LOGGER);
}

scMonitor.setSemaphore(semaphore);
scMonitor.start();

boolean sauceConnectStarted = semaphore.tryAcquire(3, TimeUnit.MINUTES);
if (sauceConnectStarted) {
if (scMonitor.isFailed()) {
String message = "Error launching Sauce Connect";
logMessage(printStream, message);
// ensure that Sauce Connect process is closed
closeSauceConnectProcess(printStream, process);
throw new SauceConnectDidNotStartException(message);
} else {
// everything okay, continue the build
String provisionedTunnelId = scMonitor.getTunnelId();
if (provisionedTunnelId != null) {
tunnelInformation.setTunnelId(provisionedTunnelId);
waitForReadiness(provisionedTunnelId);
}
logMessage(
printStream, "Sauce Connect " + getCurrentVersion() + " now launched for: " + name);
}
} else {
File sauceConnectLogFile = getSauceConnectLogFile(options);
String message =
sauceConnectLogFile != null
? "Time out while waiting for Sauce Connect to start, please check the Sauce Connect log located in "
+ sauceConnectLogFile.getAbsoluteFile()
: "Time out while waiting for Sauce Connect to start, please check the Sauce Connect log";
logMessage(printStream, message);
// ensure that Sauce Connect process is closed
closeSauceConnectProcess(printStream, process);
throw new SauceConnectDidNotStartException(message);
}
} catch (InterruptedException e) {
// continue;
LOGGER.warn("Exception occurred during invocation of Sauce Connect", e);
}

incrementProcessCountForUser(tunnelInformation, printStream);
tunnelInformation.setProcess(process);
List<Process> processes = openedProcesses;
if (processes == null) {
processes = new ArrayList<>();
this.openedProcesses.put(name, processes);
}
processes.add(process);
return process;
} finally {
// release the access lock
tunnelInformation.getLock().unlock();
launchAttempts.set(0);
}
}

/**
* Creates a new process to run Sauce Connect.
*
* @param username the name of the Sauce OnDemand user
* @param apiKey the API Key for the Sauce OnDemand user
* @param dataCenter the Sauce Labs Data Center
* @param port the port which Sauce Connect should be run on
* @param sauceConnectJar the Jar file containing Sauce Connect. If null, then we attempt to find
* Sauce Connect from the classpath (only used by SauceConnectTwoManager)
* @param options the command line options to pass to Sauce Connect
* @param printStream A print stream in which to redirect the output from Sauce Connect to. Can be
* null
* @param verboseLogging indicates whether verbose logging should be output
* @param sauceConnectPath if defined, Sauce Connect will be launched from the specified path and
* won't be extracted from the jar file
* @param legacy options are in SC4 CLI style
* @return a {@link Process} instance which represents the Sauce Connect instance
* @throws SauceConnectException thrown if an error occurs launching Sauce Connect
*/
@Override
public Process openConnection(
String username,
String apiKey,
DataCenter dataCenter,
int port,
File sauceConnectJar,
String options,
PrintStream printStream,
Boolean verboseLogging,
String sauceConnectPath,
boolean legacy)
throws SauceConnectException {

// ensure that only a single thread attempts to open a connection
if (sauceRest == null) {
setSauceRest(new SauceREST(username, apiKey, dataCenter));
}
String name = getTunnelName(options, username);
TunnelInformation tunnelInformation = getTunnelInformation(name);
try {

tunnelInformation.getLock().lock();
if (options == null) {
options = "";
}
if (verboseLogging != null) {
this.quietMode = !verboseLogging;
}

// do we have an instance for the tunnel name?
String tunnelID = activeTunnelID(username, name);
if (tunnelInformation.getProcessCount() == 0) {
// if the count is zero, check to see if there are any active tunnels

if (tunnelID != null) {
// if we have an active tunnel, but the process count is zero, we have an orphaned SC
// process
// instead of deleting the tunnel, log a message
logMessage(printStream, "Detected active tunnel: " + tunnelID);
}
} else {

// check active tunnels via Sauce REST API
if (tunnelID == null) {
logMessage(
printStream, "Process count non-zero, but no active tunnels found for name: " + name);
logMessage(printStream, "Process count reset to zero");
// if no active tunnels, we have a mismatch of the tunnel count
// reset tunnel count to zero and continue to launch Sauce Connect
tunnelInformation.setProcessCount(0);
} else {
// if we have an active tunnel, increment counter and return
logMessage(printStream, "Sauce Connect already running for " + name);
incrementProcessCountForUser(tunnelInformation, printStream);
return tunnelInformation.getProcess();
}
}
final Process process =
prepAndCreateProcess(
username, apiKey, port, sauceConnectJar, options, printStream, sauceConnectPath, legacy);

// Print sauceconnect process stdout/stderr
new ProcessOutputPrinter(process.getInputStream(), (String x) -> logMessage(printStream, x)).start();
Expand Down Expand Up @@ -697,6 +850,23 @@ protected abstract String[] generateSauceConnectArgs(

protected abstract String[] addExtraInfo(String[] args);

/**
* Returns the arguments to be used to launch Sauce Connect
*
* @param args the initial Sauce Connect command line args
* @param username name of the user which launched Sauce Connect
* @param apiKey the access key for the Sauce user
* @param options command line args specified by the user
* @return String array representing the command line args to be used to launch Sauce Connect
* @deprecated Use {@link #generateSauceConnectArgs(String[], String, String, String)} instead
*/
@Deprecated
protected abstract String[] generateSauceConnectArgsLegacy(
String[] args, String username, String apiKey, String options);

@Deprecated
protected abstract String[] addExtraInfoLegacy(String[] args);

/**
* @return the user's home directory
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,8 @@ protected Process prepAndCreateProcess(
File sauceConnectJar,
String options,
PrintStream printStream,
String sauceConnectPath)
String sauceConnectPath,
boolean legacy)
throws SauceConnectException {

// find zip file to extract
Expand Down Expand Up @@ -253,8 +254,13 @@ protected Process prepAndCreateProcess(

// although we are setting the working directory, we need to specify the full path to the exe
String[] args = {sauceConnectBinary.getPath()};
args = generateSauceConnectArgs(args, username, accessKey, options);
args = addExtraInfo(args);
if (legacy) {
args = generateSauceConnectArgsLegacy(args, username, accessKey, options);
args = addExtraInfoLegacy(args);
} else {
args = generateSauceConnectArgs(args, username, accessKey, options);
args = addExtraInfo(args);
}

LOGGER.info("Launching Sauce Connect {} {}", getCurrentVersion(), hideSauceConnectCommandlineSecrets(args));
return createProcess(args, sauceConnectBinary.getParentFile());
Expand All @@ -267,6 +273,7 @@ public String hideSauceConnectCommandlineSecrets(String[] args) {
var map = new HashMap<String, String>();
map.put("-k", "^().*");
map.put("--access-key", "^().*");
map.put("--api-key", "^().*"); // Legacy command
map.put("-a", "^().*");
map.put("--auth", "^().*");
map.put("--api-basic-auth", "^([^:]*:).*");
Expand All @@ -276,6 +283,7 @@ public String hideSauceConnectCommandlineSecrets(String[] args) {

var replaceMap = new HashMap<String, String>();
replaceMap.put("-k", "****");
replaceMap.put("--api-key", "****");
replaceMap.put("--access-key", "****");
replaceMap.put("-a", "****");
replaceMap.put("--auth", "****");
Expand Down Expand Up @@ -357,6 +365,35 @@ protected String[] addExtraInfo(String[] args) {
return result;
}

/**
* @param args the initial Sauce Connect command line args
* @param username name of the user which launched Sauce Connect
* @param accessKey the access key for the Sauce user
* @param options command line args specified by the user
* @return String array representing the command line args to be used to launch Sauce Connect
*/
@Deprecated
protected String[] generateSauceConnectArgsLegacy(
String[] args, String username, String accessKey, String options) {
String[] result;

result = joinArgs(args, "legacy", "--user", username.trim(), "--api-key", accessKey.trim(), "--status-address", "0.0.0.0:" + String.valueOf(this.apiPort));
result = addElement(result, options);
return result;
}

@Deprecated
protected String[] addExtraInfoLegacy(String[] args) {
String[] result;
OperatingSystem operatingSystem = OperatingSystem.getOperatingSystem();
if (operatingSystem == OperatingSystem.WINDOWS_ARM64 || operatingSystem == OperatingSystem.WINDOWS_AMD64) {
result = joinArgs(args, "--extra-info", "{\\\"runner\\\": \\\"" + runner + "\\\"}");
} else {
result = joinArgs(args, "--extra-info", "{\"runner\": \"" + runner + "\"}");
}
return result;
}

/**
* @param workingDirectory the destination directory
* @param operatingSystem represents the current operating system
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,37 @@ Process openConnection(
Boolean verboseLogging,
String sauceConnectPath)
throws IOException;

/**
* Creates a new process to run Sauce Connect.
*
* @param username the name of the Sauce OnDemand user
* @param apiKey the API Key for the Sauce OnDemand user
* @param dataCenter the Sauce Labs Data Center
* @param port the port which Sauce Connect should be run on
* @param sauceConnectJar the Jar file containing Sauce Connect. If null, then we attempt to find
* Sauce Connect from the classpath (only used by SauceConnectTwoManager)
* @param options the command line options to pass to Sauce Connect
* @param printStream A print stream in which to redirect the output from Sauce Connect to. Can be
* null
* @param verboseLogging indicates whether verbose logging should be output
* @param sauceConnectPath if defined, Sauce Connect will be launched from the specified path and
* won't be extracted from the jar file
* @param legacy command line options are using SC4 CLI
* @return a {@link Process} instance which represents the Sauce Connect instance
* @throws IOException thrown if an error occurs launching Sauce Connect
*/
@Deprecated
Process openConnection(
String username,
String apiKey,
DataCenter dataCenter,
int port,
File sauceConnectJar,
String options,
PrintStream printStream,
Boolean verboseLogging,
String sauceConnectPath,
boolean legacy)
throws IOException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,25 @@ void testSauceConnectSecretsCoveredWithStars() {

}

@Test
void testSauceConnectLegacySecretsCoveredWithStars() {
// FIXME: Remove when SC4 legacy support is removed
SauceConnectManager manager = new SauceConnectManager();
String[] args = {"/sauce/connect/binary/path/"};
args =
manager.generateSauceConnectArgs(
args,
"username",
"apikey",
"--api-key apiKey");
String result = manager.hideSauceConnectCommandlineSecrets(args);

assertEquals(
"[/sauce/connect/binary/path/, run, --username, username, --access-key, ****, --api-address, :9000, --api-key, ****]",
result);

}

@Test
void testSauceConnectAuthSecretsCoveredWithStars() {
SauceConnectManager manager = new SauceConnectManager();
Expand Down

0 comments on commit 0faf1b3

Please sign in to comment.