~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/bundlechanges/changes.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2015 Canonical Ltd.
 
2
// Licensed under the LGPLv3, see LICENCE file for details.
 
3
 
 
4
package bundlechanges
 
5
 
 
6
import (
 
7
        "fmt"
 
8
 
 
9
        "gopkg.in/juju/charm.v6-unstable"
 
10
)
 
11
 
 
12
// FromData generates and returns the list of changes required to deploy the
 
13
// given bundle data. The changes are sorted by requirements, so that they can
 
14
// be applied in order. The bundle data is assumed to be already verified.
 
15
func FromData(data *charm.BundleData) []Change {
 
16
        cs := &changeset{}
 
17
        addedApplications := handleApplications(cs.add, data.Applications, data.Series)
 
18
        addedMachines := handleMachines(cs.add, data.Machines, data.Series)
 
19
        handleRelations(cs.add, data.Relations, addedApplications)
 
20
        handleUnits(cs.add, data.Applications, addedApplications, addedMachines, data.Series)
 
21
        return cs.sorted()
 
22
}
 
23
 
 
24
// Change holds a single change required to deploy a bundle.
 
25
type Change interface {
 
26
        // Id returns the unique identifier for this change.
 
27
        Id() string
 
28
        // Requires returns the ids of all the changes that must
 
29
        // be applied before this one.
 
30
        Requires() []string
 
31
        // Method returns the action to be performed to apply this change.
 
32
        Method() string
 
33
        // GUIArgs returns positional arguments to pass to the method, suitable for
 
34
        // being JSON-serialized and sent to the Juju GUI.
 
35
        GUIArgs() []interface{}
 
36
        // setId is used to set the identifier for the change.
 
37
        setId(string)
 
38
}
 
39
 
 
40
// changeInfo holds information on a change, suitable for embedding into a more
 
41
// specific change type.
 
42
type changeInfo struct {
 
43
        id       string
 
44
        requires []string
 
45
        method   string
 
46
}
 
47
 
 
48
// Id implements Change.Id.
 
49
func (ch *changeInfo) Id() string {
 
50
        return ch.id
 
51
}
 
52
 
 
53
// Requires implements Change.Requires.
 
54
func (ch *changeInfo) Requires() []string {
 
55
        // Avoid returning a nil interface because so that avoid returning a slice
 
56
        // that will serialize to JSON null.
 
57
        if ch.requires == nil {
 
58
                return []string{}
 
59
        }
 
60
        return ch.requires
 
61
}
 
62
 
 
63
// Method implements Change.Method.
 
64
func (ch *changeInfo) Method() string {
 
65
        return ch.method
 
66
}
 
67
 
 
68
// setId implements Change.setId.
 
69
func (ch *changeInfo) setId(id string) {
 
70
        ch.id = id
 
71
}
 
72
 
 
73
// newAddCharmChange creates a new change for adding a charm.
 
74
func newAddCharmChange(params AddCharmParams, requires ...string) *AddCharmChange {
 
75
        return &AddCharmChange{
 
76
                changeInfo: changeInfo{
 
77
                        requires: requires,
 
78
                        method:   "addCharm",
 
79
                },
 
80
                Params: params,
 
81
        }
 
82
}
 
83
 
 
84
// AddCharmChange holds a change for adding a charm to the environment.
 
85
type AddCharmChange struct {
 
86
        changeInfo
 
87
        // Params holds parameters for adding a charm.
 
88
        Params AddCharmParams
 
89
}
 
90
 
 
91
// GUIArgs implements Change.GUIArgs.
 
92
func (ch *AddCharmChange) GUIArgs() []interface{} {
 
93
        return []interface{}{ch.Params.Charm, ch.Params.Series}
 
94
}
 
95
 
 
96
// AddCharmParams holds parameters for adding a charm to the environment.
 
97
type AddCharmParams struct {
 
98
        // Charm holds the URL of the charm to be added.
 
99
        Charm string
 
100
        // Series holds the series of the charm to be added
 
101
        // if the charm default is not sufficient.
 
102
        Series string
 
103
}
 
104
 
 
105
// newAddMachineChange creates a new change for adding a machine or container.
 
