Skip to content

Commit 9fedb4c

Browse files
akomakomtimja
andauthored
Support SSH Config in template configuration (#357)
Co-authored-by: Tim Jacomb <timjacomb1@gmail.com>
1 parent a78b351 commit 9fedb4c

15 files changed

Lines changed: 136 additions & 7 deletions

File tree

src/main/java/com/microsoft/azure/vmagent/AzureVMAgent.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ public class AzureVMAgent extends AbstractCloudSlave implements TrackedItem {
105105

106106
private int sshPort;
107107

108+
private String sshConfig;
109+
108110
private final Mode mode;
109111

110112
private String templateName;
@@ -156,6 +158,7 @@ public AzureVMAgent(
156158
String vmCredentialsId,
157159
String sshPrivateKey,
158160
String sshPassPhrase,
161+
String sshConfig,
159162
String jvmOptions,
160163
boolean shutdownOnIdle,
161164
boolean eligibleForReuse,
@@ -187,6 +190,7 @@ public AzureVMAgent(
187190
this.azureCredentialsId = azureCredentialsId;
188191
this.sshPrivateKey = sshPrivateKey;
189192
this.sshPassPhrase = sshPassPhrase;
193+
this.sshConfig = sshConfig;
190194
this.jvmOptions = jvmOptions;
191195
this.shutdownOnIdle = shutdownOnIdle;
192196
this.eligibleForReuse = eligibleForReuse;
@@ -230,6 +234,7 @@ public AzureVMAgent(
230234
String vmCredentialsId,
231235
String sshPrivateKey,
232236
String sshPassPhrase,
237+
String sshConfig,
233238
String jvmOptions,
234239
boolean shutdownOnIdle,
235240
boolean eligibleForReuse,
@@ -272,6 +277,7 @@ public AzureVMAgent(
272277
vmCredentialsId,
273278
sshPrivateKey,
274279
sshPassPhrase,
280+
sshConfig,
275281
jvmOptions,
276282
shutdownOnIdle,
277283
eligibleForReuse,
@@ -434,6 +440,10 @@ public void setSshPort(int sshPort) {
434440
this.sshPort = sshPort;
435441
}
436442

443+
public String getSshConfig() {
444+
return sshConfig;
445+
}
446+
437447
public String getPublicIP() {
438448
return publicIP;
439449
}

src/main/java/com/microsoft/azure/vmagent/AzureVMAgentTemplate.java

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
2626
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
2727
import com.cloudbees.plugins.credentials.domains.DomainRequirement;
28+
import com.jcraft.jsch.OpenSSHConfig;
2829
import com.microsoft.azure.util.AzureBaseCredentials;
2930
import com.microsoft.azure.util.AzureCredentialUtil;
3031
import com.microsoft.azure.vmagent.builders.AdvancedImage;
@@ -301,6 +302,8 @@ public int hashCode() {
301302

302303
private boolean preInstallSsh;
303304

305+
private String sshConfig;
306+
304307
private final String initScript;
305308

306309
private final String terminateScript;
@@ -675,6 +678,8 @@ public static Map<String, Object> getTemplateProperties(AzureVMAgentTemplate tem
675678
isBasic ? defaultProperties.get(Constants.DEFAULT_OS_TYPE) : template.getOsType());
676679
templateProperties.put("agentLaunchMethod",
677680
isBasic ? defaultProperties.get(Constants.DEFAULT_LAUNCH_METHOD) : template.getAgentLaunchMethod());
681+
templateProperties.put("sshConfig",
682+
isBasic ? "" : template.getSshConfig());
678683
templateProperties.put("initScript",
679684
isBasic ? getBasicInitScript(template) : template.getInitScript());
680685
templateProperties.put("terminateScript",
@@ -1049,6 +1054,15 @@ public boolean isPreInstallSsh() {
10491054
return preInstallSsh;
10501055
}
10511056

1057+
public String getSshConfig() {
1058+
return sshConfig;
1059+
}
1060+
1061+
@DataBoundSetter
1062+
public void setSshConfig(String sshConfig) {
1063+
this.sshConfig = sshConfig;
1064+
}
1065+
10521066
public String getInitScript() {
10531067
return initScript;
10541068
}
@@ -1265,6 +1279,7 @@ public AdvancedImage getAdvancedImageInside() {
12651279
.withOsType(getOsType())
12661280
.withLaunchMethod(getAgentLaunchMethod())
12671281
.withPreInstallSsh(isPreInstallSsh())
1282+
.withSshConfig(getSshConfig())
12681283
.withInitScript(getInitScript())
12691284
.withVirtualNetworkName(getVirtualNetworkName())
12701285
.withVirtualNetworkResourceGroupName(getVirtualNetworkResourceGroupName())
@@ -1366,6 +1381,7 @@ public List<String> verifyTemplate() throws Exception {
13661381
builtInImage,
13671382
osType,
13681383
agentLaunchMethod,
1384+
sshConfig,
13691385
initScript,
13701386
credentialsId,
13711387
virtualNetworkName,
@@ -1585,6 +1601,18 @@ public FormValidation doCheckStorageAccountType(
15851601
return FormValidation.ok();
15861602
}
15871603

1604+
public FormValidation doCheckSshConfig(
1605+
@QueryParameter String value) {
1606+
if (!StringUtils.isBlank(value)) {
1607+
try {
1608+
OpenSSHConfig.parse(value);
1609+
} catch (IOException e) {
1610+
return FormValidation.warningWithMarkup(Messages.Ssh_Config_Invalid());
1611+
}
1612+
}
1613+
return FormValidation.ok();
1614+
}
1615+
15881616
public FormValidation doCheckInitScript(
15891617
@QueryParameter String value,
15901618
@QueryParameter String agentLaunchMethod) {
@@ -1714,6 +1742,7 @@ public FormValidation doVerifyConfiguration(
17141742
@QueryParameter String gallerySubscriptionId,
17151743
@QueryParameter String galleryResourceGroup,
17161744
@QueryParameter String agentLaunchMethod,
1745+
@QueryParameter String sshConfig,
17171746
@QueryParameter String initScript,
17181747
@QueryParameter String credentialsId,
17191748
@QueryParameter String virtualNetworkName,
@@ -1778,7 +1807,8 @@ public FormValidation doVerifyConfiguration(
17781807
+ "galleryImageDefinition: {30}\n\t"
17791808
+ "galleryImageVersion: {31}\n\t"
17801809
+ "galleryResourceGroup: {32}\n\t"
1781-
+ "gallerySubscriptionId: {33}",
1810+
+ "gallerySubscriptionId: {33}\n\t"
1811+
+ "sshConfig: {34}",
17821812
new Object[]{
17831813
"",
17841814
"",
@@ -1813,7 +1843,8 @@ public FormValidation doVerifyConfiguration(
18131843
galleryImageDefinition,
18141844
galleryImageVersion,
18151845
galleryResourceGroup,
1816-
gallerySubscriptionId});
1846+
gallerySubscriptionId,
1847+
sshConfig});
18171848

18181849
// First validate the subscription info. If it is not correct,
18191850
// then we can't validate the
@@ -1836,6 +1867,7 @@ public FormValidation doVerifyConfiguration(
18361867
builtInImage,
18371868
osType,
18381869
agentLaunchMethod,
1870+
sshConfig,
18391871
initScript,
18401872
credentialsId,
18411873
virtualNetworkName,

src/main/java/com/microsoft/azure/vmagent/AzureVMManagementServiceDelegate.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import com.fasterxml.jackson.databind.node.IntNode;
5757
import com.fasterxml.jackson.databind.node.ObjectNode;
5858

59+
import com.jcraft.jsch.OpenSSHConfig;
5960
import com.microsoft.azure.vmagent.exceptions.AzureCloudException;
6061
import com.microsoft.azure.vmagent.retry.NoRetryStrategy;
6162
import com.microsoft.azure.vmagent.util.*;
@@ -1117,6 +1118,7 @@ public AzureVMAgent parseResponse(
11171118
template.getCredentialsId(),
11181119
null,
11191120
null,
1121+
template.getAdvancedImageInside().getSshConfig(),
11201122
(String) properties.get("jvmOptions"),
11211123
template.isShutdownOnIdle(),
11221124
false,
@@ -1952,6 +1954,7 @@ public List<String> verifyTemplate(
19521954
String builtInImage,
19531955
String osType,
19541956
String agentLaunchMethod,
1957+
String sshConfig,
19551958
String initScript,
19561959
String credentialsId,
19571960
String virtualNetworkName,
@@ -2013,6 +2016,13 @@ public List<String> verifyTemplate(
20132016
return errors;
20142017
}
20152018

2019+
//verify SSH Config
2020+
validationResult = verifySshConfig(sshConfig);
2021+
addValidationResultIfFailed(validationResult, errors);
2022+
if (returnOnSingleError && errors.size() > 0) {
2023+
return errors;
2024+
}
2025+
20162026
validationResult = verifyImageParameters(
20172027
imageTopLevelType,
20182028
imageReferenceType,
@@ -2370,6 +2380,18 @@ private static String verifyJvmOptions(String jvmOptions) {
23702380
}
23712381
}
23722382

2383+
private static String verifySshConfig(String sshConfig) {
2384+
if (StringUtils.isBlank(sshConfig)) {
2385+
return Constants.OP_SUCCESS;
2386+
} else {
2387+
try {
2388+
OpenSSHConfig.parse(sshConfig);
2389+
} catch (IOException e) {
2390+
return Messages.Ssh_Config_Invalid() + e.getMessage();
2391+
}
2392+
}
2393+
return Constants.OP_SUCCESS;
2394+
}
23732395
/**
23742396
* Verify the validity of the image parameters (does not verify actual
23752397
* values).

src/main/java/com/microsoft/azure/vmagent/builders/AdvancedImage.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public class AdvancedImage {
3535

3636
private boolean preInstallSsh;
3737

38+
private String sshConfig;
39+
3840
private String initScript;
3941

4042
private String terminateScript;
@@ -81,6 +83,7 @@ public AdvancedImage(String imageReferenceType,
8183
String galleryResourceGroup,
8284
String agentLaunchMethod,
8385
boolean preInstallSsh,
86+
String sshConfig,
8487
String initScript,
8588
String terminateScript,
8689
boolean executeInitScriptAsRoot,
@@ -112,6 +115,7 @@ public AdvancedImage(String imageReferenceType,
112115
this.galleryResourceGroup = galleryResourceGroup;
113116
this.agentLaunchMethod = agentLaunchMethod;
114117
this.preInstallSsh = preInstallSsh;
118+
this.sshConfig = sshConfig;
115119
this.initScript = initScript;
116120
this.terminateScript = terminateScript;
117121
this.executeInitScriptAsRoot = executeInitScriptAsRoot;
@@ -193,6 +197,10 @@ public boolean isPreInstallSsh() {
193197
return preInstallSsh;
194198
}
195199

200+
public String getSshConfig() {
201+
return sshConfig;
202+
}
203+
196204
public String getInitScript() {
197205
return initScript;
198206
}

src/main/java/com/microsoft/azure/vmagent/builders/AdvancedImageBuilder.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public AdvancedImageBuilder(AdvancedImageFluent<?> fluent, AdvancedImage image)
2727
fluent.withOsType(image.getOsType());
2828
fluent.withLaunchMethod(image.getAgentLaunchMethod());
2929
fluent.withPreInstallSsh(image.isPreInstallSsh());
30+
fluent.withSshConfig(image.getSshConfig());
3031
fluent.withInitScript(image.getInitScript());
3132
fluent.withTerminateScript(image.getTerminateScript());
3233
fluent.withVirtualNetworkName(image.getVirtualNetworkName());
@@ -61,6 +62,7 @@ public AdvancedImageBuilder(AdvancedImage image) {
6162
fluent.withOsType(image.getOsType());
6263
fluent.withLaunchMethod(image.getAgentLaunchMethod());
6364
fluent.withPreInstallSsh(image.isPreInstallSsh());
65+
fluent.withSshConfig(image.getSshConfig());
6466
fluent.withInitScript(image.getInitScript());
6567
fluent.withTerminateScript(image.getTerminateScript());
6668
fluent.withVirtualNetworkName(image.getVirtualNetworkName());
@@ -91,6 +93,7 @@ public AdvancedImage build() {
9193
fluent.getGalleryResourceGroup(),
9294
fluent.getAgentLaunchMethod(),
9395
fluent.isPreInstallSsh(),
96+
fluent.getSshConfig(),
9497
fluent.getInitScript(),
9598
fluent.getTerminateScript(),
9699
fluent.isExecuteInitScriptAsRoot(),

src/main/java/com/microsoft/azure/vmagent/builders/AdvancedImageFluent.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public class AdvancedImageFluent<T extends AdvancedImageFluent<T>> {
3838

3939
private boolean preInstallSsh;
4040

41+
private String sshConfig;
42+
4143
private String initScript;
4244

4345
private String terminateScript;
@@ -140,6 +142,11 @@ public T withPreInstallSsh(boolean preInstallSsh) {
140142
return (T) this;
141143
}
142144

145+
public T withSshConfig(String sshConfig) {
146+
this.sshConfig = sshConfig;
147+
return (T) this;
148+
}
149+
143150
public T withInitScript(String initScript) {
144151
this.initScript = initScript;
145152
return (T) this;
@@ -279,6 +286,10 @@ public boolean isPreInstallSsh() {
279286
return preInstallSsh;
280287
}
281288

289+
public String getSshConfig() {
290+
return sshConfig;
291+
}
292+
282293
public String getInitScript() {
283294
return initScript;
284295
}

src/main/java/com/microsoft/azure/vmagent/builders/AzureVMTemplateBuilder.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ public AzureVMAgentTemplate build() {
118118
fluent.getAdvancedImage().isExecuteInitScriptAsRoot(),
119119
fluent.getAdvancedImage().isDoNotUseMachineIfInitFails()
120120
);
121+
azureVMAgentTemplate.setSshConfig(fluent.getAdvancedImage().getSshConfig());
121122
azureVMAgentTemplate.setShutdownOnIdle(fluent.isShutdownOnIdle());
122123
azureVMAgentTemplate.setEphemeralOSDisk(fluent.isEphemeralOSDisk());
123124
azureVMAgentTemplate.setOsDiskSize(fluent.getOsDiskSize());

src/main/java/com/microsoft/azure/vmagent/remote/AzureVMAgentSSHLauncher.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -279,11 +279,23 @@ private String getRemotingWorkingDirectory(boolean isUnix) {
279279
return "C:\\remoting";
280280
}
281281

282-
private Session getRemoteSession(String userName, String password, String dnsName, int sshPort) throws JSchException {
282+
private Session getRemoteSession(String userName, String password, String dnsName, int sshPort, String sshConfig) throws JSchException {
283283
LOGGER.log(Level.INFO,
284284
"Getting remote session for user {0} to host {1}:{2}",
285285
new Object[]{userName, dnsName, sshPort});
286286
JSch remoteClient = new JSch();
287+
if (StringUtils.isNotBlank(sshConfig)) {
288+
try {
289+
ConfigRepository configRepository = OpenSSHConfig.parse(sshConfig);
290+
remoteClient.setConfigRepository(configRepository);
291+
} catch (IOException e) {
292+
LOGGER.log(Level.SEVERE,
293+
"AzureVMAgentSSHLauncher: getRemoteSession: "
294+
+ "Got exception while using custom openssh config: {0} {1}",
295+
new Object[]{sshConfig, e.getMessage()});
296+
throw new JSchException("Unable to parse openssh config", e);
297+
}
298+
}
287299
final Session session = remoteClient.getSession(userName, dnsName, sshPort);
288300
session.setConfig("StrictHostKeyChecking", "no");
289301
session.setPassword(password);
@@ -296,7 +308,7 @@ private Session getRemoteSession(String userName, String password, String dnsNam
296308
new Object[]{userName, dnsName, sshPort});
297309
return session;
298310
}
299-
311+
300312
public void copyFileToRemote(AzureVMAgent agent, InputStream stream, String remotePath) throws Exception {
301313
copyFileToRemote(connectToSsh(agent), stream, remotePath);
302314
}
@@ -335,11 +347,11 @@ private void copyFileToRemote(Session jschSession, InputStream stream, String re
335347
public int executeRemoteCommand(AzureVMAgent agent, String command, PrintStream logger, boolean isUnix) throws Exception {
336348
return executeRemoteCommand(connectToSsh(agent), command, logger, isUnix, false, null);
337349
}
338-
350+
339351
public int executeRemoteCommand(AzureVMAgent agent, String command, PrintStream logger, boolean isUnix, boolean executeAsRoot, String passwordIfRoot) throws Exception {
340352
return executeRemoteCommand(connectToSsh(agent), command, logger, isUnix, executeAsRoot, passwordIfRoot);
341353
}
342-
354+
343355
/* Helper method for most common call (without root). */
344356
private int executeRemoteCommand(Session jschSession, String command, PrintStream logger, boolean isUnix) {
345357
return executeRemoteCommand(jschSession, command, logger, isUnix, false, null);
@@ -435,7 +447,8 @@ private Session connectToSsh(AzureVMAgent agent) throws Exception {
435447
creds.getUsername(),
436448
creds.getPassword().getPlainText(),
437449
agent.getPublicDNSName(),
438-
agent.getSshPort());
450+
agent.getSshPort(),
451+
agent.getSshConfig());
439452
LOGGER.fine("Got remote connection");
440453
} catch (Exception e) {
441454
// Retry till max count and throw exception if not successful even after that

src/main/java/com/microsoft/azure/vmagent/util/TemplateUtil.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public static boolean checkSame(AzureVMAgentTemplate a, AzureVMAgentTemplate b)
4040
b.getImageReference().getVersion())
4141
&& StringUtils.equals(a.getAgentLaunchMethod(), b.getAgentLaunchMethod())
4242
&& a.isPreInstallSsh() == b.isPreInstallSsh()
43+
&& StringUtils.equals(a.getSshConfig(), b.getSshConfig())
4344
&& StringUtils.equals(a.getInitScript(), b.getInitScript())
4445
&& StringUtils.equals(a.getTerminateScript(), b.getTerminateScript())
4546
&& a.getExecuteInitScriptAsRoot() == b.getExecuteInitScriptAsRoot()

src/main/resources/com/microsoft/azure/vmagent/AzureVMAgentTemplate/config.jelly

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,10 @@
212212
<f:select/>
213213
</f:entry>
214214

215+
<f:entry title="${%Ssh_Config}" field="sshConfig" help="/plugin/azure-vm-agents/help-sshConfig.html">
216+
<f:textarea/>
217+
</f:entry>
218+
215219
<f:entry title="${%Pre_Install_Ssh}" field="preInstallSsh"
216220
help="/plugin/azure-vm-agents/help-preInstallSsh.html">
217221
<f:checkbox default="true"/>

0 commit comments

Comments
 (0)