/*******************************************************************************
*
* Copyright (c) 2004-2009 Oracle Corporation.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*
* Kohsuke Kawaguchi
*
*
*******************************************************************************/
package hudson;
import hudson.matrix.MatrixBuild;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Computer;
import hudson.model.Describable;
import hudson.model.Job;
import hudson.model.TaskListener;
import hudson.model.Hudson;
import hudson.model.listeners.RunListener;
import hudson.scm.SCM;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* Prepares and provisions workspaces for {@link AbstractProject}s.
*
* <p>
*
*
* <p> STILL A WORK IN PROGRESS. SUBJECT TO CHANGE! DO NOT EXTEND.
*
* TODO: is this per {@link Computer}? Per {@link Job}? -> probably per slave.
*
* <h2>Design Problems</h2> <ol> <li> Garbage collection of snapshots. When do
* we discard snapshots? In one use case, it would be convenient to keep the
* snapshot of the last promoted/successful build. So we need to define a
* mechanism to veto GC of snapshot? like an interface that Action can
* implement?
*
* Snapshot should be obtained per user's direction. That would be a good moment
* for the user to specify the retention policy.
*
* <li> Configuration mechanism. Should we auto-detect FileSystemProvisioner per
* OS? (but for example, zfs support would require the root access.) People
* probably needs to be able to disable this feature, which means one more
* configuration option. It's especially tricky because during the configuration
* we don't know the OS type.
*
* OTOH special slave type like the ones for network.com grid can hide this.
* </ol>
*
*
* <h2>Recap</h2>
*
* To recap,
*
* - when a slave connects, we auto-detect the file system provisioner. (for
* example, ZFS FSP would check the slave root user prop and/or attempt to
* "pfexec zfs create" and take over.)
*
* - the user may configure jobs for snapshot collection, along with the
* retention policy.
*
* - keep workspace snapshots that correspond to the permalinks In ZFS, use a
* user property to remember the build and the job.
*
* Can't the 2nd step happen automatically, when someone else depends on the
* workspace snapshot of the upstream? Yes, by using {@link RunListener}. So
* this becomes like a special SCM type.
*
*
*
* <h2>Design take 2</h2> <p> The first piece of this is the custom {@link SCM},
* which inherits the workspace of another job. When this executes, it picks up
* {@link WorkspaceSnapshot} from the other job and use it to obtain the
* workspace.
*
* <p> Then there's {@link RunListener}, which creates a snapshot if someone
* else is interested in using a snapshot later.
*
* <h3>TODOs</h3> <ul> <li> Garbage collection of workspace snapshots.
*
* </ul>
*
* @author Kohsuke Kawaguchi
* @since 1.235
*/
public abstract class FileSystemProvisioner implements ExtensionPoint, Describable<FileSystemProvisioner> {
/**
* Called very early in the build (before a build places any files in the
* workspace, such as SCM checkout) to provision a workspace for the build.
*
* <p> This method can prepare the underlying file system in preparation for
* the later {@link #snapshot(AbstractBuild, FilePath, TaskListener)}.
*
* TODO : the method needs to be able to see the snapshot would be later
* needed. In fact, perhaps we should only call this method when Hudson
* knows that a snapshot is later needed?
*
* @param ws New workspace should be prepared in this location. This is the
* same value as {@code build.getProject().getWorkspace()} but passed
* separately for convenience.
*/
public abstract void prepareWorkspace(AbstractBuild<?, ?> build, FilePath ws, TaskListener listener) throws IOException, InterruptedException;
/**
* When a project is deleted, this method is called to undo the effect of
* {@link #prepareWorkspace(AbstractBuild, FilePath, TaskListener)}.
*
* @param project Project whose workspace is being discarded.
* @param ws Workspace to be discarded. This workspace is on the node this
* {@link FileSystemProvisioner} is provisioned for.
*/
public abstract void discardWorkspace(AbstractProject<?, ?> project, FilePath ws) throws IOException, InterruptedException;
// public abstract void moveWorkspace(AbstractProject<?,?> project, File oldWorkspace, File newWorkspace) throws IOException;
/**
* Obtains the snapshot of the workspace of the given build.
*
* <p> The state of the build when this method is invoked depends on the
* project type. Most would call this at the end of the build, but for
* example {@link MatrixBuild} would call this after SCM check out so that
* the state of the fresh workspace can be then propagated to elsewhere.
*
* <p> If the implementation of this method needs to store data in a file
* system, do so under {@link AbstractBuild#getRootDir()}, since the
* lifecycle of the snapshot is tied to the life cycle of a build.
*
* @param ws New workspace should be prepared in this location. This is the
* same value as {@code build.getProject().getWorkspace()} but passed
* separately for convenience.
* @param glob Ant-style file glob for files to include in the snapshot. May
* not be pertinent for all implementations.
*/
public abstract WorkspaceSnapshot snapshot(AbstractBuild<?, ?> build, FilePath ws, String glob, TaskListener listener) throws IOException, InterruptedException;
public FileSystemProvisionerDescriptor getDescriptor() {
return (FileSystemProvisionerDescriptor) Hudson.getInstance().getDescriptorOrDie(getClass());
}
/**
* Default implementation.
*/
public static final FileSystemProvisioner DEFAULT = new Default();
/**
* Returns all the registered {@link FileSystemProvisioner} descriptors.
*/
public static DescriptorExtensionList<FileSystemProvisioner, FileSystemProvisionerDescriptor> all() {
return Hudson.getInstance().<FileSystemProvisioner, FileSystemProvisionerDescriptor>getDescriptorList(FileSystemProvisioner.class);
}
/**
* Default implementation that doesn't rely on any file system specific
* capability, and thus can be used anywhere that Hudson runs.
*/
public static final class Default extends FileSystemProvisioner {
public void prepareWorkspace(AbstractBuild<?, ?> build, FilePath ws, TaskListener listener) throws IOException, InterruptedException {
}
public void discardWorkspace(AbstractProject<?, ?> project, FilePath ws) throws IOException, InterruptedException {
}
/**
* @deprecated as of 1.350
*/
public WorkspaceSnapshot snapshot(AbstractBuild<?, ?> build, FilePath ws, TaskListener listener) throws IOException, InterruptedException {
return snapshot(build, ws, "**/*", listener);
}
/**
* Creates a tar ball.
*/
public WorkspaceSnapshot snapshot(AbstractBuild<?, ?> build, FilePath ws, String glob, TaskListener listener) throws IOException, InterruptedException {
File wss = new File(build.getRootDir(), "workspace.zip");
OutputStream os = new BufferedOutputStream(new FileOutputStream(wss));
try {
ws.zip(os, glob);
} finally {
os.close();
}
return new WorkspaceSnapshotImpl();
}
public static final class WorkspaceSnapshotImpl extends WorkspaceSnapshot {
public void restoreTo(AbstractBuild<?, ?> owner, FilePath dst, TaskListener listener) throws IOException, InterruptedException {
File wss = new File(owner.getRootDir(), "workspace.zip");
new FilePath(wss).unzip(dst);
}
}
@Extension
public static final class DescriptorImpl extends FileSystemProvisionerDescriptor {
public boolean discard(FilePath ws, TaskListener listener) throws IOException, InterruptedException {
// the default provisioner doens't do anything special,
// so allow other types to manage it
return false;
}
public String getDisplayName() {
return "Default";
}
}
}
}