Jenkins插件开发(6.1)—— 分析JenkinsJOB的CRUD源码

标题上说CRUD,其实不太准确。

我的这个插件不关注Retrieve操作,所以只研究:Create Job, Update Job, Rename Job, Delete Job

1. Create(创建JOB)

Jenkins Job的创建有三种方式:

  • 通过Copy已有JOB创建
  • 通过CLI命令远程创建
  • 通过Jenkins创建Job页面填写相关配置信息来创建。

这三种方式的实现在hudson.model.ItemGroupMixIn中实现,源码片段如下:

    /**
     * Copies an existing {@link TopLevelItem} to a new name.
     *
     * The caller is responsible for calling {@link ItemListener#fireOnCopied(Item, Item)}. This method
     * cannot do that because it doesn't know how to make the newly added item reachable from the parent.
     */
// Copy已有JOB创建新JOB(页面Copy和CLI命令Copy都调用这个方法) @SuppressWarnings({"unchecked"}) public synchronized <T extends TopLevelItem> T copy(T src, String name) throws IOException { acl.checkPermission(Job.CREATE); T result = (T)createProject(src.getDescriptor(),name,false); // copy config Util.copyFile(Items.getConfigFile(src).getFile(),Items.getConfigFile(result).getFile()); // reload from the new config result = (T)Items.load(parent,result.getRootDir()); result.onCopiedFrom(src); add(result);

          //这就是我想要看到的:触发ItemListener监听器
          //只要注册一个扩展点(@extension),并继承ItemListener,就可捕获这个动作

        ItemListener.fireOnCopied(src,result);  
        Hudson.getInstance().rebuildDependencyGraph();

        return result;
    }

