Saturday, September 7, 2013

Running Go Programs In IronWorker

Introduction
Iron.io has a product called IronWorker which provides a task oriented Linux container that you can run your programs inside. If you are not sure what I mean, think of this as having a temporary Linux virtual machine instantly available for your personal but short term use. IronWorker allows you to load your binaries, code files, support files, shells scripts and just about anything else you may need to run your program in the container. You specify a single task to execute, such as running a shell script or a binary and IronWorker will perform that task when requested. Once the task is complete, IronWorker will tear down the container as if it never existed.

If you are developing in Windows or on the Mac and plan to load and run pre-built binaries in IronWorker, they must be built for Linux. If that could be a problem don't despair, you have options.

You can create an Ubuntu VM so you can build the Linux binaries you need. This is what I do. I don't develop in my Ubuntu VM, I use it as a testing and staging area. Your second option is to build your program inside of the IronWorker container and then execute it. You have a lot of flexibility to do what you need with IronWorker.

To Install or Not to Install Ubuntu
I highly recommend that you buy VMWare Fusion or Parallels and create an Ubuntu VM. Most of the cloud based environments you may choose to use for your testing and production environments will be running Linux. It helps to stage and test things before uploading code to these environments. I use VMWare Fusion and I love the product. VMWare Fusion will cost around $60 USD and Ubuntu is FREE. I run Ubuntu 12.04 LTS but version 13.04 is now available.

http://www.vmware.com/products/fusion/
http://www.ubuntu.com/download/desktop

In this post I will be using my Mac for everything. So if you are running Windows you should be able to follow along. Once you see how to build your applications inside the IronWorker container, you will know how to load and run Linux binaries. For those who are interested in setting up or using Ubuntu, read the next section. If you want to continue in your Windows or Mac environment, go to the Installing Ruby section.

Setting Up Your Terminal In Ubuntu
If you decided to go ahead and install an Ubuntu VM or you already have one, awesome. Here are a few things you will want to do so you can use IronWorker.


The first Icon in the Launcher bar is the Dash Home Icon. Select this and perform a search for the Terminal application.

You will see several terminal applications show up. Select the Terminal program, which will launch a Terminal Session. Now back in Launcher you will see an icon for the Terminal session you started. Right Click on the Terminal Icon and select Lock To Launcher.

One last step, we need to change a configuration option for Terminal:


Make sure the Terminal session is the active program and move your mouse to the top of the screen. The menu for Terminal will appear. Under Edit choose Profile Preferences. Then select the Title and Command tab and check the Run command as a login shell. We need this for our Ruby programs to work properly in our Terminal sessions.

Installing Ruby
Before we can start using IronWorker we need the latest version of Ruby installed. IronWorker provides a Ruby based command line tool that allows us to upload IronWorker tasks for the different projects we create.

Window Users

In Windows use this website to install the latest version of Ruby:

http://rubyinstaller.org/

Once you are done skip to the next section called Installing IronWorker.

Mac and Linux Users

On the Mac and Linux I have found the best way to install and manage Ruby is with the Ruby Version Manager (RVM):

https://rvm.io/rvm/install

Perform these steps on both your Mac and Linux operating systems. Open a Terminal session and run this command:

\curl -L https://get.rvm.io | bash -s stable --ruby --autolibs=enable --auto-dotfiles

Use the backslash when running the command. This prevents misbehaving if you have aliased it with configuration in your ~/.curlrc file.

That command should download the latest version of Ruby and make it the default version. Before moving on, close your Terminal session and start a new one. Check that everything is good by running this command:

run rvm list

This should be the output:

rvm rubies

=* ruby-2.0.0-p247 [ x86_64 ]

# => - current
# =* - current && default
# * - default

There may be a newer version of Ruby by the time you are reading this. But as long as the version is greater than or equal to 2.0.0 and has the =* operator in front, you will be all set.

If you are not sure if you have the correct default version set and want to make absolutely sure, run this command:

rvm use ruby-2.0.0-p247 --default

Just make sure you specify the version correctly.

Just for your information, the rvm tool and the different versions of Ruby you install get placed in a folder called .rvm inside your $HOME folder. It will be a hidden folder. To see that is does exist, run the following command in your Terminal session:

cd $HOME
ls -d .rvm

This should display the name of the directory back in the Terminal session.

Installing IronWorker
With Ruby properly installed we can install the IronWorker tool. This tool is a Ruby program that allows us to create tasks by uploading our programs and support files into IronWorker.

To install the tool we must install the IronWorker Ruby gem. Gem's are Ruby packages that get installed under the default version of Ruby. This is why I stressed we have the right default version set.

Run the following command in the Terminal session:

gem install iron_worker_ng

If everything installs properly, the output should end like this:

7 gems installed

As a final test to make sure everything is setup correctly, run the IronWorker program:

iron_worker

If everything is working properly you should get the following output:

usage: iron_worker COMMAND [OPTIONS]

    COMMAND: upload, patch, queue, retry, schedule, log, run, install, webhook, info
    run iron_worker COMMAND --help to get more information about each command

Now we have our environment setup to talk with the IronWorker platform. Make sure you do this in all of your environments.

The Test Program
I have built a test application that we are going to run in IronWorker. To download the code and the IronWorker support files, run the following commands:

cd $HOME
export GOPATH=$HOME/example
go get github.com/goinggo/ironworker

This will copy, build and install the code into the example folder under $HOME. The program has been written to test a few things about the IronWorker Linux container environment. Let's review the code for the program first and test it locally.

For IronWorker to be really effective you want to build programs that perform a specific task. The program should be designed to be started on demand or on a schedule, run and then be terminated. You don't want to build programs that run for long periods of time. The test program runs for 60 seconds before it terminates.

There were two things I wanted to know about IronWorker that the program tests. First, I wanted to know how logging worked. Second, I wanted to know if the program would receive OS signals when I requested the running program to be killed.

With all that said let's review the code we are going to run.

Here is the logging code which can be found in the helper package:

package helper

import (
    "fmt"
    "runtime"
    "time"
)

func WriteStdout(goRoutine string, functionName string, message string) {
    fmt.Printf("%s : %s : %s : %s\n",
        time.Now().Format("2006-01-02T15:04:05.000"),
        goRoutine,
        functionName,
        message)
}

func WriteStdoutf(goRoutine string, functionName string, format string, a ...interface{}) {
    WriteStdout(goRoutine, functionName, fmt.Sprintf(format, a...))
}

There is nothing fancy here, just an abstraction layer so I change out the logging mechanism if this doesn't work.

Here is the controller code that manages the starting and termination of the program:

package controller

import (
    "github.com/goinggo/ironworker/helper"
    "github.com/goinggo/ironworker/program"
    "os"
    "os/signal"
)

const (
    NAMESPACE = "controller"
    GO_ROUTINE = "main"
)

func Run() {

    helper.WriteStdout("Main", "controller.Run", "Started")

    // Create a channel to talk with the OS.
    sigChan := make(chan os.Signal, 1)

    // Create a channel to let the program tell us it is done.
    waitChan := make(chan bool)

    // Create a channel to shut down the program early.
    shutChan := make(chan bool)

    // Launch the work routine.
    go program.DoWork(shutChan, waitChan, "Test")

    // Ask the OS to notify us about interrupt events.
    signal.Notify(sigChan, os.Interrupt)

    for {
        select {
        case <-sigchan:
            helper.WriteStdout("Main", "controller.Run", "******> Program Being Killed")

            // Signal the program to shutdown and wait for confirmation.
            shutChan <- true
            <-shutChan

            helper.WriteStdout("Main", "controller.Run", "******> Shutting Down")
            return

        case <-waitchan:
            helper.WriteStdout("Main", "controller.Run", "******> Shutting Down")
            return
        }
    }
}

I like to shutdown my applications gracefully, so if I could receive an OS signal on a kill request, that would be fantastic. I am not a channel guru and I am sure there are better ways to accomplish this. I welcome any suggestions. For now this is what we got.

The function creates three channels. The first channel is used to receive signals from the OS. The second channel is used by the Go routine that is performing the program logic to signal when it is done. The third channel is used by the controller to signal the Go routine to terminate early if necessary.

The program.DoWork function is started as a Go routine and then the controller waits for either the OS to signal or the running Go routine to signal it is done. If the OS signals to terminate, then the controller uses the shutdown channel and waits for the Go routine to respond. Then everything shuts down gracefully.

