Kudu how I loath the

So, now that we have a brain what shall we do?

alt

Every time my wife Megan asks me:

What do you want to do today?

I respond

Take over the world.

So let's use Kudu to take over Azure's world. We know how to use Hubot to respond to simple commands as well as how to take advantage of Hubot's brain. The next logical step is to be able to communicate with our build server. So where do we begin? Well, with a command:

@hubot build deploy [alias]

Once Hubot hears that command he will then do the following:

  1. make sure we can build that environment / alias
  2. if a build is running let the user know
  3. kick off the build
  4. communicate successful queue of build.

Assume deploy went well, then we will also provide a command that will allow a user to query status of a build.

@hubot build status [alias]

The end result would be the last known status of the [alias] specified.

Lets get to it! So building on our poor mans command/strategy pattern lets implement a handle_deploy command.

  function handle_deploy(req, options){
      //look  up in brain if we know about the [alias]
      var serverName = _aliasLookup(options.argumentsString);

      if(!serverName){
        req.send('Could not find: ' + options.argumentsString);
        return;
      }
      req.send('Checking for if we can deploy ' + serverName);
      //make sure build is not happening.
      api.deployment.get('latest', latestResult);

      function latestResult(error, result){
        if(error){
          req.send("could not find ", serverName);
        }else{
          if(result.complete){
            //no build is running
            req.send('Start deployment: ' + serverName);
            api.deployment.redeploy('', { clean: options.action === 'force' } ,deploySuccess);
          }else{
            req.send("can not start build one is running on ", serverName);
          }
        }
      }

      function deploySuccess(err, dResult){
        if(err){
          req.send('failed to deploy', err);
        }else{
          req.send("Successfully queue deployment");
        }
      }

Hello deep end. That's literally the entire block of code. I found a node library called kudu-api. I ended up forking it because i wanted to use a little stronger password management than plain text. But at the end of the day it did 90% of what I needed. If I could come up with a base64 encoded username:password I could access Kudu. As mentioned in the previous post you have two options with connecting to Kudu. use your deployment credentials. These are azure wide. So if the user you setup has access to all the projects, Hubot will be able to deploy all projects. Option B is to use the site credentials, while more painful at least if someone hacks Hubot they can only deploy the sites you have setup.

As for the code...

Step 1: make sure we know about this alias. Look up into brain a mapping for what was passed in.

 var serverName = _aliasLookup(options.argumentsString);

Step 2: Make sure there is not a deployment running

api.deployment.get('latest', latestResult);  

Step 3: Optional. If alias is unknown or a build is running return a message to the user that executed the command.

//unknown
req.send('Could not find: ' + options.argumentsString);  
 ...
//  req.send("can not start build on... ", serverName);

Step 4: Do the build. If a force action was specified do a clean and then build:

api.deployment  
   .redeploy('', { clean: options.action === 'force' } ,deploySuccess);

Volia deployment queued.. Next up lets query for status. Well that kudu-api library makes things painfuly simple. So we just define a command handle_status and lets use the api.deployment.get command:

function handle_status(req, options){

    var serverName = _aliasLookup(options.argumentsString);
    if(!serverName){
       req.send("unknown server:", serverName);
      return;
    }

    req.send('Checking status of ' + serverName);
    api.deployment.get('latest', latestResult);
    function latestResult(err, result){
      if(err){
        req.send("could not find ", serverName);
      }else{
        //return a pretty result of whats going on
        req.send(_formatBuildMessage(result));
      }
    }
  }

  function _formatBuildMessage(deployment){
    var msg = '';
    if(deployment.complete){
      var success = 'failed ';
      if(deployment.status === 4){
        success = ' succeeded ';
      }
      msg+='Build ' + success + new moment(deployment.start_time).fromNow() +  ' by ' + deployment.author + '. ';
    }else{
      msg +='Started ' + new moment(deployment.start_time).fromNow() + ' and is ' +  deployment.status_text + '. ';
    }
    msg += '\n\tCommit: ' + deployment.message ;
    console.log(deployment);

    return msg;
  }

Let's walk through that.

Step 1: Make sure we know about this environment (_aliasLookup).

Step 2: api.deployment.get. Since we know this alias lets query the kudu api for the latest deployment.

Step 3: If we didn't know the env or there was an error retrieving latest build return message to user:

req.send("could not find ", serverName);

Step 4: Success. We got info back about the last build lets just format it and return it via the _formatBuildMessage function. Nothing special here, I just decided to return the data in a very simple format. The only weirdness was i found that if status==4 that meant success! Simple right?


Man looking back I learned so much through this process. I was shocked to learn that Kudu was not part of the management interfaces. I also quickly learned that my deploy:force option was useless because the kudu console could not actually remove all files because of path limits. At the end of the day the hack was useful because we could query status and start builds, but since force didn't work I was still stuck going in and clearing out bower_components and node_modules.

It was definitely a learning process, and at the end of the day this experience thought me about Kudu but also about the Azure management API's. About a week later I ended up using Azure SDK for Node to automate setting up new environments for one of our products. That is probably a blog for a different day, because the documentation was absolutely awful.

Well, I hope you left inspired to use Hubot. If so consider this series successful. If not let me know why and we can go from there!

Cheers.

Nick Capito

Professional developer in all things generic. Good at AngularJS, CSS3 , Sass, .NET, Azure, Node. Love dogs. In an alternate live I would have been an American Cesar Millan.

Richmond, VA http://nixo.us