forked from juju/juju
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmodels.go
154 lines (138 loc) · 4.38 KB
/
models.go
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
// Copyright 2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package jujuclient
import (
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/juju/errors"
"github.com/juju/utils"
"gopkg.in/juju/names.v2"
"gopkg.in/yaml.v2"
"github.com/juju/juju/juju/osenv"
)
// JujuModelsPath is the location where models information is
// expected to be found.
func JujuModelsPath() string {
// TODO(axw) models.yaml should go into XDG_CACHE_HOME.
return osenv.JujuXDGDataHomePath("models.yaml")
}
// ReadModelsFile loads all models defined in a given file.
// If the file is not found, it is not an error.
func ReadModelsFile(file string) (map[string]*ControllerModels, error) {
data, err := ioutil.ReadFile(file)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
models, err := ParseModels(data)
if err != nil {
return nil, err
}
if err := migrateLocalModelUsers(models); err != nil {
return nil, err
}
return models, nil
}
// migrateLocalModelUsers strips any @local domains from any qualified model names.
func migrateLocalModelUsers(usermodels map[string]*ControllerModels) error {
changes := false
for _, modelDetails := range usermodels {
for name, model := range modelDetails.Models {
migratedName, changed, err := migrateModelName(name)
if err != nil {
return errors.Trace(err)
}
if !changed {
continue
}
delete(modelDetails.Models, name)
modelDetails.Models[migratedName] = model
changes = true
}
migratedName, changed, err := migrateModelName(modelDetails.CurrentModel)
if err != nil {
return errors.Trace(err)
}
if !changed {
continue
}
modelDetails.CurrentModel = migratedName
}
if changes {
return WriteModelsFile(usermodels)
}
return nil
}
func migrateModelName(legacyName string) (string, bool, error) {
i := strings.IndexRune(legacyName, '/')
if i < 0 {
return legacyName, false, nil
}
owner := legacyName[:i]
if !names.IsValidUser(owner) {
return "", false, errors.NotValidf("user name %q", owner)
}
if !strings.HasSuffix(owner, "@local") {
return legacyName, false, nil
}
rawModelName := legacyName[i+1:]
return JoinOwnerModelName(names.NewUserTag(owner), rawModelName), true, nil
}
// WriteModelsFile marshals to YAML details of the given models
// and writes it to the models file.
func WriteModelsFile(models map[string]*ControllerModels) error {
data, err := yaml.Marshal(modelsCollection{models})
if err != nil {
return errors.Annotate(err, "cannot marshal models")
}
return utils.AtomicWriteFile(JujuModelsPath(), data, os.FileMode(0600))
}
// ParseModels parses the given YAML bytes into models metadata.
func ParseModels(data []byte) (map[string]*ControllerModels, error) {
var result modelsCollection
err := yaml.Unmarshal(data, &result)
if err != nil {
return nil, errors.Annotate(err, "cannot unmarshal models")
}
return result.ControllerModels, nil
}
type modelsCollection struct {
ControllerModels map[string]*ControllerModels `yaml:"controllers"`
}
// ControllerModels stores per-controller account-model information.
type ControllerModels struct {
// Models is the collection of models for the account, indexed
// by model name. This should be treated as a cache only, and
// not the complete set of models for the account.
Models map[string]ModelDetails `yaml:"models,omitempty"`
// CurrentModel is the name of the active model for the account.
CurrentModel string `yaml:"current-model,omitempty"`
}
// JoinOwnerModelName returns a model name qualified with the model owner.
func JoinOwnerModelName(owner names.UserTag, modelName string) string {
return fmt.Sprintf("%s/%s", owner.Id(), modelName)
}
// IsQualifiedModelName returns true if the provided model name is qualified
// with an owner. The name is assumed to be either a valid qualified model
// name, or a valid unqualified model name.
func IsQualifiedModelName(name string) bool {
return strings.ContainsRune(name, '/')
}
// SplitModelName splits a qualified model name into the model and owner
// name components.
func SplitModelName(name string) (string, names.UserTag, error) {
i := strings.IndexRune(name, '/')
if i < 0 {
return "", names.UserTag{}, errors.NotValidf("unqualified model name %q", name)
}
owner := name[:i]
if !names.IsValidUser(owner) {
return "", names.UserTag{}, errors.NotValidf("user name %q", owner)
}
name = name[i+1:]
return name, names.NewUserTag(owner), nil
}