~axwalk/juju-core/lp1303195-manual-ubuntuuser-bash

1206.2.1 by Martin Packman
Add copyright statement at the top of all go files bar thirdparty
1
// Copyright 2011, 2012, 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
3
11 by Gustavo Niemeyer
Applied the juju/charm renaming to the Go code base.
4
package charm
2.1.4 by Gustavo Niemeyer
Ported formula id parsing and interface schema normalization.
5
6
import (
75.1.2 by root
formating files with go fmt
7
	"errors"
75.1.3 by root
Improve the code for Go port for charm metadata support subordinates
8
	"fmt"
75.1.2 by root
formating files with go fmt
9
	"io"
10
	"io/ioutil"
1408.1.1 by John Arbash Meinel
Start changing the imports of the middle level files.
11
	"strings"
12
75.1.2 by root
formating files with go fmt
13
	"launchpad.net/goyaml"
1408.1.1 by John Arbash Meinel
Start changing the imports of the middle level files.
14
894.2.2 by Dimiter Naydenov
Changes after review - renamed charm/hook/ to charm/hooks/
15
	"launchpad.net/juju-core/charm/hooks"
255 by Gustavo Niemeyer
launchpad.net/juju-core/juju => launchpad.net/juju-core
16
	"launchpad.net/juju-core/schema"
75.1.1 by root
Go port for charm metadata support for subordinates
17
)
18
1066.1.6 by Roger Peppe
charm: trivial changes for review
19
// RelationScope describes the scope of a relation.
330.1.1 by Roger Peppe
state: move RelationScope to charm
20
type RelationScope string
21
22
// Note that schema doesn't support custom string types,
23
// so when we use these values in a schema.Checker,
24
// we must store them as strings, not RelationScopes.
25
75.1.1 by root
Go port for charm metadata support for subordinates
26
const (
330.1.1 by Roger Peppe
state: move RelationScope to charm
27
	ScopeGlobal    RelationScope = "global"
28
	ScopeContainer RelationScope = "container"
2.1.4 by Gustavo Niemeyer
Ported formula id parsing and interface schema normalization.
29
)
30
1066.1.6 by Roger Peppe
charm: trivial changes for review
31
// RelationRole defines the role of a relation.
1066.1.2 by Roger Peppe
charm: add Role to Relation
32
type RelationRole string
33
34
const (
35
	RoleProvider RelationRole = "provider"
36
	RoleRequirer RelationRole = "requirer"
37
	RolePeer     RelationRole = "peer"
38
)
39
11 by Gustavo Niemeyer
Applied the juju/charm renaming to the Go code base.
40
// Relation represents a single relation defined in the charm
2.1.8 by Gustavo Niemeyer
Added support for pasing relations within the formula metadata.
41
// metadata.yaml file.
42
type Relation struct {
1066.1.4 by Roger Peppe
gofmt
43
	Name      string
44
	Role      RelationRole
75.1.2 by root
formating files with go fmt
45
	Interface string
46
	Optional  bool
47
	Limit     int
330.1.1 by Roger Peppe
state: move RelationScope to charm
48
	Scope     RelationScope
2.1.8 by Gustavo Niemeyer
Added support for pasing relations within the formula metadata.
49
}
50
1659.1.1 by Dimiter Naydenov
Moved IsImplicit() and ImplementedBy() from state/Endpoint to charm/Relation
51
// ImplementedBy returns whether the relation is implemented by the supplied charm.
52
func (r Relation) ImplementedBy(ch Charm) bool {
53
	if r.IsImplicit() {
54
		return true
55
	}
56
	var m map[string]Relation
57
	switch r.Role {
58
	case RoleProvider:
59
		m = ch.Meta().Provides
60
	case RoleRequirer:
61
		m = ch.Meta().Requires
62
	case RolePeer:
63
		m = ch.Meta().Peers
64
	default:
65
		panic(fmt.Errorf("unknown relation role %q", r.Role))
66
	}
67
	rel, found := m[r.Name]
68
	if !found {
69
		return false
70
	}
71
	if rel.Interface == r.Interface {
72
		switch r.Scope {
73
		case ScopeGlobal:
74
			return rel.Scope != ScopeContainer
75
		case ScopeContainer:
76
			return true
77
		default:
78
			panic(fmt.Errorf("unknown relation scope %q", r.Scope))
79
		}
80
	}
81
	return false
82
}
83
84
// IsImplicit returns whether the relation is supplied by juju itself,
85
// rather than by a charm.
86
func (r Relation) IsImplicit() bool {
87
	return (r.Name == "juju-info" &&
88
		r.Interface == "juju-info" &&
89
		r.Role == RoleProvider)
90
}
91
2.1.8 by Gustavo Niemeyer
Added support for pasing relations within the formula metadata.
92
// Meta represents all the known content that may be defined
11 by Gustavo Niemeyer
Applied the juju/charm renaming to the Go code base.
93
// within a charm's metadata.yaml file.
2.1.5 by Gustavo Niemeyer
Implemented initial metadata.yaml reading and parsing.
94
type Meta struct {
75.1.3 by root
Improve the code for Go port for charm metadata support subordinates
95
	Name        string
96
	Summary     string
97
	Description string
98
	Subordinate bool
569.1.1 by Gustavo Niemeyer
state: added add/get deep equivalence test
99
	Provides    map[string]Relation `bson:",omitempty"`
100
	Requires    map[string]Relation `bson:",omitempty"`
101
	Peers       map[string]Relation `bson:",omitempty"`
102
	Format      int                 `bson:",omitempty"`
103
	OldRevision int                 `bson:",omitempty"` // Obsolete
970.2.1 by Aaron Bentley
Add categories to charms.
104
	Categories  []string            `bson:",omitempty"`
2499.4.1 by Casey Marshall
Add 'series:' attribute to charm metadata.
105
	Series      string              `bson:",omitempty"`
2.1.5 by Gustavo Niemeyer
Implemented initial metadata.yaml reading and parsing.
106
}
107
894.2.2 by Dimiter Naydenov
Changes after review - renamed charm/hook/ to charm/hooks/
108
func generateRelationHooks(relName string, allHooks map[string]bool) {
894.2.3 by Dimiter Naydenov
Changes after review
109
	for _, hookName := range hooks.RelationHooks() {
894.2.1 by Dimiter Naydenov
Another try..
110
		allHooks[fmt.Sprintf("%s-%s", relName, hookName)] = true
111
	}
112
}
113
114
// Hooks returns a map of all possible valid hooks, taking relations
115
// into account. It's a map to enable fast lookups, and the value is
116
// always true.
117
func (m Meta) Hooks() map[string]bool {
118
	allHooks := make(map[string]bool)
119
	// Unit hooks
894.2.3 by Dimiter Naydenov
Changes after review
120
	for _, hookName := range hooks.UnitHooks() {
894.2.1 by Dimiter Naydenov
Another try..
121
		allHooks[string(hookName)] = true
122
	}
123
	// Relation hooks
894.2.2 by Dimiter Naydenov
Changes after review - renamed charm/hook/ to charm/hooks/
124
	for hookName := range m.Provides {
125
		generateRelationHooks(hookName, allHooks)
126
	}
127
	for hookName := range m.Requires {
128
		generateRelationHooks(hookName, allHooks)
129
	}
896.1.4 by Dimiter Naydenov
Changes after review
130
	for hookName := range m.Peers {
894.2.2 by Dimiter Naydenov
Changes after review - renamed charm/hook/ to charm/hooks/
131
		generateRelationHooks(hookName, allHooks)
894.2.1 by Dimiter Naydenov
Another try..
132
	}
133
	return allHooks
134
}
135
970.2.5 by Aaron Bentley
Reformat with gofmt.
136
func parseCategories(categories interface{}) []string {
137
	if categories == nil {
138
		return nil
970.2.1 by Aaron Bentley
Add categories to charms.
139
	}
140
	slice := categories.([]interface{})
141
	result := make([]string, 0, len(slice))
142
	for _, cat := range slice {
970.2.5 by Aaron Bentley
Reformat with gofmt.
143
		result = append(result, cat.(string))
970.2.1 by Aaron Bentley
Add categories to charms.
144
	}
145
	return result
146
}
147
2.1.17 by Gustavo Niemeyer
Unified formula.ParseMeta and ReadMeta.
148
// ReadMeta reads the content of a metadata.yaml file and returns
2.1.5 by Gustavo Niemeyer
Implemented initial metadata.yaml reading and parsing.
149
// its representation.
15 by Roger Peppe
fixes for new error interface
150
func ReadMeta(r io.Reader) (meta *Meta, err error) {
75.1.2 by root
formating files with go fmt
151
	data, err := ioutil.ReadAll(r)
152
	if err != nil {
153
		return
154
	}
155
	raw := make(map[interface{}]interface{})
156
	err = goyaml.Unmarshal(data, raw)
157
	if err != nil {
158
		return
159
	}
160
	v, err := charmSchema.Coerce(raw, nil)
161
	if err != nil {
162
		return nil, errors.New("metadata: " + err.Error())
163
	}
314.1.1 by Gustavo Niemeyer
schema: introduce StringMap; kill MapType and ListType
164
	m := v.(map[string]interface{})
75.1.2 by root
formating files with go fmt
165
	meta = &Meta{}
166
	meta.Name = m["name"].(string)
167
	// Schema decodes as int64, but the int range should be good
168
	// enough for revisions.
169
	meta.Summary = m["summary"].(string)
170
	meta.Description = m["description"].(string)
1066.1.2 by Roger Peppe
charm: add Role to Relation
171
	meta.Provides = parseRelations(m["provides"], RoleProvider)
172
	meta.Requires = parseRelations(m["requires"], RoleRequirer)
173
	meta.Peers = parseRelations(m["peers"], RolePeer)
506.3.1 by Dave Cheney
wip
174
	meta.Format = int(m["format"].(int64))
970.2.1 by Aaron Bentley
Add categories to charms.
175
	meta.Categories = parseCategories(m["categories"])
815.1.1 by William Reade
relax juju-* relation restrictions for require relations with container scope
176
	if subordinate := m["subordinate"]; subordinate != nil {
815.1.4 by William Reade
addres review
177
		meta.Subordinate = subordinate.(bool)
815.1.1 by William Reade
relax juju-* relation restrictions for require relations with container scope
178
	}
75.1.2 by root
formating files with go fmt
179
	if rev := m["revision"]; rev != nil {
180
		// Obsolete
181
		meta.OldRevision = int(m["revision"].(int64))
182
	}
2499.4.2 by Casey Marshall
Check for existence of 'series' key.
183
	if series, ok := m["series"]; ok && series != nil {
2499.4.1 by Casey Marshall
Add 'series:' attribute to charm metadata.
184
		meta.Series = series.(string)
185
	}
1066.1.1 by Roger Peppe
charm: add Name to Relation
186
	if err := meta.Check(); err != nil {
187
		return nil, err
188
	}
189
	return meta, nil
190
}
675.2.1 by William Reade
initial implementation
191
1066.1.1 by Roger Peppe
charm: add Name to Relation
192
// Check checks that the metadata is well-formed.
193
func (meta Meta) Check() error {
815.1.4 by William Reade
addres review
194
	// Check for duplicate or forbidden relation names or interfaces.
675.2.1 by William Reade
initial implementation
195
	names := map[string]bool{}
1066.1.2 by Roger Peppe
charm: add Role to Relation
196
	checkRelations := func(src map[string]Relation, role RelationRole) error {
815.1.1 by William Reade
relax juju-* relation restrictions for require relations with container scope
197
		for name, rel := range src {
1066.1.1 by Roger Peppe
charm: add Name to Relation
198
			if rel.Name != name {
199
				return fmt.Errorf("charm %q has mismatched relation name %q; expected %q", meta.Name, rel.Name, name)
200
			}
1066.1.2 by Roger Peppe
charm: add Role to Relation
201
			if rel.Role != role {
202
				return fmt.Errorf("charm %q has mismatched role %q; expected %q", meta.Name, rel.Role, role)
203
			}
815.1.1 by William Reade
relax juju-* relation restrictions for require relations with container scope
204
			// Container-scoped require relations on subordinates are allowed
205
			// to use the otherwise-reserved juju-* namespace.
1066.1.2 by Roger Peppe
charm: add Role to Relation
206
			if !meta.Subordinate || role != RoleRequirer || rel.Scope != ScopeContainer {
815.1.1 by William Reade
relax juju-* relation restrictions for require relations with container scope
207
				if reservedName(name) {
208
					return fmt.Errorf("charm %q using a reserved relation name: %q", meta.Name, name)
209
				}
210
			}
1066.1.2 by Roger Peppe
charm: add Role to Relation
211
			if role != RoleRequirer {
815.1.1 by William Reade
relax juju-* relation restrictions for require relations with container scope
212
				if reservedName(rel.Interface) {
213
					return fmt.Errorf("charm %q relation %q using a reserved interface: %q", meta.Name, name, rel.Interface)
214
				}
215
			}
216
			if names[name] {
217
				return fmt.Errorf("charm %q using a duplicated relation name: %q", meta.Name, name)
218
			}
219
			names[name] = true
220
		}
675.2.1 by William Reade
initial implementation
221
		return nil
222
	}
1066.1.2 by Roger Peppe
charm: add Role to Relation
223
	if err := checkRelations(meta.Provides, RoleProvider); err != nil {
224
		return err
225
	}
226
	if err := checkRelations(meta.Requires, RoleRequirer); err != nil {
227
		return err
228
	}
229
	if err := checkRelations(meta.Peers, RolePeer); err != nil {
1066.1.1 by Roger Peppe
charm: add Name to Relation
230
		return err
675.2.1 by William Reade
initial implementation
231
	}
232
75.1.3 by root
Improve the code for Go port for charm metadata support subordinates
233
	// Subordinate charms must have at least one relation that
234
	// has container scope, otherwise they can't relate to the
235
	// principal.
815.1.1 by William Reade
relax juju-* relation restrictions for require relations with container scope
236
	if meta.Subordinate {
75.1.3 by root
Improve the code for Go port for charm metadata support subordinates
237
		valid := false
75.1.2 by root
formating files with go fmt
238
		if meta.Requires != nil {
239
			for _, relationData := range meta.Requires {
240
				if relationData.Scope == ScopeContainer {
75.1.3 by root
Improve the code for Go port for charm metadata support subordinates
241
					valid = true
242
					break
75.1.2 by root
formating files with go fmt
243
				}
244
			}
245
		}
75.1.3 by root
Improve the code for Go port for charm metadata support subordinates
246
		if !valid {
1066.1.6 by Roger Peppe
charm: trivial changes for review
247
			return fmt.Errorf("subordinate charm %q lacks \"requires\" relation with container scope", meta.Name)
75.1.2 by root
formating files with go fmt
248
		}
249
	}
2499.4.1 by Casey Marshall
Add 'series:' attribute to charm metadata.
250
251
	if meta.Series != "" {
252
		if !IsValidSeries(meta.Series) {
253
			return fmt.Errorf("charm %q declares invalid series: %q", meta.Name, meta.Series)
254
		}
255
	}
256
1066.1.1 by Roger Peppe
charm: add Name to Relation
257
	return nil
2.1.5 by Gustavo Niemeyer
Implemented initial metadata.yaml reading and parsing.
258
}
2.1.4 by Gustavo Niemeyer
Ported formula id parsing and interface schema normalization.
259
675.2.3 by William Reade
address review
260
func reservedName(name string) bool {
675.2.1 by William Reade
initial implementation
261
	return name == "juju" || strings.HasPrefix(name, "juju-")
262
}
263
1066.1.2 by Roger Peppe
charm: add Role to Relation
264
func parseRelations(relations interface{}, role RelationRole) map[string]Relation {
75.1.2 by root
formating files with go fmt
265
	if relations == nil {
266
		return nil
267
	}
268
	result := make(map[string]Relation)
314.1.1 by Gustavo Niemeyer
schema: introduce StringMap; kill MapType and ListType
269
	for name, rel := range relations.(map[string]interface{}) {
270
		relMap := rel.(map[string]interface{})
1066.1.1 by Roger Peppe
charm: add Name to Relation
271
		relation := Relation{
1066.1.4 by Roger Peppe
gofmt
272
			Name:      name,
273
			Role:      role,
1066.1.1 by Roger Peppe
charm: add Name to Relation
274
			Interface: relMap["interface"].(string),
1066.1.4 by Roger Peppe
gofmt
275
			Optional:  relMap["optional"].(bool),
1066.1.1 by Roger Peppe
charm: add Name to Relation
276
		}
75.1.2 by root
formating files with go fmt
277
		if scope := relMap["scope"]; scope != nil {
330.1.1 by Roger Peppe
state: move RelationScope to charm
278
			relation.Scope = RelationScope(scope.(string))
75.1.2 by root
formating files with go fmt
279
		}
280
		if relMap["limit"] != nil {
281
			// Schema defaults to int64, but we know
282
			// the int range should be more than enough.
283
			relation.Limit = int(relMap["limit"].(int64))
284
		}
157.1.1 by Gustavo Niemeyer
Introduce StringMapType in schema package to make things simpler.
285
		result[name] = relation
75.1.2 by root
formating files with go fmt
286
	}
287
	return result
2.1.8 by Gustavo Niemeyer
Added support for pasing relations within the formula metadata.
288
}
289
2.1.4 by Gustavo Niemeyer
Ported formula id parsing and interface schema normalization.
290
// Schema coercer that expands the interface shorthand notation.
291
// A consistent format is easier to work with than considering the
292
// potential difference everywhere.
293
//
294
// Supports the following variants::
295
//
296
//   provides:
297
//     server: riak
298
//     admin: http
299
//     foobar:
300
//       interface: blah
301
//
302
//   provides:
303
//     server:
304
//       interface: mysql
305
//       limit:
306
//       optional: false
307
//
308
// In all input cases, the output is the fully specified interface
309
// representation as seen in the mysql interface description above.
310
func ifaceExpander(limit interface{}) schema.Checker {
75.1.2 by root
formating files with go fmt
311
	return ifaceExpC{limit}
2.1.4 by Gustavo Niemeyer
Ported formula id parsing and interface schema normalization.
312
}
313
314
type ifaceExpC struct {
75.1.2 by root
formating files with go fmt
315
	limit interface{}
2.1.4 by Gustavo Niemeyer
Ported formula id parsing and interface schema normalization.
316
}
317
318
var (
75.1.2 by root
formating files with go fmt
319
	stringC = schema.String()
314.1.1 by Gustavo Niemeyer
schema: introduce StringMap; kill MapType and ListType
320
	mapC    = schema.StringMap(schema.Any())
2.1.4 by Gustavo Niemeyer
Ported formula id parsing and interface schema normalization.
321
)
322
15 by Roger Peppe
fixes for new error interface
323
func (c ifaceExpC) Coerce(v interface{}, path []string) (newv interface{}, err error) {
75.1.2 by root
formating files with go fmt
324
	s, err := stringC.Coerce(v, path)
325
	if err == nil {
314.1.1 by Gustavo Niemeyer
schema: introduce StringMap; kill MapType and ListType
326
		newv = map[string]interface{}{
75.1.2 by root
formating files with go fmt
327
			"interface": s,
328
			"limit":     c.limit,
329
			"optional":  false,
330.1.1 by Roger Peppe
state: move RelationScope to charm
330
			"scope":     string(ScopeGlobal),
75.1.2 by root
formating files with go fmt
331
		}
332
		return
333
	}
2.1.4 by Gustavo Niemeyer
Ported formula id parsing and interface schema normalization.
334
75.1.2 by root
formating files with go fmt
335
	v, err = mapC.Coerce(v, path)
336
	if err != nil {
337
		return
338
	}
314.1.1 by Gustavo Niemeyer
schema: introduce StringMap; kill MapType and ListType
339
	m := v.(map[string]interface{})
75.1.2 by root
formating files with go fmt
340
	if _, ok := m["limit"]; !ok {
341
		m["limit"] = c.limit
342
	}
343
	return ifaceSchema.Coerce(m, path)
2.1.4 by Gustavo Niemeyer
Ported formula id parsing and interface schema normalization.
344
}
2.1.5 by Gustavo Niemeyer
Implemented initial metadata.yaml reading and parsing.
345
75.1.1 by root
Go port for charm metadata support for subordinates
346
var ifaceSchema = schema.FieldMap(
75.1.2 by root
formating files with go fmt
347
	schema.Fields{
348
		"interface": schema.String(),
349
		"limit":     schema.OneOf(schema.Const(nil), schema.Int()),
330.1.1 by Roger Peppe
state: move RelationScope to charm
350
		"scope":     schema.OneOf(schema.Const(string(ScopeGlobal)), schema.Const(string(ScopeContainer))),
75.1.2 by root
formating files with go fmt
351
		"optional":  schema.Bool(),
352
	},
314.1.2 by Gustavo Niemeyer
schema: support defaults in FieldMap
353
	schema.Defaults{
330.1.1 by Roger Peppe
state: move RelationScope to charm
354
		"scope":    string(ScopeGlobal),
314.1.2 by Gustavo Niemeyer
schema: support defaults in FieldMap
355
		"optional": false,
356
	},
75.1.1 by root
Go port for charm metadata support for subordinates
357
)
2.1.5 by Gustavo Niemeyer
Implemented initial metadata.yaml reading and parsing.
358
11 by Gustavo Niemeyer
Applied the juju/charm renaming to the Go code base.
359
var charmSchema = schema.FieldMap(
75.1.2 by root
formating files with go fmt
360
	schema.Fields{
361
		"name":        schema.String(),
362
		"summary":     schema.String(),
363
		"description": schema.String(),
314.1.1 by Gustavo Niemeyer
schema: introduce StringMap; kill MapType and ListType
364
		"peers":       schema.StringMap(ifaceExpander(int64(1))),
157.1.1 by Gustavo Niemeyer
Introduce StringMapType in schema package to make things simpler.
365
		"provides":    schema.StringMap(ifaceExpander(nil)),
314.1.1 by Gustavo Niemeyer
schema: introduce StringMap; kill MapType and ListType
366
		"requires":    schema.StringMap(ifaceExpander(int64(1))),
75.1.2 by root
formating files with go fmt
367
		"revision":    schema.Int(), // Obsolete
506.3.1 by Dave Cheney
wip
368
		"format":      schema.Int(),
75.1.2 by root
formating files with go fmt
369
		"subordinate": schema.Bool(),
970.2.1 by Aaron Bentley
Add categories to charms.
370
		"categories":  schema.List(schema.String()),
2499.4.1 by Casey Marshall
Add 'series:' attribute to charm metadata.
371
		"series":      schema.String(),
75.1.2 by root
formating files with go fmt
372
	},
314.1.2 by Gustavo Niemeyer
schema: support defaults in FieldMap
373
	schema.Defaults{
374
		"provides":    schema.Omit,
375
		"requires":    schema.Omit,
376
		"peers":       schema.Omit,
377
		"revision":    schema.Omit,
506.3.1 by Dave Cheney
wip
378
		"format":      1,
314.1.2 by Gustavo Niemeyer
schema: support defaults in FieldMap
379
		"subordinate": schema.Omit,
970.2.5 by Aaron Bentley
Reformat with gofmt.
380
		"categories":  schema.Omit,
2499.4.1 by Casey Marshall
Add 'series:' attribute to charm metadata.
381
		"series":      schema.Omit,
314.1.2 by Gustavo Niemeyer
schema: support defaults in FieldMap
382
	},
2.1.5 by Gustavo Niemeyer
Implemented initial metadata.yaml reading and parsing.
383
)