106
func newAddMachineChange(params AddMachineParams, requires ...string) *AddMachineChange {
 
107
        return &AddMachineChange{
 
108
                changeInfo: changeInfo{
 
109
                        requires: requires,
 
110
                        method:   "addMachines",
 
111
                },
 
112
                Params: params,
 
113
        }
 
114
}
 
115
 
 
116
// AddMachineChange holds a change for adding a machine or container.
 
117
type AddMachineChange struct {
 
118
        changeInfo
 
119
        // Params holds parameters for adding a machine.
 
120
        Params AddMachineParams
 
121
}
 
122
 
 
123
// GUIArgs implements Change.GUIArgs.
 
124
func (ch *AddMachineChange) GUIArgs() []interface{} {
 
125
        options := AddMachineOptions{
 
126
                Series:        ch.Params.Series,
 
127
                Constraints:   ch.Params.Constraints,
 
128
                ContainerType: ch.Params.ContainerType,
 
129
                ParentId:      ch.Params.ParentId,
 
130
        }
 
131
        return []interface{}{options}
 
132
}
 
133
 
 
134
// AddMachineOptions holds GUI options for adding a machine or container.
 
135
type AddMachineOptions struct {
 
136
        // Series holds the machine OS series.
 
137
        Series string `json:"series,omitempty"`
 
138
        // Constraints holds the machine constraints.
 
139
        Constraints string `json:"constraints,omitempty"`
 
140
        // ContainerType holds the machine container type (like "lxc" or "kvm").
 
141
        ContainerType string `json:"containerType,omitempty"`
 
142
        // ParentId holds the id of the parent machine.
 
143
        ParentId string `json:"parentId,omitempty"`
 
144
}
 
145
 
 
146
// AddMachineParams holds parameters for adding a machine or container.
 
147
type AddMachineParams struct {
 
148
        // Series holds the optional machine OS series.
 
149
        Series string
 
150
        // Constraints holds the optional machine constraints.
 
151
        Constraints string
 
152
        // ContainerType optionally holds the type of the container (for instance
 
153
        // ""lxc" or kvm"). It is not specified for top level machines.
 
154
        ContainerType string
 
155
        // ParentId optionally holds a placeholder pointing to another machine
 
156
        // change or to a unit change. This value is only specified in the case
 
157
        // this machine is a container, in which case also ContainerType is set.
 
158
        ParentId string
 
159
}
 
160
 
 
161
// newAddRelationChange creates a new change for adding a relation.
 
162
func newAddRelationChange(params AddRelationParams, requires ...string) *AddRelationChange {
 
163
        return &AddRelationChange{
 
164
                changeInfo: changeInfo{
 
165
                        requires: requires,
 
166
                        method:   "addRelation",
 
167
                },
 
168
                Params: params,
 
169
        }
 
170
}
 
171
 
 
172
// AddRelationChange holds a change for adding a relation between two applications.
 
173
type AddRelationChange struct {
 
174
        changeInfo
 
175
        // Params holds parameters for adding a relation.
 
176
        Params AddRelationParams
 
177
}
 
178
 
 
179
// GUIArgs implements Change.GUIArgs.
 
180
func (ch *AddRelationChange) GUIArgs() []interface{} {
 
181
        return []interface{}{ch.Params.Endpoint1, ch.Params.Endpoint2}
 
182
}
 
183
 
 
184
// AddRelationParams holds parameters for adding a relation between two applications.
 
185
type AddRelationParams struct {
 
186
        // Endpoint1 and Endpoint2 hold relation endpoints in the
 
187
        // "application:interface" form, where the application is always a placeholder
 
188
        // pointing to an application change, and the interface is optional. Examples
 
189
        // are "$deploy-42:web" or just "$deploy-42".
 
190
        Endpoint1 string
 
191
        Endpoint2 string
 
192
}
 
193
 
 
194
// newAddApplicationChange creates a new change for adding an application.
 
195
func newAddApplicationChange(params AddApplicationParams, requires ...string) *AddApplicationChange {
 
196
        return &AddApplicationChange{
 
197
                changeInfo: changeInfo{
 
198
                        requires: requires,
 
199
                        method:   "deploy",
 
200
                },
 
201
                Params: params,
 
202
        }
 
203
}
 