Here is the code for the Go routine that is simulating the work:

package program

import (
    "github.com/goinggo/ironworker/helper"
    "time"
)

func DoWork(shutChan chan bool, waitChan chan bool, logKey string) {
    helper.WriteStdout("Program", "program.DoWork", "Program Started")

    defer func() {
        waitChan <- true
    }()

    // Perform work for 60 seconds
    for count := 0; count < 240; count++ {
        select {
        case <-shutChan:
            helper.WriteStdout("Program", "program.DoWork", "Info : Completed : KILL REQUESTED")
            shutChan <- true
            return

        default:
            helper.WriteStdoutf("Program", "program.DoWork", "Info : Performing Work : %d", count)
            time.Sleep(time.Millisecond * 250)
        }
    }

    helper.WriteStdout("Program", "program.DoWork", "Completed")
}

The DoWork function prints a message to the log every 250 milliseconds 240 times. This gives us a minute of work that must be performed. After each write log call, the function checks if the shutdown channel has been signaled. If it has, the function terminates immediately.

Just to have a complete code sample in the post, here is the main function:

package main

import (
    "github.com/goinggo/ironworker/controller"
    "github.com/goinggo/ironworker/helper"
)

func main() {
    helper.WriteStdout("Main", "main", "Started")

    controller.Run()

    helper.WriteStdout("Main", "main", "Completed")
}

In your native environment, mine being the Mac, download the code and let Go tool build and install the application.

cd $HOME
export GOPATH=$HOME/example
go get github.com/goinggo/ironworker
cd $HOME/example/bin
./ironworker

When you run the program, let it run to completion. You should see the following output:

2013-09-07T11:42:48.701 : Main : main : Started
2013-09-07T11:42:48.701 : Main : controller.Run : Started
2013-09-07T11:42:48.701 : Program : program.DoWork : Program Started
2013-09-07T11:42:48.701 : Program : program.DoWork : Info : Performing Work : 0
2013-09-07T11:42:48.951 : Program : program.DoWork : Info : Performing Work : 1
2013-09-07T11:42:49.203 : Program : program.DoWork : Info : Performing Work : 2
2013-09-07T11:42:49.453 : Program : program.DoWork : Info : Performing Work : 3
2013-09-07T11:42:49.704 : Program : program.DoWork : Info : Performing Work : 4
2013-09-07T11:42:49.955 : Program : program.DoWork : Info : Performing Work : 5
...
2013-09-07T11:43:48.161 : Program : program.DoWork : Info : Performing Work : 237
2013-09-07T11:43:48.412 : Program : program.DoWork : Info : Performing Work : 238
2013-09-07T11:43:48.662 : Program : program.DoWork : Info : Performing Work : 239
2013-09-07T11:43:48.913 : Program : program.DoWork : Completed
2013-09-07T11:43:48.913 : Main : controller.Run : ******> Shutting Down
2013-09-07T11:43:48.913 : Main : main : Completed

The program started and terminated successfully. So the Controller logic is working. This time let's kill the program early by hitting <Ctrl> C after it starts:

2013-09-07T11:46:31.854 : Main : main : Started
2013-09-07T11:46:31.854 : Main : controller.Run : Started
2013-09-07T11:46:31.854 : Program : program.DoWork : Program Started
2013-09-07T11:46:31.854 : Program : program.DoWork : Info : Performing Work : 0
2013-09-07T11:46:32.105 : Program : program.DoWork : Info : Performing Work : 1
2013-09-07T11:46:32.356 : Program : program.DoWork : Info : Performing Work : 2
2013-09-07T11:46:32.607 : Program : program.DoWork : Info : Performing Work : 3
^C2013-09-07T11:46:32.706 : Main : controller.Run : ******> OS Notification: interrupt : 0x2
2013-09-07T11:46:32.706 : Main : controller.Run : ******> Program Being Killed
2013-09-07T11:46:32.857 : Program : program.DoWork : Info : Completed : KILL REQUESTED
2013-09-07T11:46:32.857 : Main : controller.Run : ******> Shutting Down
2013-09-07T11:46:32.857 : Main : main : Completed

