fredag 1 november 2013

Allow developers to always checkout code that compiles and passes tests with Git(hub) and Jenkins

Introduction

A new branch can be added into GitHub called latest.
Only an automated user (used by jenkins) is pushing into this branch(from Jenkins). This means that, at least,
Jenkins(and the build scripts/tasks) is verifying for any compilation errors, and other potential kpi's (like passing unit tests, test coverage etc).

Without meeting the specified requirements, code will not be pushed to Github's latest branch.

The interesting part for the developers is that makes pulling from the latest branch much safer than from the master branch.

Development machine

The advised workflow as a developer is to:
  • Push to master
  • Pull from latest

Push to master

Nothing new here, just plain old  
git push

Pull from latest

Git needs to be configured to pulling from the latest branch instead of the default one (master). IEither edit the .git/config file by hand, or run the following commands in the root of the local git repository:
git config branch.master.remote origin
git config branch.master.merge refs/heads/latest

Then do
git pull

Git repo

Create *-latest branch associated with a branch that is updated via Jenkins and permits developers to pull into their own local repos the latest (working) changes.
  1. Get on the branch that you want to create the continuous integration on (using foo as example branch):
    git checkout foo
  2. Get the latest commits from that branch:
    git pull origin foo
  3. Create the associated *-latest branch based on the current branch:
    git checkout -b Release1-latest
  4. Push to the main repo:
    git push origin Release1-latest

Build Server (Jenkins)

Configure jenkins to pull from the master branch - e.g:
And then configure the git publisher plugin to push to latest:

Wrapping up

Whenever you do a git pull, you're actually pulling from latest instead of the default master branch. If you push code to GitHub and the Jenkins build fails, i.e. can't compile or unit test fails or somtehing else, other developers can't submit until the failing push is fixed. This is due to commit version conficts since latest is used as the base and master has a later commit version. Latest and master will have the same commit version after a successful Jenkins build.

References

This page has more details on the process of changing the branch from where to pull from: http://stackoverflow.com/questions/658885/how-do-you-get-git-to-always-pull-from-a-specific-branch

Thanks to

Peter Jansson & Raul Brito for teaching me this, and to Ulrik Sandberg for asking annoyingly necessary questions for me to have to document it.

torsdag 17 oktober 2013

Stamp a pdf with it's file name.

Need to stamp  a pdf file with it's filename? Here you go:
Usage:
pdfstamper
usage: pdfstamper
 -d,--directory <input folder>   All pdf's in this folder will be
                                 processed
 -i,--input <input file>         This file will be processed
 -x,--xoffset <x offset>         The horizontal offset from the rightmost
                                 part of the page
 -y,--yoffset <y offset>         The vertical offset from the top of the
                                 page

Eclipse project:
Download File
excutable jar:
Download File
runnable unix cmd:
Download File

Source:
import java.io.File;
import java.io.FileFilter;
import java.util.List;

import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;

public class PDFStamper {

    /**
     * @param args the command line arguments
     */
    public static final FileFilter pdfFileFilter = new FileFilter() {

        public boolean accept(File file) {
            return file.isFile() && file.getName().toLowerCase().endsWith(".pdf");
        }
    };

    public static void closeQuietly(PDDocument doc) {
        if (doc != null) {
            try {
                doc.close();
            } catch (Exception exception) {
                //do something here if you wish like logging 
            }
        }
    }

    public static void checkPages(File[] sourcePdfFiles, String prefix, int offsetX, int offsetY) {

        for (File sourcePdfFile : sourcePdfFiles) {
            stampFile(sourcePdfFile.getName(), prefix, offsetX, offsetY, sourcePdfFile);
        }
    }

  private static void stampFile(String textToInsert, String prefix,
   int offsetX, int offsetY, File sourcePdfFile) {
  PDFont font = PDType1Font.HELVETICA_BOLD;
  float fontSize = 12.0f;
  PDDocument pdoc = null;
  try {

       pdoc = PDDocument.load(sourcePdfFile);
      List allPages = pdoc.getDocumentCatalog().getAllPages();
      for(PDPage page : allPages)
      {
       PDRectangle pageSize = page.findMediaBox();
          float stringWidth = font.getStringWidth(textToInsert);
          float centeredPosition = (pageSize.getWidth() - (stringWidth * fontSize) / 1000f) / 2f;
          float right = (pageSize.getWidth() - (stringWidth * fontSize) / 1000f-offsetX);
          float top = (pageSize.getHeight()-offsetY);

           PDPageContentStream contentStream = new PDPageContentStream(pdoc, page, true, true);
          contentStream.beginText();
          contentStream.setFont(font, fontSize);
          contentStream.moveTextPositionByAmount(right, top);
          contentStream.drawString(textToInsert);
          contentStream.endText();
          contentStream.close();
      }
      
      File resultFile = new File(sourcePdfFile.getParentFile(), prefix + sourcePdfFile.getName());
      pdoc.save(resultFile.getAbsolutePath());


   } catch (Exception e) {
      System.err.println("An exception occured in parsing the PDF Document." + e.getMessage());
  } finally {
      closeQuietly(pdoc);
  }
 }