204
 
 
205
// AddApplicationChange holds a change for deploying a Juju application.
 
206
type AddApplicationChange struct {
 
207
        changeInfo
 
208
        // Params holds parameters for adding an application.
 
209
        Params AddApplicationParams
 
210
}
 
211
 
 
212
// GUIArgs implements Change.GUIArgs.
 
213
func (ch *AddApplicationChange) GUIArgs() []interface{} {
 
214
        options := ch.Params.Options
 
215
        if options == nil {
 
216
                options = make(map[string]interface{}, 0)
 
217
        }
 
218
        storage := ch.Params.Storage
 
219
        if storage == nil {
 
220
                storage = make(map[string]string, 0)
 
221
        }
 
222
        endpointBindings := ch.Params.EndpointBindings
 
223
        if endpointBindings == nil {
 
224
                endpointBindings = make(map[string]string, 0)
 
225
        }
 
226
        resources := ch.Params.Resources
 
227
        if resources == nil {
 
228
                resources = make(map[string]int, 0)
 
229
        }
 
230
        return []interface{}{
 
231
                ch.Params.Charm,
 
232
                ch.Params.Series,
 
233
                ch.Params.Application,
 
234
                options,
 
235
                ch.Params.Constraints,
 
236
                storage,
 
237
                endpointBindings,
 
238
                resources,
 
239
        }
 
240
}
 
241
 
 
242
// AddApplicationParams holds parameters for deploying a Juju application.
 
243
type AddApplicationParams struct {
 
244
        // Charm holds the URL of the charm to be used to deploy this application.
 
245
        Charm string
 
246
        // Series holds the series of the application to be deployed
 
247
        // if the charm default is not sufficient.
 
248
        Series string
 
249
        // Application holds the application name.
 
250
        Application string
 
251
        // Options holds application options.
 
252
        Options map[string]interface{}
 
253
        // Constraints holds the optional application constraints.
 
254
        Constraints string
 
255
        // Storage holds the optional storage constraints.
 
256
        Storage map[string]string
 
257
        // EndpointBindings holds the optional endpoint bindings
 
258
        EndpointBindings map[string]string
 
259
        // Resources identifies the revision to use for each resource
 
260
        // of the application's charm.
 
261
        Resources map[string]int
 
262
}
 
263
 
 
264
// newAddUnitChange creates a new change for adding an application unit.
 
265
func newAddUnitChange(params AddUnitParams, requires ...string) *AddUnitChange {
 
266
        return &AddUnitChange{
 
267
                changeInfo: changeInfo{
 
268
                        requires: requires,
 
269
                        method:   "addUnit",
 
270
                },
 
271
                Params: params,
 
272
        }
 
273
}
 
274
 
 
275
// AddUnitChange holds a change for adding an application unit.
 
276
type AddUnitChange struct {
 
277
        changeInfo
 
278
        // Params holds parameters for adding a unit.
 
279
        Params AddUnitParams
 
280
}
 
281
 
 
282
// GUIArgs implements Change.GUIArgs.
 
283
func (ch *AddUnitChange) GUIArgs() []interface{} {
 
284
        args := []interface{}{ch.Params.Application, nil}
 
285
        if ch.Params.To != "" {
 
286
                args[1] = ch.Params.To
 
287
        }
 
288
        return args
 
289
}
 
290
 
 
291
// AddUnitParams holds parameters for adding an application unit.
 
292
type AddUnitParams struct {
 
293
        // Application holds the application placeholder name for which a unit is added.
 
294
        Application string
 
295
        // To holds the optional location where to add the unit, as a placeholder
 
296
        // pointing to another unit change or to a machine change.
 
297
        To string
 
298
}
 
299
 
 
300
// newExposeChange creates a new change for exposing an application.
 
301
func newExposeChange(params ExposeParams, requires ...string) *ExposeChange {
 
302
        return &ExposeChange{
 
303
                changeInfo: changeInfo{
 
304
                        requires: requires,
 
305
                        method:   "expose",
 
306
                },
 
307
                Params: params,
 
308
        }
 
309
}
 