As soon as I hit <Ctrl> C the OS signaled the program with the syscall.SIGINT message. That caused the Controller to signal the running program to shutdown and the program terminated gracefully.

Configuring IronWork
This is the documentation for using IronWorker:

http://dev.iron.io/worker/

I am going to walk you through the process for the test application we have. Login to your Iron.io account and select Projects from the top menu:


Enter Test into the text box and hit the Create New Project button. That should send you to the following screen:


Select the Key icon which is where you will find the Credentials for this project. We need these credentials to create a special file called iron.json. This file will be required by the IronWorker Ruby program to load our tasks into this project.

In our Terminal session let's move to the folder where the IronWorker script files are located. I want to work with the buildandrun scripts:

cd $HOME/example/src/github.com/goinggo/ironworker/scripts/buildandrun
ls -l

You should see the following files:

-rw-r--r-- 1 bill staff 995 Sep 8 20:12 buildandrun.sh
-rw-r--r-- 1 bill staff 106 Sep 8 20:12 buildandrun.worker
-rw-r--r-- 1 bill staff 81 Sep 8 20:12 iron.json

You will find a iron.json file in the folder. Edit the file and put in your credentials:

{
    "project_id" : "XXXXXXXXXXXXXXXXXXXXXX",
    "token" : "XXXXXXXXXXXXXXXXXXXXXX"
}

The next file we want to look at is the .worker file. Here is the documentation for .worker files:

http://dev.iron.io/worker/reference/dotworker/

Here is what the buildandrun.worker file looks like:

# define the runtime language
runtime 'binary'

# exec is the file that will be executed:
exec 'buildandrun.sh'

The buildandrun.worker file is telling the IronWorker Ruby program to upload and execute the buildandrun.sh file. This is the only file that will be placed into the IronWork container.  Here is what the buildandrun.sh file looks like:

export HOME_FOLDER="$HOME/Container"
export CODE_FOLDER="$HOME_FOLDER/code"
export PROGRAM_FOLDER="$CODE_FOLDER/src/github.com/goingo/ironworker"

if [ ! -e $CODE_FOLDER/bin/ironworker ]
then
  mkdir $HOME_FOLDER
  cd $HOME_FOLDER
  curl https://go.googlecode.com/files/go1.1.2.linux-amd64.tar.gz -o p.tar.bz2 && tar xf p.tar.bz2 && rm p.tar.bz2
  export GOARCH="amd64"
  export GOBIN=""
  export GOCHAR="6"
  export GOEXE=""
  export GOHOSTARCH="amd64"
  export GOHOSTOS="linux"
  export GOOS="linux"
  export GOPATH="$CODE_FOLDER"
  export GORACE=""
  export GOROOT="$HOME_FOLDER/go"
  export GOTOOLDIR="$HOME_FOLDER/go/pkg/tool/linux_amd64"
  export CC="gcc"
  export GOGCCFLAGS="-g -O2 -fPIC -m64 -pthread"
  export CGO_ENABLED="1"
  export PATH=$GOROOT/bin:$PATH
  go get github.com/goinggo/ironworker

  #git clone https://username:password@github.com/goinggo/ironworker $PROGRAM_FOLDER
  #cd $PROGRAM_FOLDER
  #go clean -i
  #go build
  #go install
fi

cd $CODE_FOLDER/bin
./ironworker

This script tests to see if the ironworker test application already exists. If it doesn't, it then proceeds to download the latest Linux binary package for Go and builds the program using the Go tool. Once the build and install is complete, the script executes the program.

You will notice these lines have been commented out in the shell script:

  #git clone https://username:password@github.com/goinggo/ironworker $PROGRAM_FOLDER
  #cd $PROGRAM_FOLDER
  #go clean -i
  #go build
  #go install

If you have a repository that requires authentication you can use this technique. This calls git clone the same way the Go tool does, so everything is copied to the right directory structure. If your code references other libraries, you will need to do that manually.  If you run the Go Get command with the [-x] option, you can see all the calls the Go tool issues. Just copy what you need and add it to your shell script.

IronWorker does have a copy of the Linux binary package for Go version 1.0.2 already pre-configured in every IronWorker container. The script installs the latest version to show you how that can be accomplished if the version you need is not the one installed. The technique can also be used to install other packages that might be required.

