How to Add Custom Build Step to behave exactly same as Execute Windows Batch Command.

134 views
Skip to first unread message

Kul Bhushan Srivastava

unread,
Nov 14, 2016, 3:07:49 AM11/14/16
to Jenkins Developers

Kul Bhushan Srivastava

unread,
Nov 16, 2016, 2:41:44 AM11/16/16
to Jenkins Developers
If anyone can guide how the windows bactch command execution are handled from backend JAVA API. Then I may create one of my own.

On Monday, 14 November 2016 13:37:49 UTC+5:30, Kul Bhushan Srivastava wrote:
 

Robert Sandell

unread,
Nov 16, 2016, 6:45:12 AM11/16/16
to jenkin...@googlegroups.com

On Wed, Nov 16, 2016 at 8:41 AM, Kul Bhushan Srivastava <cool...@gmail.com> wrote:
If anyone can guide how the windows bactch command execution are handled from backend JAVA API. Then I may create one of my own.

On Monday, 14 November 2016 13:37:49 UTC+5:30, Kul Bhushan Srivastava wrote:
 

--
You received this message because you are subscribed to the Google Groups "Jenkins Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jenkinsci-dev+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/jenkinsci-dev/76d6ddbf-14a2-405d-8197-f2dd549e8248%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Robert Sandell
Software Engineer
CloudBees Inc.

Kul Bhushan Srivastava

unread,
Nov 16, 2016, 9:59:42 AM11/16/16
to Jenkins Developers
Thanks Robert.

I am facing maven compilation issue. Saying @DataBoundConstructor cannot be used inside abstract class.

Please suggest.

My Java File:

import org.kohsuke.stapler.DataBoundConstructor;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.tasks.Messages;
import hudson.Proc;
import hudson.Util;
import hudson.EnvVars;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.Node;
import hudson.model.Result;
import hudson.model.TaskListener;
import hudson.remoting.ChannelClosedException;
import hudson.model.AbstractProject;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.tasks.CommandInterpreter;
import java.io.IOException;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nonnull;

public abstract class BuildStepCreator extends Builder {
   
    private String text1, text2, text3;
   
    public String getText1(){
        return text1;
    }
    public String getText2(){
        return text2;
    }
    public String getText3(){
        return text3;
    }
   

    @DataBoundConstructor
    public BuildStepCreator(OptionalTextBlock1 enableText1, OptionalTextBlock2 enableText2, OptionalTextBlock3 enableText3) {
        this.text1 = (enableText1 != null) ? enableText1.text1 : null;
        this.text2 = (enableText2 != null) ? enableText2.text2 : null;
        this.text3 = (enableText3 != null) ? enableText3.text3 : null;
    }

    public static class OptionalTextBlock1 {
        private String text1;
        @DataBoundConstructor
        public OptionalTextBlock1(String text1) {
            this.text1 = text1;
        }
    }
    public static class OptionalTextBlock2 {
        private String text2;
        @DataBoundConstructor
        public OptionalTextBlock2(String text2) {
            this.text2 = text2;
        }
    }
    public static class OptionalTextBlock3 {
        private String text3;
        @DataBoundConstructor
        public OptionalTextBlock3(String text3) {
            this.text3 = text3;
        }
    }

    @Extension
    public static final class DescriptorImpl extends BuildStepDescriptor<Builder>{
       
        /*public FormValidation doCheckTask(@QueryParameter String value){
            try{
                if(value.equalsIgnoreCase(null) || value.equalsIgnoreCase("")){
                    return FormValidation.error("Invalid Details.");
                }
            }catch(Exception e){
                return FormValidation.error("Error validating the form details.");
            }
            return FormValidation.ok();
        }
       
        public FormValidation doCheckGoal(@QueryParameter String value){
            try{
                Integer.parseInt(value);
            }catch(Exception e){
                return FormValidation.error("Please provide integer in the Goal.");
            }
            return FormValidation.ok();
        }*/

        @Override
        public boolean isApplicable(Class<? extends AbstractProject> jobType) {
            // TODO Auto-generated method stub
            return true;
        }

        @Override
        public String getDisplayName() {
            // TODO Auto-generated method stub
            return "Job Orchestration";
        }
    }
   
