I still don't have an elegant solution, but my hack using runcmd and Ruby (which may or may not be installed on your base image, so you may need to list it in packages) looks like this:
runcmd:
- - ruby
- -ryaml
- -rfileutils
- -rzlib
- -rstringio
- -e
- |
ud=%x{curl -s http://169.254.169.254/2009-04-04/user-data}
ud = Zlib::GzipReader.new(StringIO.new(ud)).read unless ud[0] = "#"
YAML.load(ud)["write_files"].each { |f|
FileUtils.mkdir_p(File.dirname(f["path"]))
File.open(f["path"], "w") { |io| io.write(f["content"]) }
File.chmod(f["permissions"].to_i(8), f["path"])
}
This hack also works from relatively recent Amazon Linux instances which have a recent cloud-init, but for some reason have the write_files module disabled.
The code assumes user-data is either the plain text #cloud-config or a gzip compression of that. If using something more complicated, such as multi-part or includes, then you may want to read the parsed data from /var/lib/cloud/instance/user-data.txt.i, but that requires MIME parsing - which I'll leave as an exercise to the reader :-).