If you want to build the code every time the task runs, you could be smarter and run go version first. If the right version is not already available then you can download the version of Go you need:

export HOME_FOLDER="$HOME/Container"
export CODE_FOLDER="$HOME_FOLDER/code"
export PROGRAM_FOLDER="$CODE_FOLDER/src/github.com/goingo/ironworker"

go version > ver.txt
goversion=$(<ver.txt)
head ver.txt

# IronWorker will report go version go1.0.2

if [ "$goversion" != "go version go1.1.2 linux/amd64" ]
then
  mkdir $HOME_FOLDER
  cd $HOME_FOLDER
  curl https://go.googlecode.com/files/go1.1.2.linux-amd64.tar.gz -o p.tar.bz2 && tar xf p.tar.bz2 && rm p.tar.bz2
  export GOARCH="amd64"
  export GOBIN=""
  export GOCHAR="6"
  export GOEXE=""
  export GOHOSTARCH="amd64"
  export GOHOSTOS="linux"
  export GOOS="linux"
  export GOPATH="$CODE_FOLDER"
  export GORACE=""
  export GOROOT="$HOME_FOLDER/go"
  export GOTOOLDIR="$HOME_FOLDER/go/pkg/tool/linux_amd64"
  export CC="gcc"
  export GOGCCFLAGS="-g -O2 -fPIC -m64 -pthread"
  export CGO_ENABLED="1"
  export PATH=$GOROOT/bin:$PATH
fi

go get -x github.com/goinggo/ironworker
cd $CODE_FOLDER/bin
./ironworker

Loading IronWork With A Task
We have everything we need to load and run our first task for the Test project. In your Terminal session run the following command:

cd $HOME/example/src/github.com/goinggo/ironworker/scripts/buildandrun
iron_worker upload buildandrun

If everything is successful you should see the following output:

------> Creating client
        Project 'Test' with id='522b4c518a0c960009000007'
------> Creating code package
        Found workerfile with path='buildandrun.worker'
        Detected exec with path='buildandrun.sh' and args='{}'
        Code package name is 'buildandrun'
------> Uploading code package 'buildandrun'
        Code package uploaded with id='522d147b91c530531f6f4e92' and revision='1'
        Check 'https://hud.iron.io/tq/projects/522b4c518a0c960009000007/code/522d147b91c530531f6f4e92' for more info

Go back to the Iron.io website and let's see if our new task is there. Select Projects again from the main menu and select the Worker button to the right of your Test project.


Select the Tasks tab and you should see the buildandrun task we just uploaded. Select the task and you should see the following screen:



There is a big grey button that says Queue a Task. Let's hit that button to run our task.

This dialog box will popup. Use all the defaults and hit the Queue Task button.

This will queue the task and then it should start running.
Once you hit the queue button the screen should change:



The task will start in the queued state, then running and finally complete. Once the task is done, click on the Log link.



You will see the shell script did everything perfectly. It downloaded Go and successfully built the program. Then it started executing it.

Let's run the program again and check two things. First, let's see if the container is saved and the download of Go does not have to occur again. Second, let's kill the program early and see if it receives any OS signals.

Hit the Queue a Task button again and after several seconds let's kill it:




You can see that I killed the task 21 seconds into the run. When I look at the logs I am a bit disappointed. First, the task downloaded Go again. This means I have a new and clean IronWorker container every time the task runs. Second, the program did not receive any OS signals when I issued the kill. It appears the IronWork container is forced to die and the program gets no OS notifications.

It is not the end of the world, just something that is good to know. Based on this new information it seems we want to load binaries into the IronWorker container when we can. This way we don't need to spend time downloading things that can be pre-compiled. However, I was able to use IronWorker from my Mac environment which is a real plus.

On the other hand, having Go build and install your program every time could be huge. If you upload code changes to your repository you don't need to upload a new revision of the task. The Go tool will pull down the latest code, build it and run the program. Then again, that could cause problems.

At the end of the day you need to decide what is best for your different scenarios. What's awesome is that IronWorker gives you all the flexibility you need to make it work.