    /*@Override
    public boolean perform(hudson.model.AbstractBuild build, hudson.Launcher launcher, hudson.model.BuildListener listener) {
        listener.getLogger().println("OptionalBlockSampleBuilder " + text1);
        return true;
    }*/
   
    @Override
    public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException {
        return perform(build,launcher,(TaskListener)listener);
    }
   
    /**
     * Determines whether a non-zero exit code from the process should change the build
     * status to {@link Result#UNSTABLE} instead of default {@link Result#FAILURE}.
     *
     * Changing to {@link Result#UNSTABLE} does not abort the build, next steps are continued.
     *
     */
    protected boolean isErrorlevelForUnstableBuild(int exitCode) {
        return false;
    }
   
    public boolean perform(AbstractBuild<?,?> build, Launcher launcher, TaskListener listener) throws InterruptedException{
        FilePath ws = build.getWorkspace();
        if (ws == null) {
            Node node = build.getBuiltOn();
            if (node == null) {
                throw new NullPointerException("no such build node: " + build.getBuiltOnStr());
            }
            throw new NullPointerException("no workspace from node " + node + " which is computer " + node.toComputer() + " and has channel " + node.getChannel());
        }
        FilePath script=null;
        int r = -1;
        try {
            try {
                script = createScriptFile(ws);
            } catch (IOException e) {
                Util.displayIOException(e,listener);
                e.printStackTrace(listener.fatalError(Messages.CommandInterpreter_UnableToProduceScript()));
                return false;
            }

            try {
                EnvVars envVars = build.getEnvironment(listener);
                // on Windows environment variables are converted to all upper case,
                // but no such conversions are done on Unix, so to make this cross-platform,
                // convert variables to all upper cases.
                for(Map.Entry<String,String> e : build.getBuildVariables().entrySet())
                    envVars.put(e.getKey(),e.getValue());

                r = join(launcher.launch().cmds(buildCommandLine(script)).envs(envVars).stdout(listener).pwd(ws).start());

                if(isErrorlevelForUnstableBuild(r)) {
                    build.setResult(Result.UNSTABLE);
                    r = 0;
                }
            } catch (IOException e) {
                Util.displayIOException(e, listener);
                e.printStackTrace(listener.fatalError(Messages.CommandInterpreter_CommandFailed()));
            }
            return r==0;
        } finally {
            try {
                if(script!=null)
                    script.delete();
            } catch (IOException e) {
                if (r==-1 && e.getCause() instanceof ChannelClosedException) {
                    // JENKINS-5073
                    // r==-1 only when the execution of the command resulted in IOException,
                    // and we've already reported that error. A common error there is channel
                    // losing a connection, and in that case we don't want to confuse users
                    // by reporting the 2nd problem. Technically the 1st exception may not be
                    // a channel closed error, but that's rare enough, and JENKINS-5073 is common enough
                    // that this suppressing of the error would be justified
                    LOGGER.log(Level.FINE, "Script deletion failed", e);
                } else {
                    Util.displayIOException(e,listener);
                    e.printStackTrace( listener.fatalError(Messages.CommandInterpreter_UnableToDelete(script)) );
                }
            } catch (Exception e) {
                e.printStackTrace( listener.fatalError(Messages.CommandInterpreter_UnableToDelete(script)) );
            }
        }
    }
   
    /**
     * Reports the exit code from the process.
     *
     * This allows subtypes to treat the exit code differently (for example by treating non-zero exit code
     * as if it's zero, or to set the status to {@link Result#UNSTABLE}). Any non-zero exit code will cause
     * the build step to fail. Use {@link #isErrorlevelForUnstableBuild(int exitCode)} to redefine the default
     * behaviour.
     *
     * @since 1.549
     */
    protected int join(Proc p) throws IOException, InterruptedException {
        return p.join();
    }

    /**
     * Creates a script file in a temporary name in the specified directory.
     */
    public FilePath createScriptFile(@Nonnull FilePath dir) throws IOException, InterruptedException {
        return dir.createTextTempFile("hudson", getFileExtension(), getContents(), false);
    }

    public abstract String[] buildCommandLine(FilePath script);

    protected abstract String getContents();

    protected abstract String getFileExtension();

    private static final Logger LOGGER = Logger.getLogger(CommandInterpreter.class.getName());
   
}


