~rogpeppe/juju-core/438-local-instance-Addresses

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package formula

import (
	"io"
	"io/ioutil"
	"launchpad.net/ensemble/go/schema"
	"launchpad.net/goyaml"
	"os"
)

// Relation represents a single relation defined in the formula
// metadata.yaml file.
type Relation struct {
	Interface string
	Optional  bool
	Limit     int
}

// Meta represents all the known content that may be defined
// within a formula's metadata.yaml file.
type Meta struct {
	Name        string
	Revision    int
	Summary     string
	Description string
	Provides    map[string]Relation
	Requires    map[string]Relation
	Peers       map[string]Relation
}

// ReadMeta reads the content of a metadata.yaml file and returns
// its representation.
func ReadMeta(r io.Reader) (meta *Meta, err os.Error) {
	data, err := ioutil.ReadAll(r)
	if err != nil {
		return
	}
	raw := make(map[interface{}]interface{})
	err = goyaml.Unmarshal(data, raw)
	if err != nil {
		return
	}
	v, err := formulaSchema.Coerce(raw, nil)
	if err != nil {
		return nil, os.NewError("metadata: " + err.String())
	}
	m := v.(schema.MapType)
	meta = &Meta{}
	meta.Name = m["name"].(string)
	// Schema decodes as int64, but the int range should be good
	// enough for revisions.
	meta.Revision = int(m["revision"].(int64))
	meta.Summary = m["summary"].(string)
	meta.Description = m["description"].(string)
	meta.Provides = parseRelations(m["provides"])
	meta.Requires = parseRelations(m["requires"])
	meta.Peers = parseRelations(m["peers"])
	return
}

func parseRelations(relations interface{}) map[string]Relation {
	if relations == nil {
		return nil
	}
	result := make(map[string]Relation)
	for name, rel := range relations.(schema.MapType) {
		relMap := rel.(schema.MapType)
		relation := Relation{}
		relation.Interface = relMap["interface"].(string)
		relation.Optional = relMap["optional"].(bool)
		if relMap["limit"] != nil {
			// Schema defaults to int64, but we know
			// the int range should be more than enough.
			relation.Limit = int(relMap["limit"].(int64))
		}
		result[name.(string)] = relation
	}
	return result
}

// Schema coercer that expands the interface shorthand notation.
// A consistent format is easier to work with than considering the
// potential difference everywhere.
//
// Supports the following variants::
//
//   provides:
//     server: riak
//     admin: http
//     foobar:
//       interface: blah
//
//   provides:
//     server:
//       interface: mysql
//       limit:
//       optional: false
//
// In all input cases, the output is the fully specified interface
// representation as seen in the mysql interface description above.
func ifaceExpander(limit interface{}) schema.Checker {
	return ifaceExpC{limit}
}

type ifaceExpC struct {
	limit interface{}
}

var (
	stringC = schema.String()
	mapC    = schema.Map(schema.String(), schema.Any())
)

func (c ifaceExpC) Coerce(v interface{}, path []string) (newv interface{}, err os.Error) {
	s, err := stringC.Coerce(v, path)
	if err == nil {
		newv = schema.MapType{
			"interface": s,
			"limit":     c.limit,
			"optional":  false,
		}
		return
	}

	// Optional values are context-sensitive and/or have
	// defaults, which is different than what KeyDict can
	// readily support. So just do it here first, then
	// coerce to the real schema.
	v, err = mapC.Coerce(v, path)
	if err != nil {
		return
	}
	m := v.(schema.MapType)
	if _, ok := m["limit"]; !ok {
		m["limit"] = c.limit
	}
	if _, ok := m["optional"]; !ok {
		m["optional"] = false
	}
	return ifaceSchema.Coerce(m, path)
}

var ifaceSchema = schema.FieldMap(schema.Fields{
	"interface": schema.String(),
	"limit":     schema.OneOf(schema.Const(nil), schema.Int()),
	"optional":  schema.Bool(),
}, nil)

var formulaSchema = schema.FieldMap(
	schema.Fields{
		"ensemble":    schema.Const("formula"),
		"name":        schema.String(),
		"revision":    schema.Int(),
		"summary":     schema.String(),
		"description": schema.String(),
		"peers":       schema.Map(schema.String(), ifaceExpander(1)),
		"provides":    schema.Map(schema.String(), ifaceExpander(nil)),
		"requires":    schema.Map(schema.String(), ifaceExpander(1)),
	},
	schema.Optional{"provides", "requires", "peers"},
)