Working With Builder Tasks
Builder Tasks are a hidden gem within IronWorker. A builder task allows you to build your code and generate a task that you will use for your application inside of IronWorker. This is really the best of both worlds because you don't need to download Go and build the code every time the task runs. You can do your build once. Your application task runs immediately because it is always ready to go with the binaries and other support files it needs.

Go back into the scripts folder and find the sub-folder called buildertask. Let's quickly run through the files and see how this works.

The task-builder.sh file is the script that knows how to build our code.

export HOME_FOLDER="$HOME/Container"
export CODE_FOLDER="$HOME_FOLDER/code"
export PROGRAM_FOLDER="$CODE_FOLDER/src/github.com/goingo/ironworker"

mkdir $HOME_FOLDER
cd $HOME_FOLDER
curl https://go.googlecode.com/files/go1.1.2.linux-amd64.tar.gz -o p.tar.bz2 && tar xf p.tar.bz2 && rm p.tar.bz2
export GOARCH="amd64"
export GOBIN=""
export GOCHAR="6"
export GOEXE=""
export GOHOSTARCH="amd64"
export GOHOSTOS="linux"
export GOOS="linux"
export GOPATH="$CODE_FOLDER"
export GORACE=""
export GOROOT="$HOME_FOLDER/go"
export GOTOOLDIR="$HOME_FOLDER/go/pkg/tool/linux_amd64"
export CC="gcc"
export GOGCCFLAGS="-g -O2 -fPIC -m64 -pthread"
export CGO_ENABLED="1"
export PATH=$GOROOT/bin:$PATH

go get github.com/goinggo/ironworker

cd $CODE_FOLDER/bin
cp ironworker $HOME/__build__/ironworker

The code downloads Go and then uses the Go tool to build the program. At the end of the script we copy the binary that the Go tool built to the IronWorker staging area. Anything you copy to this folder will be placed into our new application task.

The task.sh file is the script that is executed by the application task.

./ironworker

In our case we only need to run the binary. Remember the binary is being created by the task-builder.sh script file.

The task.worker file performs all the magic:

runtime 'binary'
exec 'task.sh'
build 'sh ./task-builder.sh'
file 'task-builder.sh'

The worker file tells IronWorker that our application task is a binary and to load and run the task.sh script. Next we have the build command. This tells IronWorker to perform a remote build by exexuting the task-builder.sh script. The file command pulls the task-builder.sh file into the builder task so it can executed remotely.

Let's navigate to the buildertask folder and try all this out:

cd $HOME/example/src/github.com/goinggo/ironworker/scripts/buildertask

We need to edit the iron.json file with the credentials again. Once you do that run the following command:

iron_worker upload task

This time the IronWorker will take a bit longer to run. It will be performing a remote build and we must wait until it is complete. Once everything is done you should see the following:

------> Creating client
        Project 'Test' with id='522b4c518a0c960009000007'
------> Creating code package
        Found workerfile with path='task.worker'
        Detected exec with path='task.sh' and args='{}'
        Merging file with path='task-builder.sh' and dest=''
        Code package name is 'task'
------> Uploading and building code package 'task'
        Remote building worker
        Code package uploaded with id='522d0cad3cb46653c5e15cbe' and revision='1'
        Check 'https://hud.iron.io/tq/projects/522b4c518a0c960009000007/code/522d0cad3cb46653c5e15cbe' for more info

Now let's switch to the Iron.io website and see what we have. Go back to the Tasks tab and you should see two new tasks:


You will notice the task::builder task has already been executed. This is the remote build that was being performed. Let's look at the logs. You will see that the Go binary package for Linux was downloaded and the project was also downloaded and built. Look at the last two lines in the log:


This is where we copied the binary to the staging folder __build__. We didn't have any errors or problems coping the final binary.

Now we can try running the application task. Select task from the Task list and queue the job. It should start right up and run for a minute. Once it is done let's look at the log:



When you look at the log you can see the program starts running. There is no downloading of Go or any other build work.

Using a builder task is a great way to have IronWorker stage and build the code for us. If you change the code and need to perform another build you must run the IronWorker program again. This will create new revisions of both the builder and application tasks. You can't rerun the Builder task manually.

IronWork and PaperTrail
Looking at the logs in IronWork is a real convenience but you will want to use a third party system to manage your logging. I love PaperTrail and the IronWorker integration is seamless.