// CLI命令远程创建JOB
public synchronized TopLevelItem createProjectFromXML(String name, InputStream xml) throws IOException { acl.checkPermission(Job.CREATE); Jenkins.getInstance().getProjectNamingStrategy().checkName(name); // place it as config.xml File configXml = Items.getConfigFile(getRootDirFor(name)).getFile(); configXml.getParentFile().mkdirs(); try { IOUtils.copy(xml,configXml); // load it TopLevelItem result = (TopLevelItem)Items.load(parent,configXml.getParentFile()); add(result);

             //这就是我想要看到的:触发ItemListener监听器
             //只要注册一个扩展点(@extension),并继承ItemListener,就可捕获这个动作

            ItemListener.fireOnCreated(result); 
Jenkins.getInstance().rebuildDependencyGraph();
return result; } catch (IOException e) { // if anything fails, delete the config file to avoid further confusion Util.deleteRecursive(configXml.getParentFile()); throw e; } }
// 填写配置信息创建JOB
public synchronized TopLevelItem createProject( TopLevelItemDescriptor type, String name, boolean notify ) throws IOException { acl.checkPermission(Job.CREATE); Jenkins.getInstance().getProjectNamingStrategy().checkName(name); if(parent.getItem(name)!=null) throw new IllegalArgumentException("Project of the name "+name+" already exists"); TopLevelItem item; try { item = type.newInstance(parent,name); } catch (Exception e) { throw new IllegalArgumentException(e); } try { callOnCreatedFromScratch(item); } catch (AbstractMethodError e) { // ignore this error. Must be older plugin that doesn't have this method } item.save(); add(item);

          //这就是我想要看到的:触发ItemListener监听器
          //只要注册一个扩展点(@extension),并继承ItemListener,就可捕获这个动作

        if (notify)
            ItemListener.fireOnCreated(item); 
return item; }

2. Update(更新JOB)

Jenkins Job的Update有两种方式:

  • 通过配置页面修改
  • 通过CLI命令远程更新

这三种方式的实现在hudson.model.AbstractItem中实现,源码片段如下:

/**
     * Updates Job by its XML definition.
     */
    public void updateByXml(Source source) throws IOException {
        checkPermission(CONFIGURE);
        XmlFile configXmlFile = getConfigFile();
        AtomicFileWriter out = new AtomicFileWriter(configXmlFile.getFile());
        try {
            try {
                // this allows us to use UTF-8 for storing data,
                // plus it checks any well-formedness issue in the submitted
                // data
                Transformer t = TransformerFactory.newInstance()
                        .newTransformer();
                t.transform(source,
                        new StreamResult(out));
                out.close();
            } catch (TransformerException e) {
                throw new IOException2("Failed to persist configuration.xml", e);
            }

            // try to reflect the changes by reloading
            new XmlFile(Items.XSTREAM, out.getTemporaryFile()).unmarshal(this);
            onLoad(getParent(), getRootDir().getName());
            Jenkins.getInstance().rebuildDependencyGraph();

            // if everything went well, commit this new version
            out.commit();
            //这就是我想要看到的:触发SaveableListener监听器
            //只要注册一个扩展点(@extension),并继承SaveableListener,就可捕获这个动作 SaveableListener.fireOnChange(
this, getConfigFile());
}
finally { out.abort(); // don't leave anything behind } }

3. Rename(重名JOB)

Jenkins Job的Rename只有一种方式:

  • 通过配置页面修改名称->提交->同意改名

也是在hudson.model.AbstractItem中实现,源码片段如下:

/**
     * Renames this item.
     * Not all the Items need to support this operation, but if you decide to do so,
     * you can use this method.
     */
    protected void renameTo(String newName) throws IOException {
        // always synchronize from bigger objects first
        final ItemGroup parent = getParent();
        synchronized (parent) {
            synchronized (this) {
                // sanity check
                if (newName == null)
                    throw new IllegalArgumentException("New name is not given");

                // noop?
                if (this.name.equals(newName))
                    return;

                Item existing = parent.getItem(newName);
                if (existing != null && existing!=this)
                    // the look up is case insensitive, so we need "existing!=this"
                    // to allow people to rename "Foo" to "foo", for example.
                    // see http://www.nabble.com/error-on-renaming-project-tt18061629.html
                    throw new IllegalArgumentException("Job " + newName
                            + " already exists");

                String oldName = this.name;
                File oldRoot = this.getRootDir();

                doSetName(newName);
                File newRoot = this.getRootDir();

                boolean success = false;

                try {// rename data files
                    boolean interrupted = false;
                    boolean renamed = false;

                    // try to rename the job directory.
                    // this may fail on Windows due to some other processes
                    // accessing a file.
                    // so retry few times before we fall back to copy.
                    for (int retry = 0; retry < 5; retry++) {
                        if (oldRoot.renameTo(newRoot)) {
                            renamed = true;
                            break; // succeeded
                        }
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            // process the interruption later
                            interrupted = true;
                        }
                    }

                    if (interrupted)
                        Thread.currentThread().interrupt();

                    if (!renamed) {
                        // failed to rename. it must be that some lengthy
                        // process is going on
                        // to prevent a rename operation. So do a copy. Ideally
                        // we'd like to
                        // later delete the old copy, but we can't reliably do
                        // so, as before the VM
                        // shuts down there might be a new job created under the
                        // old name.
                        Copy cp = new Copy();
                        cp.setProject(new org.apache.tools.ant.Project());
                        cp.setTodir(newRoot);
                        FileSet src = new FileSet();
                        src.setDir(getRootDir());
                        cp.addFileset(src);
                        cp.setOverwrite(true);
                        cp.setPreserveLastModified(true);
                        cp.setFailOnError(false); // keep going even if
                                                    // there's an error
                        cp.execute();

                        // try to delete as much as possible
                        try {
                            Util.deleteRecursive(oldRoot);
                        } catch (IOException e) {
                            // but ignore the error, since we expect that
                            e.printStackTrace();
                        }
                    }

                    success = true;
                } finally {
                    // if failed, back out the rename.
                    if (!success)
                        doSetName(oldName);
                }

                callOnRenamed(newName, parent, oldName);

//这里也会触发监听器
//只要注册一个扩展点(@extension),并继承ItemListener,就可捕获这个动作
for (ItemListener l : ItemListener.all()) l.onRenamed(this, oldName, newName); } } }

4. Delete(删除JOB)

Jenkins Job的Delete有两种方式:

  • 通过页面直接删除
  • 通过CLI命令远程删除

这两种方式的实现在hudson.model.AbstractItem中实现,源码片段如下:

    
   /**
     * Deletes this item.
     */
    @CLIMethod(name="delete-job")
    @RequirePOST
    public void doDoDelete( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException, InterruptedException {
        delete();
        if (rsp != null) // null for CLI
            rsp.sendRedirect2(req.getContextPath()+"/"+getParent().getUrl());
    }

public void delete( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException { try { doDoDelete(req,rsp); } catch (InterruptedException e) { // TODO: allow this in Stapler throw new ServletException(e); } } /** * Deletes this item. * * <p> * Any exception indicates the deletion has failed, but {@link AbortException} would prevent the caller * from showing the stack trace. This */ public synchronized void delete() throws IOException, InterruptedException { checkPermission(DELETE); performDelete(); try { invokeOnDeleted(); } catch (AbstractMethodError e) { // ignore } Jenkins.getInstance().rebuildDependencyGraph(); } /** * A pointless function to work around what appears to be a HotSpot problem. See JENKINS-5756 and bug 6933067 * on BugParade for more details. */ private void invokeOnDeleted() throws IOException { getParent().onDeleted(this); } /** * Does the real job of deleting the item. */ protected void performDelete() throws IOException, InterruptedException { getConfigFile().delete(); Util.deleteRecursive(getRootDir()); }

getParent().onDeleteed(this)方法在jenkins.model.Jenkins定义,如下:

    /**
     * Called in response to {@link Job#doDoDelete(StaplerRequest, StaplerResponse)}
     */
    public void onDeleted(TopLevelItem item) throws IOException {
//同样,这里也触发了监听器。只要注册一个扩展点(@extension),并继承ItemListener,就可捕获这个动作。
for (ItemListener l : ItemListener.all()) l.onDeleted(item); items.remove(item.getName()); for (View v : views) v.onJobRenamed(item, item.getName(), null); save(); }

 另外,这里是使用注解的方式注册CLI的,除了@CLIMethod外,还有@CLIResolver配套使用:

    /**
     * Used for CLI binding.
     */
    @CLIResolver
    public static AbstractItem resolveForCLI(
            @Argument(required=true,metaVar="NAME",usage="Job name") String name) throws CmdLineException {
        AbstractItem item = Jenkins.getInstance().getItemByFullName(name, AbstractItem.class);
        if (item==null)
            throw new CmdLineException(null,Messages.AbstractItem_NoSuchJobExists(name,AbstractProject.findNearest(name).getFullName()));
        return item;
    }

关于使用注解的方式注册CLI命令,请参照:https://wiki.jenkins-ci.org/display/JENKINS/Writing+CLI+commands

原文地址:https://www.cnblogs.com/zhangqingsh/p/3030256.html