On Wednesday, 16 November 2016 17:15:12 UTC+5:30, Robert Sandell wrote:
On Wed, Nov 16, 2016 at 8:41 AM, Kul Bhushan Srivastava <cool...@gmail.com> wrote:
If anyone can guide how the windows bactch command execution are handled from backend JAVA API. Then I may create one of my own.

On Monday, 14 November 2016 13:37:49 UTC+5:30, Kul Bhushan Srivastava wrote:
 

--
You received this message because you are subscribed to the Google Groups "Jenkins Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to jenkinsci-de...@googlegroups.com.

Robert Sandell

unread,
Nov 16, 2016, 12:30:36 PM11/16/16
to jenkin...@googlegroups.com
Abstract classes cannot be instanciated, so you can't annotate the constructor with DataBoundConstructor since the class can't be directly instanciated but will need to be subclassed.

/B

To unsubscribe from this group and stop receiving emails from it, send an email to jenkinsci-dev+unsubscribe@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/jenkinsci-dev/39ff5e49-dcf3-4098-a505-de706522682e%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.
Message has been deleted

Kul Bhushan Srivastava

unread,
Nov 17, 2016, 3:24:00 AM11/17/16
to Jenkins Developers
Agree with your point.

I have change the code accordingly. Compiles properly through Maven. But now Jenkins at Runtime throwing exception when saving the job configuration, saying -

Caused by: java.lang.IllegalArgumentException: Failed to instantiate class com.oracle.siebel.jenkins.ABC from {"enableText1":{"text1":"hello"},"enableText2":{"text2":"kul"},"enableText3":{"text3":"bhushan"},"stapler-class":"com.oracle.siebel.jenkins.ABC","$class":"com.oracle.siebel.jenkins.ABC"}
    at org.kohsuke.stapler.RequestImpl$TypePair.convertJSON(RequestImpl.java:674)
    at org.kohsuke.stapler.RequestImpl.bindJSON(RequestImpl.java:476)
    at org.kohsuke.stapler.RequestImpl.bindJSON(RequestImpl.java:472)
    at hudson.model.Descriptor.newInstance(Descriptor.java:588)
    ... 81 more
Caused by: org.kohsuke.stapler.NoStaplerConstructorException: There's no @DataBoundConstructor on any constructor of class com.oracle.siebel.jenkins.ABC
    at org.kohsuke.stapler.ClassDescriptor.loadConstructorParamNames(ClassDescriptor.java:177)
    at org.kohsuke.stapler.RequestImpl.instantiate(RequestImpl.java:756)
    at org.kohsuke.stapler.RequestImpl.access$200(RequestImpl.java:81)
    at org.kohsuke.stapler.RequestImpl$TypePair.convertJSON(RequestImpl.java:672)
    ... 84 more

New Java File

import org.kohsuke.stapler.DataBoundConstructor;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.tasks.Messages;
import hudson.Proc;
import hudson.Util;
import hudson.EnvVars;
import hudson.model.AbstractBuild;
import hudson.model.AbstractDescribableImpl;

import hudson.model.BuildListener;
import hudson.model.Node;
import hudson.model.Result;
import hudson.model.TaskListener;
import hudson.remoting.ChannelClosedException;
import hudson.model.AbstractProject;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.tasks.CommandInterpreter;
import java.io.IOException;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nonnull;

public class BuildStepCreator extends Builder{
abstract class ABC extends BuildStepCreator{
   
    public ABC(OptionalTextBlock1 enableText1, OptionalTextBlock2 enableText2, OptionalTextBlock3 enableText3) {
        super(enableText1, enableText2, enableText3);
        // TODO Auto-generated constructor stub

    }

    public abstract String[] buildCommandLine(FilePath script);

    protected abstract String getContents();

    protected abstract String getFileExtension();
   
    @Extension
    public static final class DescriptorImpl extends BuildStepDescriptor<Builder>{
       
        @Override
        public boolean isApplicable(Class<? extends AbstractProject> jobType) {
            // TODO Auto-generated method stub
            return true;
        }

        @Override
        public String getDisplayName() {
            // TODO Auto-generated method stub
            return "Job Orchestration";
        }
    }
   
   
   