Go to PaperTrail and create a free account:

https://papertrailapp.com/

After you get your account and login, go to the Dashboard.


Find the Create Group button and create a new group:


Click the Save button at the bottom. Now on the Dashboard you should have your new group:


Go to the Account options.


Create a Log Destination for your IronWorker Test project. Click on the Create log destination button:


Click on the Edit Settings button:


Make sure you accept logs from unrecognized systems and select the group you just created. Then hit the Update button.

Now copy the destination url and go back to your IronWorker Test project. Select the settings icon:


Take the PaperTrail url and enter it into the Logger URL text box using udp as the protocol. Click the Update button and then click on the Worker button again and find the buildandrun task. The udp must be lowercase or your task will fail.

Queue the task one last time and let it run to completion. As the task is running, go back to the PaperTrail website. Go to the Dashboard and hit the refresh button on the browser:


You should start seeing events coming into your group.  Click on the All events drop down on the right and you will see the logs in PaperTrail.



Conclusion
I just scratched the surface with how you can use IronWorker to run your applications. This is a really flexible environment with really no restrictions. You have access to download and install any packages you need, access to the local disk and integration to PaperTrail and a few other logging systems.

Though you don't need a Linux VM to use IronWorker, you may want to consider having one so you can stage and load your binary programs directly. Again, you have the flexibility to use IronWorker as you see fit.

I hope you try out the service. Use my application or build your own. Post a comment about your experience and anything new your learn. I plan on using IronWorker for two projects I am working on and expect only great things.

11 comments:

  1. Did you try (.worker file)
    ---
    exec 'run.sh'
    build 'build.sh' # run this once on upload
    ...
    ---

    ReplyDelete
    Replies
    1. I did not try this but I don't think there is a build command for a worker file. I just checked the documentation. Also, every time the task runs you have a clean container. So the build would need to run again for everything to work. At least this is what I have learned from my experiments and talking with the tech team at Iron.io. So this solution would require two files to be uploaded and for run.sh to call build.sh. Though I would love to be wrong and this be a solution to the rebuild problem.

      Delete
    2. Just checked - 'build' command is supported. And this command will modify original package content.
      upload package (with name ::builder)-> launch build command -> upload results as real package

      exec 'run.sh'
      build 'sh ./build.sh'
      file 'build.sh'
      dir '....'


      Delete
    3. I will try it first thing tomorrow and see if the container keeps the changes. That would be awesome. Thanks for the comment.

      Delete
    4. You could email me at bill@ardanstudios.com. I tried this and it did not work. The build process ran but the task did not.

      Delete
  2. I added a new script and worker file called buildandcheckgoversion. This will check the version of go installed in the container.

    ReplyDelete
  3. Someone commented about a product called Vagrant ( http://www.vagrantup.com/ ). Looks like another cool and easy way to have an Ubuntu Linux environment available to stage and test your programs. It does not provide a desktop. You SSH into the virtual machine. You won't need to purchase VMWare or Parallels.

    ReplyDelete
  4. Take a look at goxc (https://github.com/laher/goxc) for cross-compiling your go applications. A bit more lightweight and maintainable than trying to maintain a VM just for compilation.

    ReplyDelete
    Replies
    1. Thanks for the comment. Funny you say that. Before I learned how to create the builder task I was thinking about cross-compiling. I have not tried to use cross-compling. Maybe you can share some examples?

      Delete
    2. The goxc documentation is pretty good at outlining the basics for using that utility, but basically cross-compilation is crazy easy in Go. If you've installed Go from source (http://golang.org/doc/install/source) the goxc tool will actually build the toolchain for all supported platforms in a nifty little loop.

      Then you can specify your options (target output directory, architecture, operating system(s), etc.) via flags OR you can create a config file (example of one of mine: https://gist.github.com/TehBilly/6541005) so that each time you run 'goxc' it builds all applicable versions and places them in your "ArtifactsDest" directory.

      Very straightforward overall, but I'm sure the steps taken could be outlined in more detail. But this is how I moved away from either just building on my target machine or manually setting up a cross-compilation environment and a handful of helper batch/bash scripts.

      Delete
    3. Thanks for the comment. There are so many things to try and such little time. This is great information to know!!

      Delete