because (en)ough with the y(aml) already

go get



Because (EN)ough with the y(AML) already

wercker status


  • deployment manifests as testable code
  • so no one has to write another bosh deployment manifest in yaml again.


how to use the enaml core to develop OMG product plugins

Download the latest binary release for your OS from:

create golang structs for job properties from a release

# user @ MacBook-Pro in ~/stuff/sample [00:00:00]
$ wget

# user @ MacBook-Pro in ~/stuff/sample [00:00:00]
$ #target the bosh release you with to enamlize
$ enaml generate\?v\=27
Could not find release in local cache. Downloading now.
completed generating release job structs for

WARNING: You may see a warning similar to the following:

2016/08/22 17:01:16 W0822 17:01:16.225166 3622 generate.go:47] ******** Recommended creating custom yaml marshaller on GorouterJob for RoutingApi ********

This is warning is letting you know that by default, the RoutingApi field in the generated GorouterJob struct will not be marshalled. The most common reason for this is that the code generator combined multiple YAML objects into a single struct. When this happens, you'll want to create a custom marshaller.

The convention is to place a new file alongside the original, with an __marshal.go_ suffix. In this file, add a MarshalYAML function to the autogenerated struct, and return an object that marshals to your desired output.

In this example, we put some fields of RoutingApi under the routing_api YAML key, and other fields under routing-api:

package gorouter

// MarshalYAML implements the yaml.Marshaler interface.
func (j *GorouterJob) MarshalYAML() (interface{}, error) {
    result := make(map[string]interface{})
    if j.Nats != nil {
        result["nats"] = j.Nats
    if j.MetronEndpoint != nil {
        result["metron_endpoint"] = j.MetronEndpoint
    if j.RequestTimeoutInSeconds != nil {
        result["request_timeout_in_seconds"] = j.RequestTimeoutInSeconds
    if j.Uaa != nil {
        result["uaa"] = j.Uaa
    if j.Dropsonde != nil {
        result["dropsonde"] = j.Dropsonde
    if j.Router != nil {
        result["router"] = j.Router

    // The routing API is the tricky part that enaml can't solve alone.
    // Some of the fields are under "routing_api", and others are under
    // "routing-api".
    if j.RoutingApi != nil {
        result["routing_api"] = map[string]interface{}{
            "enabled": j.RoutingApi.Enabled,
        result["routing-api"] = map[string]interface{}{
            "port":          j.RoutingApi.Port,
            "auth_disabled": j.RoutingApi.AuthDisabled,
    return result, nil

When code generation completes, you'll see the resulting code in the enaml-gen directory:

# user @ MacBook-Pro in ~/stuff/sample [00:00:00]
$ ls

# user @ MacBook-Pro in ~/stuff/sample [00:00:00]
$ #golang packages have been generated for all job properties in the give release version
$ ls enaml-gen/
broker-deregistrar      cf-containers-broker    docker                  swarm_agent
broker-registrar        containers              fetch-containers-images swarm_manager

use the generated structs in your plugin

package main

import (

func main() {
        DockerRef: new(docker.Docker),

type MyProduct struct{
    DockerRef docker.Docker

func (s *MyProduct) GetProduct(args []string, cloudconfig []byte) []byte {
    return []byte("")

func (s *MyProduct) GetFlags() (flags []pcli.Flag) {

func (s *MyProduct) GetMeta() product.Meta {
    return product.Meta{
        Name: "myfakeproduct",

maybe you've got a manifest but dont know how to maintain it (ie. key/cert/pass rotation, or automated component scaling, etc)

package main

import (


//this will take a manifest scale its instance counts and return a new manifest
func main() {
    originalFileBytes, _ := ioutil.ReadFile(os.Args[1])
    enamlizedManifest := enaml.NewDeploymentManifest(originalFileBytes)

    for i, job := range enamlizedManifest.Jobs {
        job.Instances += 1
        enamlizedManifest.Jobs[i] = job
    ymlBytes, _ := enaml.Paint(enamlizedManifest)

#then call it
$> go run main.go my-cf-manifest.yml > my-scaled-cf-manifest.yml

how your deployment could look

package concourse

import (

var (
    DefaultName   = "concourse"
    DirectorUUID  = "asdfasdfasdf"
    StemcellAlias = "trusty"

func main() {

type Deployment struct {
    Manifest *enaml.DeploymentManifest

func NewDeployment() (d Deployment) {
    d = Deployment{}
    d.Manifest = new(enaml.DeploymentManifest)
    d.Manifest.AddStemcellByName("ubuntu-trusty", StemcellAlias)
    web := enaml.NewInstanceGroup("web", 1, "web", StemcellAlias)
    web.AddNetwork(enaml.Network{"name": "private"})
    atc := enaml.NewInstanceJob("atc", "concourse", releasejobs.Atc{
        ExternalUrl:        "something",
        BasicAuthUsername:  "user",
        BasicAuthPassword:  "password",
        PostgresqlDatabase: "&atc_db atc",
    tsa := enaml.NewInstanceJob("tsa", "concourse", releasejobs.Tsa{})
    db := enaml.NewInstanceGroup("db", 1, "database", StemcellAlias)
    worker := enaml.NewInstanceGroup("worker", 1, "worker", StemcellAlias)

func (s Deployment) GetDeployment() enaml.DeploymentManifest {
    return *s.Manifest


Enaml uses Glide to manage vendored Go dependencies. Glide is a tool for managing the vendor directory within a Go package. As such, Golang 1.6+ is recommended.

  1. If you haven't done so already, install glide and configure your GOPATH.
  2. Clone enaml to your GOPATH: git clone $GOPATH/src/
  3. Install dependencies: cd $GOPATH/src/ && glide install
  4. Run the enaml tests go test $(glide novendor)
  5. Build the enaml executable go build -o $GOPATH/bin/enaml cmd/enaml/main.go