github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/controller/modelmanager/createmodel.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // Package modelmanager provides the business logic for 5 // model management operations in the controller. 6 package modelmanager 7 8 import ( 9 "fmt" 10 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 "github.com/juju/utils" 14 "github.com/juju/version" 15 16 "github.com/juju/juju/environs" 17 "github.com/juju/juju/environs/config" 18 "github.com/juju/juju/tools" 19 ) 20 21 var ( 22 logger = loggo.GetLogger("juju.controller.modelmanager") 23 24 configValuesFromController = []string{ 25 "type", 26 config.CACertKey, 27 "state-port", 28 "api-port", 29 config.ControllerUUIDKey, 30 } 31 ) 32 33 const ( 34 // IsAdmin is used when generating a model config for an admin user. 35 IsAdmin = true 36 37 // IsNotAdmin is used when generating a model config for a non admin user. 38 IsNotAdmin = false 39 ) 40 41 // ModelConfigCreator provides a method of creating a new model config. 42 // 43 // The zero value of ModelConfigCreator is usable with the limitations 44 // noted on each struct field. 45 type ModelConfigCreator struct { 46 // FindTools, if non-nil, will be used to validate the agent-version 47 // value in NewModelConfig if it differs from the base configuration. 48 // 49 // If FindTools is nil, agent-version may not be different to the 50 // base configuration. 51 FindTools func(version.Number) (tools.List, error) 52 } 53 54 // NewModelConfig returns a new model config given a base (controller) config 55 // and a set of attributes that will be specific to the new model, overriding 56 // any non-restricted attributes in the base configuration. The resulting 57 // config will be suitable for creating a new model in state. 58 // 59 // If "attrs" does not include a UUID, a new, random one will be generated 60 // and added to the config. 61 // 62 // The config will be validated with the provider before being returned. 63 func (c ModelConfigCreator) NewModelConfig( 64 isAdmin bool, 65 base *config.Config, 66 attrs map[string]interface{}, 67 ) (*config.Config, error) { 68 69 if err := c.checkVersion(base, attrs); err != nil { 70 return nil, errors.Trace(err) 71 } 72 73 // Before comparing any values, we need to push the config through 74 // the provider validation code. One of the reasons for this is that 75 // numbers being serialized through JSON get turned into float64. The 76 // schema code used in config will convert these back into integers. 77 // However, before we can create a valid config, we need to make sure 78 // we copy across fields from the main config that aren't there. 79 baseAttrs := base.AllAttrs() 80 restrictedFields, err := RestrictedProviderFields(base.Type()) 81 if err != nil { 82 return nil, errors.Trace(err) 83 } 84 for _, field := range restrictedFields { 85 if _, ok := attrs[field]; !ok { 86 if baseValue, ok := baseAttrs[field]; ok { 87 attrs[field] = baseValue 88 } 89 } 90 } 91 92 // Generate a new UUID for the model as necessary, 93 // and finalize the new config. 94 if _, ok := attrs[config.UUIDKey]; !ok { 95 uuid, err := utils.NewUUID() 96 if err != nil { 97 return nil, errors.Trace(err) 98 } 99 attrs[config.UUIDKey] = uuid.String() 100 } 101 cfg, err := finalizeConfig(isAdmin, base, attrs) 102 if err != nil { 103 return nil, errors.Trace(err) 104 } 105 attrs = cfg.AllAttrs() 106 107 // Any values that would normally be copied from the controller 108 // config can also be defined, but if they differ from the controller 109 // values, an error is returned. 110 for _, field := range restrictedFields { 111 if value, ok := attrs[field]; ok { 112 if serverValue := baseAttrs[field]; value != serverValue { 113 return nil, errors.Errorf( 114 "specified %s \"%v\" does not match controller \"%v\"", 115 field, value, serverValue) 116 } 117 } 118 } 119 120 return cfg, nil 121 } 122 123 func (c *ModelConfigCreator) checkVersion(base *config.Config, attrs map[string]interface{}) error { 124 baseVersion, ok := base.AgentVersion() 125 if !ok { 126 return errors.Errorf("agent-version not found in base config") 127 } 128 129 // If there is no agent-version specified, use the current version. 130 // otherwise we need to check for tools 131 value, ok := attrs["agent-version"] 132 if !ok { 133 attrs["agent-version"] = baseVersion.String() 134 return nil 135 } 136 versionStr, ok := value.(string) 137 if !ok { 138 return errors.Errorf("agent-version must be a string but has type '%T'", value) 139 } 140 versionNumber, err := version.Parse(versionStr) 141 if err != nil { 142 return errors.Trace(err) 143 } 144 145 n := versionNumber.Compare(baseVersion) 146 switch { 147 case n > 0: 148 return errors.Errorf( 149 "agent-version (%s) cannot be greater than the controller (%s)", 150 versionNumber, baseVersion, 151 ) 152 case n == 0: 153 // If the version is the same as the base config, 154 // then assume tools are available. 155 return nil 156 case n < 0: 157 if c.FindTools == nil { 158 return errors.New( 159 "agent-version does not match base config, " + 160 "and no tools-finder is supplied", 161 ) 162 } 163 } 164 165 // Look to see if we have tools available for that version. 166 list, err := c.FindTools(versionNumber) 167 if err != nil { 168 return errors.Trace(err) 169 } 170 if len(list) == 0 { 171 return errors.Errorf("no tools found for version %s", versionNumber) 172 } 173 logger.Tracef("found tools: %#v", list) 174 return nil 175 } 176 177 // RestrictedProviderFields returns the set of config fields that may not be 178 // overridden. 179 func RestrictedProviderFields(providerType string) ([]string, error) { 180 provider, err := environs.Provider(providerType) 181 if err != nil { 182 return nil, errors.Trace(err) 183 } 184 var fields []string 185 fields = append(fields, configValuesFromController...) 186 fields = append(fields, provider.RestrictedConfigAttributes()...) 187 return fields, nil 188 } 189 190 // finalizeConfig creates the config object from attributes, calls 191 // PrepareForCreateEnvironment, and then finally validates the config 192 // before returning it. 193 func finalizeConfig(isAdmin bool, controllerCfg *config.Config, attrs map[string]interface{}) (*config.Config, error) { 194 provider, err := environs.Provider(controllerCfg.Type()) 195 if err != nil { 196 return nil, errors.Trace(err) 197 } 198 199 // Controller admins creating models do not have to re-supply new secrets. 200 // These may be copied from the controller model if not supplied. 201 if isAdmin { 202 maybeCopyControllerSecrets(provider, controllerCfg.AllAttrs(), attrs) 203 } 204 cfg, err := config.New(config.UseDefaults, attrs) 205 if err != nil { 206 return nil, errors.Annotate(err, "creating config from values failed") 207 } 208 209 cfg, err = provider.PrepareForCreateEnvironment(cfg) 210 if err != nil { 211 return nil, errors.Trace(err) 212 } 213 cfg, err = provider.Validate(cfg, nil) 214 if err != nil { 215 return nil, errors.Annotate(err, "provider validation failed") 216 } 217 return cfg, nil 218 } 219 220 // maybeCopyControllerSecrets asks the specified provider for all possible config 221 // attributes representing credential values and copies those across from the 222 // controller config into the new model's config attrs if not already present. 223 func maybeCopyControllerSecrets(provider environs.ProviderCredentials, controllerAttrs, attrs map[string]interface{}) { 224 requiredControllerAttrNames := []string{"authorized-keys"} 225 var controllerCredentialAttrNames []string 226 for _, schema := range provider.CredentialSchemas() { 227 // possibleCredentialValues holds any values from attrs that belong to 228 // the credential schema. 229 possibleCredentialValues := make(map[string]string) 230 for _, attr := range schema { 231 attrName := attr.Name 232 if v, ok := attrs[attrName]; ok && v != "" { 233 possibleCredentialValues[attrName] = fmt.Sprintf("%v", attrs[attrName]) 234 } 235 controllerCredentialAttrNames = append(controllerCredentialAttrNames, attrName) 236 } 237 // readFile is not needed server side. 238 readFile := func(string) ([]byte, error) { 239 return nil, errors.NotImplementedf("read file") 240 } 241 // If the user has passed in valid credentials, we'll use 242 // those and not the ones from the controller. 243 if len(possibleCredentialValues) == 0 { 244 continue 245 } 246 finalValues, err := schema.Finalize(possibleCredentialValues, readFile) 247 if err == nil { 248 for k, v := range finalValues { 249 attrs[k] = v 250 } 251 controllerCredentialAttrNames = nil 252 break 253 } 254 } 255 256 // Ensure any required attributes which are empty are copied from the controller config. 257 for _, attrName := range requiredControllerAttrNames { 258 if _, ok := attrs[attrName]; !ok { 259 attrs[attrName] = controllerAttrs[attrName] 260 } 261 } 262 for _, attrName := range controllerCredentialAttrNames { 263 if _, ok := attrs[attrName]; !ok { 264 attrs[attrName] = controllerAttrs[attrName] 265 } 266 } 267 }