                    build.setResult(Result.FAILURE);

Kul Bhushan Srivastava

unread,
Nov 18, 2016, 12:39:26 PM11/18/16
to Jenkins Developers
Thanks Guys for helping. I am able to crack it now. After creating below two files it works.

First Java File

import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import com.oracle.siebel.jenkins.BuildStepCommandInterpreter.OptionalTextBlock1;

import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.tasks.Messages;
import hudson.Proc;
import hudson.Util;
import hudson.EnvVars;
import hudson.model.AbstractBuild;
import hudson.model.AbstractDescribableImpl;
import hudson.model.BuildListener;
import hudson.model.Node;
import hudson.model.Result;
import hudson.model.TaskListener;
import hudson.remoting.ChannelClosedException;
import hudson.model.AbstractProject;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.tasks.CommandInterpreter;
import java.io.IOException;
import java.io.ObjectStreamException;

import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;

public class BuildStepCreator extends BuildStepCommandInterpreter {
   
    @DataBoundConstructor
    public BuildStepCreator(OptionalTextBlock1 enableText1) {
        super(enableText1);
    }
   
    private Integer unstableReturn;

    public String[] buildCommandLine(FilePath script) {
        return new String[] {"cmd","/c","call",script.getRemote()};
    }

    protected String getContents() {
        return text1;
    }

    protected String getFileExtension() {
        return ".bat";
    }
   
    @CheckForNull
    public final Integer getUnstableReturn() {
        return new Integer(0).equals(unstableReturn) ? null : unstableReturn;
    }

    @DataBoundSetter
    public void setUnstableReturn(Integer unstableReturn) {
        this.unstableReturn = unstableReturn;
    }

    @Override
    protected boolean isErrorlevelForUnstableBuild(int exitCode) {
        return this.unstableReturn != null && exitCode != 0 && this.unstableReturn.equals(exitCode);
    }

    private Object readResolve() throws ObjectStreamException {
        return new BuildStepCreator(enableText1);

    }
   
    @Extension
    public static final class DescriptorImpl extends BuildStepDescriptor<Builder>{
        /*public FormValidation doCheckTask(@QueryParameter String value){
            try{
                if(value.equalsIgnoreCase(null) || value.equalsIgnoreCase("")){
                    return FormValidation.error("Invalid Details.");
                }
            }catch(Exception e){
                return FormValidation.error("Error validating the form details.");
            }
            return FormValidation.ok();
        }
       
        public FormValidation doCheckGoal(@QueryParameter String value){
            try{
                Integer.parseInt(value);
            }catch(Exception e){
                return FormValidation.error("Please provide integer in the Goal.");
            }
            return FormValidation.ok();
        }*/

        @Override
        public boolean isApplicable(Class<? extends AbstractProject> jobType) {
            // TODO Auto-generated method stub
            return true;
        }

        @Override
        public String getDisplayName() {
            // TODO Auto-generated method stub
            return "Job Orchestration";
        }
    }
}

Second Java File


import java.io.IOException;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nonnull;
import org.kohsuke.stapler.DataBoundConstructor;
import hudson.EnvVars;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Messages;
import hudson.Proc;
import hudson.Util;

import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.Node;
import hudson.model.Result;
import hudson.model.TaskListener;
import hudson.remoting.ChannelClosedException;
import hudson.tasks.Builder;
import hudson.tasks.CommandInterpreter;

public abstract class BuildStepCommandInterpreter extends Builder {
    protected final String text1;
    //protected final String text2;
    //protected final String text3;

   
    public String getText1(){
        return text1;
    }
   
    /*public String getText2(){

        return text2;
    }
   
    public String getText3(){
        return text3;
    }*/
   
    protected OptionalTextBlock1 enableText1;
    //protected OptionalTextBlock2 enableText2;
    //protected OptionalTextBlock3 enableText3;
   
    /*public BuildStepCommandInterpreter(OptionalTextBlock1 enableText1, OptionalTextBlock2 enableText2, OptionalTextBlock3 enableText3) {

        this.text1 = (enableText1 != null) ? enableText1.text1 : null;
        this.text2 = (enableText2 != null) ? enableText2.text2 : null;
        this.text3 = (enableText3 != null) ? enableText3.text3 : null;
    }*/
    public BuildStepCommandInterpreter(OptionalTextBlock1 enableText1) {

        this.text1 = (enableText1 != null) ? enableText1.text1 : null;
    }
    public static class OptionalTextBlock1 {
        protected String text1;

