Skip to content

Commit 10572de

Browse files
committed
Extend "vSphere" step (buildStep "Clone") with ability to specify namedSnapshot and whether to useCurrentSnapshot
1 parent 8ce559a commit 10572de

File tree

7 files changed

+137
-14
lines changed

7 files changed

+137
-14
lines changed

src/main/java/org/jenkinsci/plugins/vsphere/builders/Clone.java

Lines changed: 102 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import java.io.PrintStream;
2929
import java.io.PrintWriter;
3030
import java.io.StringWriter;
31+
import java.util.HashMap;
32+
import java.util.Map;
3133

3234
import edu.umd.cs.findbugs.annotations.NonNull;
3335

@@ -60,20 +62,44 @@ public class Clone extends VSphereBuildStep {
6062
private final Integer timeoutInSeconds;
6163
private String IP;
6264

65+
/** Optionally used by {@code #linkedClone} setting or on its own,
66+
* conflicts with {@code #namedSnapshot}. Is {@code null} by default. */
67+
private final Boolean useCurrentSnapshot;
68+
/** Optionally used by {@code #linkedClone} setting or on its own,
69+
* conflicts with {@code #useCurrentSnapshot}. Is {@code null} by default. */
70+
private final String namedSnapshot;
71+
private final Map<String, String> extraConfigParameters;
72+
6373
@DataBoundConstructor
6474
public Clone(String sourceName, String clone, boolean linkedClone,
6575
String resourcePool, String cluster, String datastore, String folder,
66-
boolean powerOn, Integer timeoutInSeconds, String customizationSpec) throws VSphereException {
76+
boolean powerOn, Integer timeoutInSeconds, String customizationSpec,
77+
Boolean useCurrentSnapshot, String namedSnapshot,
78+
Map<String, String> extraConfigParameters) throws VSphereException {
6779
this.sourceName = sourceName;
6880
this.clone = clone;
6981
this.linkedClone = linkedClone;
70-
this.resourcePool=resourcePool;
71-
this.cluster=cluster;
72-
this.datastore=datastore;
73-
this.folder=folder;
74-
this.customizationSpec=customizationSpec;
75-
this.powerOn=powerOn;
82+
this.resourcePool = resourcePool;
83+
this.cluster = cluster;
84+
this.datastore = datastore;
85+
this.folder = folder;
86+
this.customizationSpec = customizationSpec;
87+
this.powerOn = powerOn;
7688
this.timeoutInSeconds = timeoutInSeconds;
89+
this.useCurrentSnapshot = useCurrentSnapshot;
90+
91+
// Config form data may involve empty strings - treat them as null
92+
if (namedSnapshot == null || namedSnapshot.isEmpty()) {
93+
this.namedSnapshot = null;
94+
} else {
95+
this.namedSnapshot = namedSnapshot;
96+
}
97+
98+
if (extraConfigParameters == null || extraConfigParameters.isEmpty()) {
99+
this.extraConfigParameters = null;
100+
} else {
101+
this.extraConfigParameters = extraConfigParameters;
102+
}
77103
}
78104

79105
public String getSourceName() {
@@ -88,6 +114,27 @@ public boolean isLinkedClone() {
88114
return linkedClone;
89115
}
90116

117+
public String getNamedSnapshot() {
118+
return namedSnapshot;
119+
}
120+
121+
public boolean isUseCurrentSnapshot() {
122+
if (useCurrentSnapshot == null) {
123+
if (namedSnapshot == null) {
124+
// Hard-coded default in VSphere.cloneVm()
125+
// TOTHINK: Should this rely on linkedClone value?
126+
return true;
127+
}
128+
129+
// Will use specified named snapshot
130+
return false;
131+
}
132+
133+
// Caller had an explicit request
134+
// Note that if linkedClone==true, at least some snapshot must be used
135+
return useCurrentSnapshot;
136+
}
137+
91138
public String getCluster() {
92139
return cluster;
93140
}
@@ -119,6 +166,10 @@ public int getTimeoutInSeconds() {
119166
return timeoutInSeconds.intValue();
120167
}
121168

169+
public Map<String, String> getExtraConfigParameters() {
170+
return extraConfigParameters;
171+
}
172+
122173
@Override
123174
public void perform(@NonNull Run<?, ?> run, @NonNull FilePath filePath, @NonNull Launcher launcher, @NonNull TaskListener listener) throws InterruptedException, IOException {
124175
try {
@@ -163,6 +214,8 @@ private boolean cloneFromSource(final Run<?, ?> run, final Launcher launcher, fi
163214
String expandedFolder = folder;
164215
String expandedResourcePool = resourcePool;
165216
String expandedCustomizationSpec = customizationSpec;
217+
String expandedNamedSnapshot = namedSnapshot;
218+
Map<String, String> expandedExtraConfigParameters;
166219
EnvVars env;
167220
try {
168221
env = run.getEnvironment(listener);
@@ -179,9 +232,29 @@ private boolean cloneFromSource(final Run<?, ?> run, final Launcher launcher, fi
179232
expandedFolder = env.expand(folder);
180233
expandedResourcePool = env.expand(resourcePool);
181234
expandedCustomizationSpec = env.expand(customizationSpec);
235+
if (namedSnapshot != null) {
236+
expandedNamedSnapshot = env.expand(namedSnapshot);
237+
}
182238
}
183-
vsphere.cloneVm(expandedClone, expandedSource, linkedClone, expandedResourcePool, expandedCluster,
184-
expandedDatastore, expandedFolder, powerOn, expandedCustomizationSpec, jLogger);
239+
240+
if (extraConfigParameters != null && !(extraConfigParameters.isEmpty())) {
241+
// Always pass a copy of the non-trivial original parameter map
242+
// (expanded or not), just in case, to protect caller's data.
243+
expandedExtraConfigParameters = new HashMap<String, String>();
244+
if (run instanceof AbstractBuild) {
245+
extraConfigParameters.forEach((k, v) -> expandedExtraConfigParameters.put(k, env.expand(v)));
246+
} else {
247+
expandedExtraConfigParameters.putAll(extraConfigParameters);
248+
}
249+
} else {
250+
// Only init to null here, due to lambda used in forEach() above
251+
expandedExtraConfigParameters = null;
252+
}
253+
254+
vsphere.cloneOrDeployVm(expandedClone, expandedSource, linkedClone, expandedResourcePool, expandedCluster,
255+
expandedDatastore, expandedFolder, this.isUseCurrentSnapshot(), expandedNamedSnapshot,
256+
powerOn, expandedExtraConfigParameters, expandedCustomizationSpec, jLogger);
257+
185258
final int timeoutInSecondsForGetIp = getTimeoutInSeconds();
186259
if (powerOn && timeoutInSecondsForGetIp>0) {
187260
VSphereLogger.vsLogger(jLogger, "Powering on VM \""+expandedClone+"\". Waiting for its IP for the next "+timeoutInSecondsForGetIp+" seconds.");
@@ -264,7 +337,11 @@ public FormValidation doTestData(@AncestorInPath Item context,
264337
@QueryParameter String serverName,
265338
@QueryParameter String sourceName, @QueryParameter String clone,
266339
@QueryParameter String resourcePool, @QueryParameter String cluster,
267-
@QueryParameter String customizationSpec) {
340+
@QueryParameter String customizationSpec,
341+
@QueryParameter Boolean linkedClone,
342+
@QueryParameter Boolean useCurrentSnapshot,
343+
@QueryParameter String namedSnapshot) {
344+
// TODO? @QueryParameter Map<String, String> extraConfigParameters
268345
throwUnlessUserHasPermissionToConfigureJob(context);
269346
try {
270347
if (sourceName.length() == 0 || clone.length()==0 || serverName.length()==0
@@ -285,9 +362,21 @@ public FormValidation doTestData(@AncestorInPath Item context,
285362
if (vm == null)
286363
return FormValidation.error(Messages.validation_notFound("sourceName"));
287364

288-
VirtualMachineSnapshot snap = vm.getCurrentSnapShot();
289-
if (snap == null)
290-
return FormValidation.error(Messages.validation_noSnapshots());
365+
if (linkedClone || useCurrentSnapshot || (namedSnapshot != null && !(namedSnapshot.isEmpty()))) {
366+
// Use-case (according to parameters) requires a snapshot
367+
VirtualMachineSnapshot snap;
368+
if (namedSnapshot == null || namedSnapshot.isEmpty()) {
369+
// either useCurrentSnapshot or linkedClone is true
370+
snap = vm.getCurrentSnapShot();
371+
} else {
372+
// namedSnapshot is non-trivial
373+
if (useCurrentSnapshot)
374+
return FormValidation.error(Messages.validation_useCurrentAndNamedSnapshots());
375+
snap = vsphere.getSnapshotInTree(vm, namedSnapshot);
376+
}
377+
if (snap == null)
378+
return FormValidation.error(Messages.validation_noSnapshots());
379+
}
291380

292381
if(customizationSpec != null && customizationSpec.length() > 0 &&
293382
vsphere.getCustomizationSpecByName(customizationSpec) == null) {

src/main/java/org/jenkinsci/plugins/vsphere/tools/VSphere.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,8 @@ public void cloneOrDeployVm(String cloneName, String sourceName, boolean linkedC
222222
// NOTE: This "if" clause may be superfluous - just that previously
223223
// this message was only logged by cloneVm() or deployVm()... so for
224224
// least surprise and unexpected noise in the logs, effectively kept
225-
// so for upgraded plugins.
225+
// so for upgraded plugins where we can also directly call this method
226+
// as a "buildStep" under a "vSphere" pipeline step.
226227
if (useCurrentSnapshot) {
227228
// Called from cloneVm() above.
228229
logMessage(jLogger, "Creating a " + (linkedClone ? "shallow" : "deep") + " clone of \"" + sourceName + "\" to \"" + cloneName + "\"");

src/main/resources/org/jenkinsci/plugins/vsphere/builders/Clone/config.jelly

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ limitations under the License.
2828
<f:checkbox />
2929
</f:entry>
3030

31+
<f:entry title="${%Use Current Snapshot?}" field="useCurrentSnapshot">
32+
<f:checkbox />
33+
</f:entry>
34+
35+
<f:entry title="${%Use Named Snapshot?}" field="namedSnapshot">
36+
<f:checkbox />
37+
</f:entry>
38+
3139
<f:entry title="${%Cluster}" field="cluster">
3240
<f:textbox />
3341
</f:entry>
@@ -54,5 +62,7 @@ limitations under the License.
5462
</f:entry>
5563
</f:optionalBlock>
5664

65+
<!-- TODO: How can we specify an optional Map<String,String> to set extraConfigParameters? -->
66+
5767
<f:validateButton title="${%Check Data}" progress="${%Testing...}" method="testData" with="serverName,sourceName,clone,resourcePool,cluster"/>
5868
</j:jelly>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<div>
2+
(Optional) A Map of parameters to set in the VM's "extra config"
3+
object. This data can then be read back at a later stage.
4+
In the case of parameters whose name starts "guestinfo.", the
5+
parameter can be read by the VMware Tools on the client OS.
6+
e.g. a variable named "guestinfo.Foo" with value "Bar" could
7+
be read on the guest using the command-line
8+
<tt>vmtoolsd --cmd "info-get guestinfo.Foo"</tt>.
9+
</div>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<div>
2+
If set then the clone will be created from the source VM's
3+
snapshot of this name.
4+
If this is set then <tt>useCurrentSnapshot</tt> must not be set.
5+
May impact the <tt>linkedClone</tt> behavior.
6+
</div>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<div>
2+
If true then the clone will be created from the source VM's
3+
"current" snapshot. This means that the VM <em>must</em> have
4+
at least one snapshot.
5+
If this is set then <tt>namedSnapshot</tt> must not be set.
6+
May impact the <tt>linkedClone</tt> behavior.
7+
</div>

src/main/resources/org/jenkinsci/plugins/vsphere/builders/Messages.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ validation.alreadySet=Specified {0} is already a {1}!
3333
validation.exists=Specified {0} already exists!
3434
validation.notActually=Specified {0} is not actually a {0}!
3535
validation.noSnapshots=No snapshots found for specified template!
36+
validation.useCurrentAndNamedSnapshots=Can not specify use of both a named and a current snapshot!
3637
validation.positiveInteger={0} must be a positive integer!
3738
validation.maxValue=Please enter a value less than {0}!
3839
validation.success=Success

0 commit comments

Comments
 (0)