    public static void main(String[] args) {
     int offsetX=20;
     int offsetY=20;
      String modifiedFilePrefix = "modified-";
     Options options = new Options();
     Option inputFolder = OptionBuilder.hasArgs(1).withArgName("input folder")
          .withDescription("All pdf's in this folder will be processed").isRequired(false)
          .withLongOpt("directory").create("d");
     Option inputFile = OptionBuilder.hasArgs(1).withArgName("input file")
          .withDescription("This file will be processed").isRequired(false)
          .withLongOpt("input").create("i");
     Option offsetXOpt = OptionBuilder.hasArgs(1).withArgName("x offset")
          .withDescription("The horizontal offset from the rightmost part of the page").isRequired(false)
          .withLongOpt("xoffset").create("x");  
     Option offsetYOpt = OptionBuilder.hasArgs(1).withArgName("y offset")
          .withDescription("The vertical offset from the top of the page").isRequired(false)
          .withLongOpt("yoffset").create("y");  
   
     options.addOption(inputFolder);
     options.addOption(inputFile);
     options.addOption(offsetXOpt);
     options.addOption(offsetYOpt);
     
     CommandLineParser parser = new BasicParser();
       try {
   CommandLine line = parser.parse(options, args);
   if(line.hasOption("offsetX"))
   {
    offsetX=Integer.parseInt(line.getOptionValue("offsetX"));
   }
   if(line.hasOption("offsetY"))
   {
    offsetY=Integer.parseInt(line.getOptionValue("offsetY"));

    }
   if(line.hasOption("input"))
   {
    File pdfFile = new File(line.getOptionValue("input"));
          stampFile(pdfFile.getName(), modifiedFilePrefix, offsetX, offsetY, pdfFile);
    
   }
   if(line.hasOption("directory"))
   {
    File pdfFilesFolder = new File(line.getOptionValue("directory"));
          File[] pdfFiles = pdfFilesFolder.listFiles(pdfFileFilter);
          checkPages(pdfFiles,modifiedFilePrefix,offsetX,offsetY);
   }
   if(line.getOptions().length==0)
   {
    HelpFormatter formatter = new HelpFormatter();
    formatter.printHelp( "pdfstamper", options );
   }
  } catch (ParseException e) {
   HelpFormatter formatter = new HelpFormatter();
   formatter.printHelp( "pdfstamper", options );
  
  } // args are the arguments passed to the  the application via the main method

     
        
        //when a file is processed, the result will be saved in a new file having the location of the source file 
        //and the same name of source file prefixed with this
       
        
    }
}


onsdag 3 juli 2013

Consideration when using post/put and spnego/kerberos


From RFC(http://tools.ietf.org/html/rfc4559):

 When using the SPNEGO HTTP authentication facility with client-
   supplied data such as PUT and POST, the authentication should be
   complete between the client and server before sending the user data.
   The return status from the gss_init_security_context will indicate
   that the security context is complete.  At this point, the data can
   be sent to the server.

What does this mean?
This actually means that if you have a loadbalancer/reverse proxy/similar(tmg/cisco-ace/iis/apache/nginx)) that supports
spnego/kerberos running towards a middleware system that doesn't(but replies ok whenever an authentication header is passed - or not) - post requests won't work.

The reverse proxy will send an empty post request with an headers, expecting an authentication negotiation from the server to properly authenticate before sending a new post with the actual data.

Seen in action With Hybris and Microsoft forefront TMG.

söndag 5 maj 2013

Create policy for S3 bucket to prevent accidental deletion.


Create policy to not delete bucket for everyone(prevent accidental deletion):
{
  "Version": "2008-10-17",
  "Id": "a unique policy id",
  "Statement": [
   {
    "Sid": "a stmnt ID",
    "Effect": "Deny",
    "Principal": {"AWS": "*"},
    "Action": ["s3:DeleteBucket","s3:DeleteBucketWebsite"],
    "Resource": "arn:aws:s3:::<bucket-name>"
   }]
}
}

Rationale: 
In order to prevent accidental deletion of a bucket in dev, test or prod environments when
developing scripts etc - all buckets could have this policy set by default, so a deletion requires two actions, change policy, then delete lowering the risk of accidental error.