        @DataBoundConstructor
        public OptionalTextBlock1(String text1) {
            this.text1 = text1;
        }
    }
   
    /*public static class OptionalTextBlock2 {

        private String text2;
        @DataBoundConstructor
        public OptionalTextBlock2(String text2) {
            this.text2 = text2;
        }
    }
   
    public static class OptionalTextBlock3 {
        private String text3;
        @DataBoundConstructor
        public OptionalTextBlock3(String text3) {
            this.text3 = text3;
        }
    }*/
   
    @Override
    public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException {
        return perform(build,launcher,(TaskListener)listener);
    }

    /**
     * Determines whether a non-zero exit code from the process should change the build
     * status to {@link Result#UNSTABLE} instead of default {@link Result#FAILURE}.
     *
     * Changing to {@link Result#UNSTABLE} does not abort the build, next steps are continued.
     *
     * @since 2.26

     */
    protected boolean isErrorlevelForUnstableBuild(int exitCode) {
        return false;
    }

    public boolean perform(AbstractBuild<?,?> build, Launcher launcher, TaskListener listener) throws InterruptedException {
        FilePath ws = build.getWorkspace();
        if (ws == null) {
            Node node = build.getBuiltOn();
            if (node == null) {
                throw new NullPointerException("no such build node: " + build.getBuiltOnStr());
            }
            throw new NullPointerException("no workspace from node " + node + " which is computer " + node.toComputer() + " and has channel " + node.getChannel());
        }
        FilePath script=null;
        int r = -1;
        try {
            try {
                script = createScriptFile(ws);
            } catch (IOException e) {
                Util.displayIOException(e,listener);
                e.printStackTrace(listener.fatalError("Error in createScriptFile step."));

                return false;
            }

            try {
                EnvVars envVars = build.getEnvironment(listener);
                // on Windows environment variables are converted to all upper case,
                // but no such conversions are done on Unix, so to make this cross-platform,
                // convert variables to all upper cases.
                for(Map.Entry<String,String> e : build.getBuildVariables().entrySet())
                    envVars.put(e.getKey(),e.getValue());

                r = join(launcher.launch().cmds(buildCommandLine(script)).envs(envVars).stdout(listener).pwd(ws).start());

                if(isErrorlevelForUnstableBuild(r)) {
                    build.setResult(Result.FAILURE);
                    r = 0;
                }
            } catch (IOException e) {
                Util.displayIOException(e, listener);
                e.printStackTrace(listener.fatalError(""));

            }
            return r==0;
        } finally {
            try {
                if(script!=null)
                    script.delete();
            } catch (IOException e) {
                if (r==-1 && e.getCause() instanceof ChannelClosedException) {
                    // JENKINS-5073
                    // r==-1 only when the execution of the command resulted in IOException,
                    // and we've already reported that error. A common error there is channel
                    // losing a connection, and in that case we don't want to confuse users
                    // by reporting the 2nd problem. Technically the 1st exception may not be
                    // a channel closed error, but that's rare enough, and JENKINS-5073 is common enough
                    // that this suppressing of the error would be justified
                    LOGGER.log(Level.FINE, "Script deletion failed or Channel Closed Exception.", e);
                } else {
                    Util.displayIOException(e,listener);
                    e.printStackTrace( listener.fatalError("") );
                }
            } catch (Exception e) {
                e.printStackTrace( listener.fatalError("Unable to Delete") );

            }
        }
    }

    /**
     * Reports the exit code from the process.
     *
     * This allows subtypes to treat the exit code differently (for example by treating non-zero exit code
     * as if it's zero, or to set the status to {@link Result#UNSTABLE}). Any non-zero exit code will cause
     * the build step to fail. Use {@link #isErrorlevelForUnstableBuild(int exitCode)} to redefine the default
     * behaviour.
     *
     * @since 1.549
     */
    protected int join(Proc p) throws IOException, InterruptedException {
        return p.join();
    }

    /**
     * Creates a script file in a temporary name in the specified directory.
     */
    public FilePath createScriptFile(@Nonnull FilePath dir) throws IOException, InterruptedException {
        return dir.createTextTempFile("jenkins", getFileExtension(), getContents(), false);
Reply all
Reply to author
Forward
0 new messages