Skip to content

Commit

Permalink
Extend "vSphere" step (buildStep "Clone") with ability to specify nam…
Browse files Browse the repository at this point in the history
…edSnapshot and whether to useCurrentSnapshot
  • Loading branch information
jimklimov committed Jan 5, 2024
1 parent 8ce559a commit ef1984a
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 14 deletions.
115 changes: 102 additions & 13 deletions src/main/java/org/jenkinsci/plugins/vsphere/builders/Clone.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

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

Expand Down Expand Up @@ -60,20 +62,44 @@ public class Clone extends VSphereBuildStep {
private final Integer timeoutInSeconds;
private String IP;

/** Optionally used by {@code #linkedClone} setting or on its own,
* conflicts with {@code #namedSnapshot}. Is {@code null} by default. */
private final Boolean useCurrentSnapshot;
/** Optionally used by {@code #linkedClone} setting or on its own,
* conflicts with {@code #useCurrentSnapshot}. Is {@code null} by default. */
private final String namedSnapshot;
private final Map<String, String> extraConfigParameters;

@DataBoundConstructor
public Clone(String sourceName, String clone, boolean linkedClone,
String resourcePool, String cluster, String datastore, String folder,
boolean powerOn, Integer timeoutInSeconds, String customizationSpec) throws VSphereException {
boolean powerOn, Integer timeoutInSeconds, String customizationSpec,
Boolean useCurrentSnapshot, String namedSnapshot,
Map<String, String> extraConfigParameters) throws VSphereException {
this.sourceName = sourceName;
this.clone = clone;
this.linkedClone = linkedClone;
this.resourcePool=resourcePool;
this.cluster=cluster;
this.datastore=datastore;
this.folder=folder;
this.customizationSpec=customizationSpec;
this.powerOn=powerOn;
this.resourcePool = resourcePool;
this.cluster = cluster;
this.datastore = datastore;
this.folder = folder;
this.customizationSpec = customizationSpec;
this.powerOn = powerOn;
this.timeoutInSeconds = timeoutInSeconds;
this.useCurrentSnapshot = useCurrentSnapshot;

// Config form data may involve empty strings - treat them as null
if (namedSnapshot == null || namedSnapshot.isEmpty()) {
this.namedSnapshot = null;
} else {
this.namedSnapshot = namedSnapshot;
}

if (extraConfigParameters == null || extraConfigParameters.isEmpty()) {
this.extraConfigParameters = null;
} else {
this.extraConfigParameters = extraConfigParameters;
}
}

public String getSourceName() {
Expand All @@ -88,6 +114,27 @@ public boolean isLinkedClone() {
return linkedClone;
}

public String getNamedSnapshot() {
return namedSnapshot;
}

public boolean isUseCurrentSnapshot() {
if (useCurrentSnapshot == null) {
if (namedSnapshot == null) {
// Hard-coded default in VSphere.cloneVm()
// TOTHINK: Should this rely on linkedClone value?
return true;
}

// Will use specified named snapshot
return false;
}

// Caller had an explicit request
// Note that if linkedClone==true, at least some snapshot must be used
return useCurrentSnapshot;
}

public String getCluster() {
return cluster;
}
Expand Down Expand Up @@ -119,6 +166,10 @@ public int getTimeoutInSeconds() {
return timeoutInSeconds.intValue();
}

public Map<String, String> getExtraConfigParameters() {
return extraConfigParameters;
}

@Override
public void perform(@NonNull Run<?, ?> run, @NonNull FilePath filePath, @NonNull Launcher launcher, @NonNull TaskListener listener) throws InterruptedException, IOException {
try {
Expand Down Expand Up @@ -163,6 +214,8 @@ private boolean cloneFromSource(final Run<?, ?> run, final Launcher launcher, fi
String expandedFolder = folder;
String expandedResourcePool = resourcePool;
String expandedCustomizationSpec = customizationSpec;
String expandedNamedSnapshot = namedSnapshot;
Map<String, String> expandedExtraConfigParameters;
EnvVars env;
try {
env = run.getEnvironment(listener);
Expand All @@ -179,9 +232,29 @@ private boolean cloneFromSource(final Run<?, ?> run, final Launcher launcher, fi
expandedFolder = env.expand(folder);
expandedResourcePool = env.expand(resourcePool);
expandedCustomizationSpec = env.expand(customizationSpec);
if (namedSnapshot != null) {
expandedNamedSnapshot = env.expand(namedSnapshot);
}
}
vsphere.cloneVm(expandedClone, expandedSource, linkedClone, expandedResourcePool, expandedCluster,
expandedDatastore, expandedFolder, powerOn, expandedCustomizationSpec, jLogger);

if (extraConfigParameters != null && !(extraConfigParameters.isEmpty())) {
// Always pass a copy of the non-trivial original parameter map
// (expanded or not), just in case, to protect caller's data.
expandedExtraConfigParameters = new HashMap<String, String>();
if (run instanceof AbstractBuild) {
extraConfigParameters.forEach((k, v) -> expandedExtraConfigParameters.put(k, env.expand(v)));
} else {
expandedExtraConfigParameters.putAll(extraConfigParameters);
}
} else {
// Only init to null here, due to lambda used in forEach() above
expandedExtraConfigParameters = null;
}

vsphere.cloneOrDeployVm(expandedClone, expandedSource, linkedClone, expandedResourcePool, expandedCluster,
expandedDatastore, expandedFolder, this.isUseCurrentSnapshot(), expandedNamedSnapshot,
powerOn, expandedExtraConfigParameters, expandedCustomizationSpec, jLogger);

final int timeoutInSecondsForGetIp = getTimeoutInSeconds();
if (powerOn && timeoutInSecondsForGetIp>0) {
VSphereLogger.vsLogger(jLogger, "Powering on VM \""+expandedClone+"\". Waiting for its IP for the next "+timeoutInSecondsForGetIp+" seconds.");
Expand Down Expand Up @@ -264,7 +337,11 @@ public FormValidation doTestData(@AncestorInPath Item context,
@QueryParameter String serverName,
@QueryParameter String sourceName, @QueryParameter String clone,
@QueryParameter String resourcePool, @QueryParameter String cluster,
@QueryParameter String customizationSpec) {
@QueryParameter String customizationSpec,
@QueryParameter Boolean linkedClone,
@QueryParameter Boolean useCurrentSnapshot,
@QueryParameter String namedSnapshot) {
// TODO? @QueryParameter Map<String, String> extraConfigParameters

Check warning on line 344 in src/main/java/org/jenkinsci/plugins/vsphere/builders/Clone.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL: extraConfigParameters ? @QueryParameter Map
throwUnlessUserHasPermissionToConfigureJob(context);
try {
if (sourceName.length() == 0 || clone.length()==0 || serverName.length()==0
Expand All @@ -285,9 +362,21 @@ public FormValidation doTestData(@AncestorInPath Item context,
if (vm == null)
return FormValidation.error(Messages.validation_notFound("sourceName"));

VirtualMachineSnapshot snap = vm.getCurrentSnapShot();
if (snap == null)
return FormValidation.error(Messages.validation_noSnapshots());
if (linkedClone || useCurrentSnapshot || (namedSnapshot != null && !(namedSnapshot.isEmpty()))) {
// Use-case (according to parameters) requires a snapshot
VirtualMachineSnapshot snap;
if (namedSnapshot == null || namedSnapshot.isEmpty()) {
// either useCurrentSnapshot or linkedClone is true
snap = vm.getCurrentSnapShot();
} else {
// namedSnapshot is non-trivial
if (useCurrentSnapshot)
return FormValidation.error(Messages.validation_useCurrentAndNamedSnapshots());
snap = vsphere.getSnapshotInTree(vm, namedSnapshot);
}
if (snap == null)
return FormValidation.error(Messages.validation_noSnapshots());
}

if(customizationSpec != null && customizationSpec.length() > 0 &&
vsphere.getCustomizationSpecByName(customizationSpec) == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,8 @@ public void cloneOrDeployVm(String cloneName, String sourceName, boolean linkedC
// NOTE: This "if" clause may be superfluous - just that previously
// this message was only logged by cloneVm() or deployVm()... so for
// least surprise and unexpected noise in the logs, effectively kept
// so for upgraded plugins.
// so for upgraded plugins where we can also directly call this method
// as a "buildStep" under a "vSphere" pipeline step.
if (useCurrentSnapshot) {
// Called from cloneVm() above.
logMessage(jLogger, "Creating a " + (linkedClone ? "shallow" : "deep") + " clone of \"" + sourceName + "\" to \"" + cloneName + "\"");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ limitations under the License.
<f:checkbox />
</f:entry>

<f:entry title="${%Use Current Snapshot?}" field="useCurrentSnapshot">
<f:checkbox />
</f:entry>

<f:entry title="${%Use Named Snapshot?}" field="namedSnapshot">
<f:textbox />
</f:entry>

<f:entry title="${%Cluster}" field="cluster">
<f:textbox />
</f:entry>
Expand All @@ -54,5 +62,7 @@ limitations under the License.
</f:entry>
</f:optionalBlock>

<!-- TODO: How can we specify an optional Map<String,String> to set extraConfigParameters? -->

<f:validateButton title="${%Check Data}" progress="${%Testing...}" method="testData" with="serverName,sourceName,clone,resourcePool,cluster"/>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div>
(Optional) A Map of parameters to set in the VM's "extra config"
object. This data can then be read back at a later stage.
In the case of parameters whose name starts "guestinfo.", the
parameter can be read by the VMware Tools on the client OS.
e.g. a variable named "guestinfo.Foo" with value "Bar" could
be read on the guest using the command-line
<tt>vmtoolsd --cmd "info-get guestinfo.Foo"</tt>.
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div>
If set then the clone will be created from the source VM's
snapshot of this name.
If this is set then <tt>useCurrentSnapshot</tt> must not be set.
May impact the <tt>linkedClone</tt> behavior.
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div>
If true then the clone will be created from the source VM's
"current" snapshot. This means that the VM <em>must</em> have
at least one snapshot.
If this is set then <tt>namedSnapshot</tt> must not be set.
May impact the <tt>linkedClone</tt> behavior.
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ validation.alreadySet=Specified {0} is already a {1}!
validation.exists=Specified {0} already exists!
validation.notActually=Specified {0} is not actually a {0}!
validation.noSnapshots=No snapshots found for specified template!
validation.useCurrentAndNamedSnapshots=Can not specify use of both a named and a current snapshot!
validation.positiveInteger={0} must be a positive integer!
validation.maxValue=Please enter a value less than {0}!
validation.success=Success
Expand Down

0 comments on commit ef1984a

Please sign in to comment.