Setting up a SmartOS image server


Recently I've found myself in need of having a local SmartOS image server; while Joyent has a datacenter in Amsterdam, it seems that images are still pulled from the US west coast. After trying various servers and even the plain nginx setup (though that doesn't appear to work anymore with imgadm v3), I finally ran into dsapid. After a bit of research it turns out there's bits and pieces of documentation scattered across the web, so here's one page which tries to bring it all together.

Setup

First build dsapid (puts the dsapid binary in your current directory):

go get -d github.com/MerlinDMC/dsapid/server
go build -o dsapid github.com/MerlinDMC/dsapid/server

While I'm running my dsapid instance in a zone, during testing I've successfully ran it on OS X and OpenBSD too.

Having your config.json well-formed is very important: if you have invalid JSON then dsapid will silently use it's builtin defaults. Leaving you wondering why your settings aren't in effect, and why it didn't bail out. So be sure to run it through json --validate on SmartOS.

The config below is for a non-syncing instance (more on syncing below):

{
  "hostname": "images.jasper.la",
  "base_url": "http://images.jasper.la/",
  "datadir": "/opt/dsapid/data",
  "mount_ui": "/opt/dsapid/ui",
  "users": "/opt/dsapid/config/users.json",
  "listen": {
    "http": {
      "address": "0.0.0.0:8000",
      "ssl": false
    }
  }
}

Now you can start your instance with:

./dsapid -config config.json  -log_level debug

Note that this runs without SSL, I didn't yet get around to figuring out how to configure that as I couldn't find any documentation on that yet. So it's probably material for later post.

Regarding syncing, you can have you instance synchronise with images.joyent.com as well as datasets.at (community contributed images) with a config block like this:

"sync": [
    {
      "name": "official joyent imgapi",
      "active": true,
      "type": "imgapi",
      "provider": "joyent",
      "source": "https://images.joyent.com/images",
      "delay": "12h"
    }
]

Note that it will fetch all images, unless you explicitly limit it. See the post on Smartcore for more information on that for datasets; I've not tried it with images.

So now you can create your initial user (source):

curl -v -X PUT http://127.0.0.1:8000/api/users -d '{ "name": "admin", "token": "admintoken", "type": "user", "roles": [ "s_dataset.upload", "s_dataset.manage", "s_dataset.admin" ] }'

Note that it has to be run from the localhost.

Finally you can serve a frontend, I'm using a fork of MerlinDMC/dsapi-ui with some small modifications. Or you can grab the generated files from datasets-at/mi-dsapid.

Publishing images

One downside of dsapid is that imgadm publish does not work and you'll have to use this magic curl command instead:

curl -v -u admintoken: http://192.168.178.172:8000/api/upload -F manifest=@manifest.json  -Ffile=@python-15.4.0.0.zfs.gz

I'm pushing an example image that's created with sm-prepare-image and a manifest.json like:

{
  "uuid": "48ea274b-0668-64f6-fde0-e3ad3bc552eb",
  "name": "python",
  "version": "15.4.0.0",
  "description": "Minimal image with Python installed (e.g. for use with Ansible)",
  "os": "smartos",
  "type": "zone-dataset",
  "platform_type": "smartos",
  "creator_name": "jasperla",
  "creator_uuid": "3433bb9e-0144-40ad-aeb9-ed51b88c3d0f",
  "vendor_uuid": "3433bb9e-0144-40ad-aeb9-ed51b88c3d0f",
  "urn": "jasperla:jasperla:python:15.4.0.0",
  "created_at": "2016-03-06T11:40:20.000Z",
  "updated_at": "2016-03-06T11:40:20.000Z",
  "published_at": "2016-03-06T11:40:20.000Z",
  "files": [
    {
      "path": "python-15.4.0.0.zfs.gz",
      "sha1": "7595eac569ef67fab8f286f6d2f88a3a0191b11c",
      "size": 168310282,
      "compression": "gzip"
    }
  ]
}

Note however that the documentation gives an example of a manifest file, you will have to add the compression to the files section. Otherwise you cannot import images with imgadm.

Ansible

While dabbling about with Ansible I've created a playbook (not even a role right now) which takes are of provisioning a zone with dsapid. You'll probably want to adjust a bunch of settings, but it should give one hint how to write your own playbook/role.

Note that it includes an updated version of the pkgin module for Ansible which supports tasks such as: pkgin: full_upgrade=yes update_cache=yes. First part of it is awaiting review pending a merge upstream.

Update: both pull requests have now been merged into upstream ansible-modules-extras