1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
12
"github.com/juju/errors"
13
"github.com/juju/utils"
14
"gopkg.in/juju/names.v2"
17
"github.com/juju/juju/juju/osenv"
20
// JujuModelsPath is the location where models information is
21
// expected to be found.
22
func JujuModelsPath() string {
23
// TODO(axw) models.yaml should go into XDG_CACHE_HOME.
24
return osenv.JujuXDGDataHomePath("models.yaml")
27
// ReadModelsFile loads all models defined in a given file.
28
// If the file is not found, it is not an error.
29
func ReadModelsFile(file string) (map[string]*ControllerModels, error) {
30
data, err := ioutil.ReadFile(file)
32
if os.IsNotExist(err) {
37
if err := migrateLegacyModels(data); err != nil {
40
models, err := ParseModels(data)
47
// WriteModelsFile marshals to YAML details of the given models
48
// and writes it to the models file.
49
func WriteModelsFile(models map[string]*ControllerModels) error {
50
data, err := yaml.Marshal(modelsCollection{models})
52
return errors.Annotate(err, "cannot marshal models")
54
return utils.AtomicWriteFile(JujuModelsPath(), data, os.FileMode(0600))
57
// ParseModels parses the given YAML bytes into models metadata.
58
func ParseModels(data []byte) (map[string]*ControllerModels, error) {
59
var result modelsCollection
60
err := yaml.Unmarshal(data, &result)
62
return nil, errors.Annotate(err, "cannot unmarshal models")
64
return result.ControllerModels, nil
67
type modelsCollection struct {
68
ControllerModels map[string]*ControllerModels `yaml:"controllers"`
71
// ControllerModels stores per-controller account-model information.
72
type ControllerModels struct {
73
// Models is the collection of models for the account, indexed
74
// by model name. This should be treated as a cache only, and
75
// not the complete set of models for the account.
76
Models map[string]ModelDetails `yaml:"models,omitempty"`
78
// CurrentModel is the name of the active model for the account.
79
CurrentModel string `yaml:"current-model,omitempty"`
82
// TODO(axw) 2016-07-14 #1603841
83
// Drop this code once we get to 2.0.
84
func migrateLegacyModels(data []byte) error {
85
accounts, err := ReadAccountsFile(JujuAccountsPath())
90
type legacyAccountModels struct {
91
Models map[string]ModelDetails `yaml:"models"`
92
CurrentModel string `yaml:"current-model,omitempty"`
94
type legacyControllerAccountModels struct {
95
AccountModels map[string]*legacyAccountModels `yaml:"accounts"`
97
type legacyModelsCollection struct {
98
ControllerAccountModels map[string]legacyControllerAccountModels `yaml:"controllers"`
101
var legacy legacyModelsCollection
102
if err := yaml.Unmarshal(data, &legacy); err != nil {
103
return errors.Annotate(err, "cannot unmarshal models")
105
result := make(map[string]*ControllerModels)
106
for controller, controllerAccountModels := range legacy.ControllerAccountModels {
107
accountDetails, ok := accounts[controller]
111
accountModels, ok := controllerAccountModels.AccountModels[accountDetails.User]
115
result[controller] = &ControllerModels{
116
accountModels.Models,
117
accountModels.CurrentModel,
121
// Only write if we found at least one,
122
// which means the file was in legacy
123
// format. Otherwise leave it alone.
124
return WriteModelsFile(result)
129
// JoinOwnerModelName returns a model name qualified with the model owner.
130
func JoinOwnerModelName(owner names.UserTag, modelName string) string {
131
return fmt.Sprintf("%s/%s", owner.Canonical(), modelName)
134
// IsQualifiedModelName returns true if the provided model name is qualified
135
// with an owner. The name is assumed to be either a valid qualified model
136
// name, or a valid unqualified model name.
137
func IsQualifiedModelName(name string) bool {
138
return strings.ContainsRune(name, '/')
141
// SplitModelName splits a qualified model name into the model and owner
143
func SplitModelName(name string) (string, names.UserTag, error) {
144
i := strings.IndexRune(name, '/')
146
return "", names.UserTag{}, errors.NotValidf("unqualified model name %q", name)
149
if !names.IsValidUser(owner) {
150
return "", names.UserTag{}, errors.NotValidf("user name %q", owner)
153
return name, names.NewUserTag(owner), nil