310
 
 
311
// ExposeChange holds a change for exposing an application.
 
312
type ExposeChange struct {
 
313
        changeInfo
 
314
        // Params holds parameters for exposing an application.
 
315
        Params ExposeParams
 
316
}
 
317
 
 
318
// GUIArgs implements Change.GUIArgs.
 
319
func (ch *ExposeChange) GUIArgs() []interface{} {
 
320
        return []interface{}{ch.Params.Application}
 
321
}
 
322
 
 
323
// ExposeParams holds parameters for exposing an application.
 
324
type ExposeParams struct {
 
325
        // Application holds the placeholder name of the application that must be exposed.
 
326
        Application string
 
327
}
 
328
 
 
329
// newSetAnnotationsChange creates a new change for setting annotations.
 
330
func newSetAnnotationsChange(params SetAnnotationsParams, requires ...string) *SetAnnotationsChange {
 
331
        return &SetAnnotationsChange{
 
332
                changeInfo: changeInfo{
 
333
                        requires: requires,
 
334
                        method:   "setAnnotations",
 
335
                },
 
336
                Params: params,
 
337
        }
 
338
}
 
339
 
 
340
// SetAnnotationsChange holds a change for setting application and machine
 
341
// annotations.
 
342
type SetAnnotationsChange struct {
 
343
        changeInfo
 
344
        // Params holds parameters for setting annotations.
 
345
        Params SetAnnotationsParams
 
346
}
 
347
 
 
348
// GUIArgs implements Change.GUIArgs.
 
349
func (ch *SetAnnotationsChange) GUIArgs() []interface{} {
 
350
        return []interface{}{ch.Params.Id, string(ch.Params.EntityType), ch.Params.Annotations}
 
351
}
 
352
 
 
353
// EntityType holds entity types ("application" or "machine").
 
354
type EntityType string
 
355
 
 
356
const (
 
357
        ApplicationType EntityType = "application"
 
358
        MachineType     EntityType = "machine"
 
359
)
 
360
 
 
361
// SetAnnotationsParams holds parameters for setting annotations.
 
362
type SetAnnotationsParams struct {
 
363
        // Id is the placeholder for the application or machine change corresponding to
 
364
        // the entity to be annotated.
 
365
        Id string
 
366
        // EntityType holds the type of the entity, "application" or "machine".
 
367
        EntityType EntityType
 
368
        // Annotations holds the annotations as key/value pairs.
 
369
        Annotations map[string]string
 
370
}
 
371
 
 
372
// changeset holds the list of changes returned by FromData.
 
373
type changeset struct {
 
374
        changes []Change
 
375
}
 
376
 
 
377
// add adds the given change to this change set.
 
378
func (cs *changeset) add(change Change) {
 
379
        change.setId(fmt.Sprintf("%s-%d", change.Method(), len(cs.changes)))
 
380
        cs.changes = append(cs.changes, change)
 
381
}
 
382
 
 
383
// sorted returns the changes sorted by requirements, required first.
 
384
func (cs *changeset) sorted() []Change {
 
385
        numChanges := len(cs.changes)
 
386
        records := make(map[string]bool, numChanges)
 
387
        sorted := make([]Change, 0, numChanges)
 
388
        changes := make([]Change, numChanges, numChanges*2)
 
389
        copy(changes, cs.changes)
 
390
mainloop:
 
391
        for len(changes) != 0 {
 
392
                // Note that all valid bundles have at least two changes
 
393
                // (add one charm and deploy one application).
 
394
                change := changes[0]
 
395
                changes = changes[1:]
 
396
                for _, r := range change.Requires() {
 
397
                        if !records[r] {
 
398
                                // This change requires a change which is not yet listed.
 
399
                                // Push this change at the end of the list and retry later.
 
400
                                changes = append(changes, change)
 
401
                                continue mainloop
 
402
                        }
 
403
                }
 
404
                records[change.Id()] = true
 
405
                sorted = append(sorted, change)
 
406
        }
 
407
        return sorted
 
408
}