~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/jujuclient/models.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 2016 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package jujuclient
 
5
 
 
6
import (
 
7
        "fmt"
 
8
        "io/ioutil"
 
9
        "os"
 
10
        "strings"
 
11
 
 
12
        "github.com/juju/errors"
 
13
        "github.com/juju/utils"
 
14
        "gopkg.in/juju/names.v2"
 
15
        "gopkg.in/yaml.v2"
 
16
 
 
17
        "github.com/juju/juju/juju/osenv"
 
18
)
 
19
 
 
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")
 
25
}
 
26
 
 
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)
 
31
        if err != nil {
 
32
                if os.IsNotExist(err) {
 
33
                        return nil, nil
 
34
                }
 
35
                return nil, err
 
36
        }
 
37
        if err := migrateLegacyModels(data); err != nil {
 
38
                return nil, err
 
39
        }
 
40
        models, err := ParseModels(data)
 
41
        if err != nil {
 
42
                return nil, err
 
43
        }
 
44
        return models, nil
 
45
}
 
46
 
 
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})
 
51
        if err != nil {
 
52
                return errors.Annotate(err, "cannot marshal models")
 
53
        }
 
54
        return utils.AtomicWriteFile(JujuModelsPath(), data, os.FileMode(0600))
 
55
}
 
56
 
 
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)
 
61
        if err != nil {
 
62
                return nil, errors.Annotate(err, "cannot unmarshal models")
 
63
        }
 
64
        return result.ControllerModels, nil
 
65
}
 
66
 
 
67
type modelsCollection struct {
 
68
        ControllerModels map[string]*ControllerModels `yaml:"controllers"`
 
69
}
 
70
 
 
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"`
 
77
 
 
78
        // CurrentModel is the name of the active model for the account.
 
79
        CurrentModel string `yaml:"current-model,omitempty"`
 
80
}
 
81
 
 
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())
 
86
        if err != nil {
 
87
                return err
 
88
        }
 
89
 
 
90
        type legacyAccountModels struct {
 
91
                Models       map[string]ModelDetails `yaml:"models"`
 
92
                CurrentModel string                  `yaml:"current-model,omitempty"`
 
93
        }
 
94
        type legacyControllerAccountModels struct {
 
95
                AccountModels map[string]*legacyAccountModels `yaml:"accounts"`
 
96
        }
 
97
        type legacyModelsCollection struct {
 
98
                ControllerAccountModels map[string]legacyControllerAccountModels `yaml:"controllers"`
 
99
        }
 
100
 
 
101
        var legacy legacyModelsCollection
 
102
        if err := yaml.Unmarshal(data, &legacy); err != nil {
 
103
                return errors.Annotate(err, "cannot unmarshal models")
 
104
        }
 
105
        result := make(map[string]*ControllerModels)
 
106
        for controller, controllerAccountModels := range legacy.ControllerAccountModels {
 
107
                accountDetails, ok := accounts[controller]
 
108
                if !ok {
 
109
                        continue
 
110
                }
 
111
                accountModels, ok := controllerAccountModels.AccountModels[accountDetails.User]
 
112
                if !ok {
 
113
                        continue
 
114
                }
 
115
                result[controller] = &ControllerModels{
 
116
                        accountModels.Models,
 
117
                        accountModels.CurrentModel,
 
118
                }
 
119
        }
 
120
        if len(result) > 0 {
 
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)
 
125
        }
 
126
        return nil
 
127
}
 
128
 
 
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)
 
132
}
 
133
 
 
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, '/')
 
139
}
 
140
 
 
141
// SplitModelName splits a qualified model name into the model and owner
 
142
// name components.
 
143
func SplitModelName(name string) (string, names.UserTag, error) {
 
144
        i := strings.IndexRune(name, '/')
 
145
        if i < 0 {
 
146
                return "", names.UserTag{}, errors.NotValidf("unqualified model name %q", name)
 
147
        }
 
148
        owner := name[:i]
 
149
        if !names.IsValidUser(owner) {
 
150
                return "", names.UserTag{}, errors.NotValidf("user name %q", owner)
 
151
        }
 
152
        name = name[i+1:]
 
153
        return name, names.NewUserTag(owner), nil
 
154
}