If you are anything like me, you love to write apps for Ubuntu. Like the Ubuntu phone two years ago, snappy is a bit of a green field in terms of IoT related apps. If you are like me and have a Beagle Bone and a Raspberry Pi 2 and several old netbooks and laptops lying around, than snappy is quite the playground.
So, in this blog, I will provide an introduction to the developer workflow that I am currently using to create my own programs for devices running snappy Ubuntu Core.
Before I do, please note that there is one big difference between Ubuntu on the phone and snappy. Namely, in snappy Ubuntu Core, you can make any existing software run as a snap so long as you modify it to run in snappy’s application isolation model. Ubuntu phones are somewhat more limited in terms of the software that they can run. Therefore, there was a lot of writing new apps for the phone, whereas for snappy, there are new apps, but also there is making existing app available as snaps.
Of course, what I like to do is write new apps, so that’s what this post is about. I’ll leave it others to cover how to convert your existing software to snaps.
A word about my language of choice for snappy. I’ve been through two other big linguistic love affairs so far with Ubuntu app writing. First with Python on the desktop, and then with QML on the phone. Snappy is totally polyamorous when it comes to languages and libraries, of course. None the less, I’m experiencing intense NRE with Go.
Go seems perfectly suited to writing software in a snappy world.
- It is designed to create self-contained binaries that are easy to deploy and update.
- It can use existing C libraries, and embed those libraries into the binary and therefore the snaps.
- It is a “curly-brace” (i.e. “c-like”) language, for those of us experienced with such languages.
- It is designed from the start for async programming.
- It has a rich built in set of networking-related classes.
- It has a great tutorial and tons of examples online.
The Go Stub Code
I worked with Alexander Sack quite a bit to try to make a Go project template that was simple to modify and get started, but was still a valid Go workspace that would be able to grow with you as you learned more Go and your applications got more featureful.
As a result of using a proper Go workspace, there are a lot of files to rename and move around to get started, and then again to make your snap. Don’t worry, though. If you follow the instructions it is all easy, and you will come to appreciate the power of simplicity of both Go and Snappy.
Getting Set Up
Before we start writing the app, let’s get our development environment setup.
Assuming you are using Ubuntu 15.04 for your developer desktop, you can simply install go from the archives:
$ sudo apt-get install golang-go
Then, if you have an ARM-based device that you are going to target, you’ll want to setup cross compiling so you can compile your Go code for those devices.
$ sudo apt-get install golang-go-linux-arm
Setup Snappy Tools
I am doing all of this on Ubuntu 15.04. I haven’t tested it on 14.04, but I assume it will all “just work”. The getting started page has all the instructions you need right at the top for installing the snappy tools from the PPA. So go there, and then come back when you have them installed.
Setup a Snappy Image
Secondly, we need a snappy image. Fortunately, if you can run a VM (which you can), you can run snappy. For testing your snap packages you should have snappy running in a VM, so go back to the getting started page and follow the instructions for getting set up to run snappy in a KVM. Don’t launch the image quite yet, because we need to launch it slightly differently for testing our app.
Note, I strongly suggest that you choose the stable 15.04 images. These images will get updated with fixes and enhancements, but only after the edge channels have gone through significant QA. If you are on 15.04 Edge, breakages are likely.
Set Up a Snappy Development Board
If you have a Beagle Bone Black, Raspberry Pi 2, or other supported board, you might also want to set that up with the stable 15.04 so image so you can you can test your snaps there as well. You can specific directions for setting up your particular board(s) on the getting started page.
Writing the App on The Desktop
The app doesn’t do anything but listen to port 8081 and respond with the text “hello world”. That’s because I want to focus on the developer workflow in this doc, not on writing Go code. And this is the code that Alexander Sack included in the walkthrough where I basically ripped off all the ideas and code.
Ok, so let’s get really started.
Now we’ll get started by taking a branch of th 32 e template code.
$ bzr branch lp:~rick-rickspencer3/+junk/go-template port-listener
This copies the code into a dir called ‘port-listener’, which is the name of the new app. So, you can change “port-listener’ to whatever your app’s real name is.
The stub Go workspace in the template needs to be fixed up to reflect your real project structure. So we’ll need to rename some files, and change some strings within files.
- Rename src/main. In a Go workspace, you typically use directories that reference your intended public source code repository. It is not necessary to actually host the code to write it, but it is considered good practice to do so. We can assume that later we will create a launchpad project to host the code. This will keep the structure simple for now. Therefore:
$ cd port-listener
$ mv src/launchpad.net/project-name/ src/launchpad.net/port-listener
- Next, we rename the file main.go as well.
$ mv src/launchpad.net/port-listener/main.go src/launchpad.net/port-listener/port-listener.go
Go Write Some Code
So, let’s pretend to hack on our program. What I am going to do is edit the Go code and test it locally until I am happy with out how it works. By “locally” I mean “on my desktop”. This works because my desktop is Ubuntu, and snappy is Ubuntu. If my desktop were a different OS, I would do the development in an Ubuntu VM.
$ gedit src/launchpad.net/port-listener/port-listener.go &
This opens the code sample that Alexander wrote. Normally, I would delete most of this code and make Go do whatever I want it to do. For example, it could upload data to a cloud, or it could read sensor data and drive attached peripherals, etc… For now it will just provide a string.
To prove that I changed something, I’ll change “Hello World” to “hello world”.
I do all my coding and debugging right directly on my Ubuntu laptop. I don’t worry about building the snap and testing it on a snappy system until I am happy with it on my desktop. This is pretty much exactly how I write apps for the phone.
So, let’s build and test it. Since I am on an amd64 laptop, I will build for that arch.
$ cd bin/x86_64-linux-gnu/
$ go build ../../src/launchpad.net/port-listener/
Through go magic, I get binary!
Now I run it:
2015/05/27 09:57:38 Starting webserver on :8081
It seems to be working as expected, but let’s test it in the web browser by navigating to localhost:8081 (the port specified in the code).
Alright, the code is working on my desktop, let’s get this puppy onto a snappy system and test it out.
Building the Snap Package
So, first thing, let’s look in the meta directory. meta/ is required in order to make a snap. It provides information to the system such as what file to run on invocation, what permissions are necessary for the snap to run, as well things like the name of the project and other stuff that might be found in a traditional .desktop file.
I’ll just fill in some fields:
$ cd ../../snappy
$ gedit meta/package.yaml &
I change the file to look like this:
vendor: Rick Spencer
architecture: [amd64, armhf]
- name: port-listener
description: "template Go snappy project"
Ultimately, I’ll change meta/readme.md as well, but for now, we’ll just leave it alone since this is just and example.
Notice that the services section says to start something at bin/port-listener, but there is no such file there. To fix this, you need to rename bin/run to bin/port-listener.
$ mv bin/run bin/port-listener
The run file plays a little trick. It invokes the correct binary file depending on the currently running architecture. This allows you to package one snap that works on my architectures.
I am going to test it on two systems. First, I will test it on a snappy image running in a VM on my desktop, then I will test it on my Beagle Bone Black. Since the Beagle Bone Black is an Arm-based SoC, I will have to build the Arm binary. So, going back to the Go workspace, I build the Arm version as well.
$ cd ../bin/arm-linux-gnueabihf/
$ GOARCH=arm GOARM=7 go build ../../src/launchpad.net/port-listener
Now that I have the binaries built, I will simply move them to the correct place in my snap dir.
$ cd ../..
$ cp bin/x86_64-linux-gnu/port-listener snappy/bin/x86_64-linux-gnu/
$ cp bin/arm-linux-gnueabihf/port-listener snappy/bin/arm-linux-gnueabihf/
Now I need to be build my snap. I’ll just go to the snappy dir, and since I already edited my meta/package.yaml, I know that snappy build will work.
$ cd snappy
$ snappy build
Generated ‘port-listener_0.1_multi.snap’ snap
That’s it! I have my snap. So, let’s try it on an image. First, I need a snappy image to run.
Testing on Snappy in KVM
When you launch an image with kvm, you map ports. Remember, that the app listens to port 8081. So, we need to add that port mapping by saying -redir :realport::virtualimageport. Let’s say :8091:8081 so that we will map the images port 8081 to the desktop’s real port 8091. Also, to support ssh, we’ll map the image’s port 22 to the desktop’s port 8022. So …
$ kvm -m 512 -redir :8091::8081 -redir :8022::22 ubuntu-15.04-snappy-amd64-generic.img
The image is running now, so we need to install the snap onto the image. The snappy tools have a built in command for that. The snappy-remote command works over ssh, which is normally on port 22. But, remember when we launched the snappy image we mapped the images port 22 to the desktop’s port 8022. So we can use snappy remote over that port, like this:
$ snappy-remote --url=ssh://localhost:8022 install port-listener_0.1_multi.snap
Just as a note, the default password for the image is “ubuntu”. You’ll need to supply that when you run the command for the first time.
This will install the snap for us over ssh. So, we just say “yes” to all the prompts until the install is done.
Now, we can test via the browser. Remember, that we mapped port 8081 in the VM to 8091 on the desktop, so we get to it slightly differently. Notice the url in the image below:
It works! (Notice the little “g” on the end. That way, you know I am not faking, and that I can’t type well 😉 ).
Testing the Snap on a Device
Now I want to test it out on my Beagle Bone Black.
Yes, this is in my living room. Don’t judge. The Beagle Bone is in the clear case. My Raspberry Pi 2 is hanging out in the orange matchbox cover (I left the paper on the top of the cover because I thought the frosted look made it look cool).
My Beagle Bone Black is already set up with snappy, so I just put it on the network by plugging it into my home router. Now, because the Beagle Bone Black is running the webdm snap, it advertises itself on my local network at the address webdm.local.
Once, I have it, I use the exact same snappy-remote command, just with the address for my Beagle Bone instead on webdm. Note that if you choose not to installl webdm, or if you find that it doesn’t work reliably in your local network topology, you can just use the ip address of the device on the local network instead.
$ snappy-remote --url=ssh://webdm.local install port-listener_0.1_multi.snap
Now I will use a web browser to navigate to the port on the device and see if it is listening.
And … It works!
It’s tested on snappy on both architectures, so,if this were a real app, it would be ready for the store.
I have shown how to take write and debug Go code for a snappy system on an Ubuntu desktop. Then I showed that it is easy to make a snap package from that code that works on multiple architectures. Then I showed how to deploy that code to snappy in a VM, and snappy on a real device to test the snap.
There are two big things missing from my template right now.
- Tests! I will amend the template to include testability from the start.
- Configuration. An application might need to read in configuration data which the admin of the system can set. If the config is read only, this will typically be done via a YAML file. I’ll add that as well.
- Putting the snap in the store. Since the app doesn’t really do anything yet, there is no reason to put it in the store. However, after if the app did something useful I might want to make it available to other Ubuntu users (for free or paid) in the Ubuntu store.
About the author
Rick Spencer VP of Ubuntu Engineering. Based in Seattle, Rick is passionate leading teams in the creation of amazing software. He strives to bring clarity of vision, passion for quality, and user-centered decision making to large software development teams.