github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/apiserver/environmentmanager/environmentmanager.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // The environmentmanager package defines an API end point for functions 5 // dealing with envionments. Creating, listing and sharing environments. 6 package environmentmanager 7 8 import ( 9 "github.com/juju/errors" 10 "github.com/juju/loggo" 11 "github.com/juju/names" 12 "github.com/juju/utils" 13 14 "github.com/juju/juju/apiserver/common" 15 "github.com/juju/juju/apiserver/params" 16 "github.com/juju/juju/environs" 17 "github.com/juju/juju/environs/config" 18 "github.com/juju/juju/feature" 19 "github.com/juju/juju/state" 20 "github.com/juju/juju/version" 21 ) 22 23 var logger = loggo.GetLogger("juju.apiserver.environmentmanager") 24 25 func init() { 26 common.RegisterStandardFacadeForFeature("EnvironmentManager", 1, NewEnvironmentManagerAPI, feature.JES) 27 } 28 29 // EnvironmentManager defines the methods on the environmentmanager API end 30 // point. 31 type EnvironmentManager interface { 32 ConfigSkeleton(args params.EnvironmentSkeletonConfigArgs) (params.EnvironConfigResult, error) 33 CreateEnvironment(args params.EnvironmentCreateArgs) (params.Environment, error) 34 ListEnvironments(user params.Entity) (params.EnvironmentList, error) 35 } 36 37 // EnvironmentManagerAPI implements the environment manager interface and is 38 // the concrete implementation of the api end point. 39 type EnvironmentManagerAPI struct { 40 state stateInterface 41 authorizer common.Authorizer 42 toolsFinder *common.ToolsFinder 43 } 44 45 var _ EnvironmentManager = (*EnvironmentManagerAPI)(nil) 46 47 // NewEnvironmentManagerAPI creates a new api server endpoint for managing 48 // environments. 49 func NewEnvironmentManagerAPI( 50 st *state.State, 51 resources *common.Resources, 52 authorizer common.Authorizer, 53 ) (*EnvironmentManagerAPI, error) { 54 if !authorizer.AuthClient() { 55 return nil, common.ErrPerm 56 } 57 58 urlGetter := common.NewToolsURLGetter(st.EnvironUUID(), st) 59 return &EnvironmentManagerAPI{ 60 state: getState(st), 61 authorizer: authorizer, 62 toolsFinder: common.NewToolsFinder(st, st, urlGetter), 63 }, nil 64 } 65 66 func (em *EnvironmentManagerAPI) authCheck(user, adminUser names.UserTag) error { 67 authTag := em.authorizer.GetAuthTag() 68 apiUser, ok := authTag.(names.UserTag) 69 if !ok { 70 return errors.Errorf("auth tag should be a user, but isn't: %q", authTag.String()) 71 } 72 logger.Tracef("comparing api user %q against owner %q and admin %q", apiUser, user, adminUser) 73 if apiUser == user || apiUser == adminUser { 74 return nil 75 } 76 return common.ErrPerm 77 } 78 79 // ConfigSource describes a type that is able to provide config. 80 // Abstracted primarily for testing. 81 type ConfigSource interface { 82 Config() (*config.Config, error) 83 } 84 85 var configValuesFromStateServer = []string{ 86 "type", 87 "ca-cert", 88 "state-port", 89 "api-port", 90 "syslog-port", 91 "rsyslog-ca-cert", 92 "rsyslog-ca-key", 93 } 94 95 // ConfigSkeleton returns config values to be used as a starting point for the 96 // API caller to construct a valid environment specific config. The provider 97 // and region params are there for future use, and current behaviour expects 98 // both of these to be empty. 99 func (em *EnvironmentManagerAPI) ConfigSkeleton(args params.EnvironmentSkeletonConfigArgs) (params.EnvironConfigResult, error) { 100 var result params.EnvironConfigResult 101 if args.Provider != "" { 102 return result, errors.NotValidf("provider value %q", args.Provider) 103 } 104 if args.Region != "" { 105 return result, errors.NotValidf("region value %q", args.Region) 106 } 107 108 stateServerEnv, err := em.state.StateServerEnvironment() 109 if err != nil { 110 return result, errors.Trace(err) 111 } 112 113 config, err := em.configSkeleton(stateServerEnv) 114 if err != nil { 115 return result, errors.Trace(err) 116 } 117 118 result.Config = config 119 return result, nil 120 } 121 122 func (em *EnvironmentManagerAPI) restrictedProviderFields(providerType string) ([]string, error) { 123 provider, err := environs.Provider(providerType) 124 if err != nil { 125 return nil, errors.Trace(err) 126 } 127 128 var fields []string 129 fields = append(fields, configValuesFromStateServer...) 130 fields = append(fields, provider.RestrictedConfigAttributes()...) 131 return fields, nil 132 } 133 134 func (em *EnvironmentManagerAPI) configSkeleton(source ConfigSource) (map[string]interface{}, error) { 135 baseConfig, err := source.Config() 136 if err != nil { 137 return nil, errors.Trace(err) 138 } 139 baseMap := baseConfig.AllAttrs() 140 141 fields, err := em.restrictedProviderFields(baseConfig.Type()) 142 if err != nil { 143 return nil, errors.Trace(err) 144 } 145 146 var result = make(map[string]interface{}) 147 for _, field := range fields { 148 if value, found := baseMap[field]; found { 149 result[field] = value 150 } 151 } 152 return result, nil 153 } 154 155 func (em *EnvironmentManagerAPI) checkVersion(cfg map[string]interface{}) error { 156 // If there is no agent-version specified, use the current version. 157 // otherwise we need to check for tools 158 value, found := cfg["agent-version"] 159 if !found { 160 cfg["agent-version"] = version.Current.Number.String() 161 return nil 162 } 163 valuestr, ok := value.(string) 164 if !ok { 165 return errors.Errorf("agent-version must be a string but has type '%T'", value) 166 } 167 num, err := version.Parse(valuestr) 168 if err != nil { 169 return errors.Trace(err) 170 } 171 if comp := num.Compare(version.Current.Number); comp > 0 { 172 return errors.Errorf("agent-version cannot be greater than the server: %s", version.Current.Number) 173 } else if comp < 0 { 174 // Look to see if we have tools available for that version. 175 // Obviously if the version is the same, we have the tools available. 176 list, err := em.toolsFinder.FindTools(params.FindToolsParams{ 177 Number: num, 178 }) 179 if err != nil { 180 return errors.Trace(err) 181 } 182 logger.Tracef("found tools: %#v", list) 183 if len(list.List) == 0 { 184 return errors.Errorf("no tools found for version %s", num) 185 } 186 } 187 return nil 188 } 189 190 func (em *EnvironmentManagerAPI) validConfig(attrs map[string]interface{}) (*config.Config, error) { 191 cfg, err := config.New(config.UseDefaults, attrs) 192 if err != nil { 193 return nil, errors.Annotate(err, "creating config from values failed") 194 } 195 provider, err := environs.Provider(cfg.Type()) 196 if err != nil { 197 return nil, errors.Trace(err) 198 } 199 cfg, err = provider.Validate(cfg, nil) 200 if err != nil { 201 return nil, errors.Annotate(err, "provider validation failed") 202 } 203 return cfg, nil 204 } 205 206 func (em *EnvironmentManagerAPI) newEnvironmentConfig(args params.EnvironmentCreateArgs, source ConfigSource) (*config.Config, error) { 207 // For now, we just smash to the two maps together as we store 208 // the account values and the environment config together in the 209 // *config.Config instance. 210 joint := make(map[string]interface{}) 211 for key, value := range args.Config { 212 joint[key] = value 213 } 214 // Account info overrides any config values. 215 for key, value := range args.Account { 216 joint[key] = value 217 } 218 if _, found := joint["uuid"]; found { 219 return nil, errors.New("uuid is generated, you cannot specify one") 220 } 221 baseConfig, err := source.Config() 222 if err != nil { 223 return nil, errors.Trace(err) 224 } 225 baseMap := baseConfig.AllAttrs() 226 fields, err := em.restrictedProviderFields(baseConfig.Type()) 227 if err != nil { 228 return nil, errors.Trace(err) 229 } 230 // Before comparing any values, we need to push the config through 231 // the provider validation code. One of the reasons for this is that 232 // numbers being serialized through JSON get turned into float64. The 233 // schema code used in config will convert these back into integers. 234 // However, before we can create a valid config, we need to make sure 235 // we copy across fields from the main config that aren't there. 236 for _, field := range fields { 237 if _, found := joint[field]; !found { 238 if baseValue, found := baseMap[field]; found { 239 joint[field] = baseValue 240 } 241 } 242 } 243 244 cfg, err := em.validConfig(joint) 245 if err != nil { 246 return nil, errors.Trace(err) 247 } 248 attrs := cfg.AllAttrs() 249 // Any values that would normally be copied from the state server 250 // config can also be defined, but if they differ from the state server 251 // values, an error is returned. 252 for _, field := range fields { 253 if value, found := attrs[field]; found { 254 if serverValue := baseMap[field]; value != serverValue { 255 return nil, errors.Errorf( 256 "specified %s \"%v\" does not match apiserver \"%v\"", 257 field, value, serverValue) 258 } 259 } 260 } 261 if err := em.checkVersion(attrs); err != nil { 262 return nil, errors.Trace(err) 263 } 264 265 // Generate the UUID for the server. 266 uuid, err := utils.NewUUID() 267 if err != nil { 268 return nil, errors.Annotate(err, "failed to generate environment uuid") 269 } 270 attrs["uuid"] = uuid.String() 271 272 return em.validConfig(attrs) 273 } 274 275 // CreateEnvironment creates a new environment using the account and 276 // environment config specified in the args. 277 func (em *EnvironmentManagerAPI) CreateEnvironment(args params.EnvironmentCreateArgs) (params.Environment, error) { 278 result := params.Environment{} 279 // Get the state server environment first. We need it both for the state 280 // server owner and the ability to get the config. 281 stateServerEnv, err := em.state.StateServerEnvironment() 282 if err != nil { 283 return result, errors.Trace(err) 284 } 285 adminUser := stateServerEnv.Owner() 286 287 ownerTag, err := names.ParseUserTag(args.OwnerTag) 288 if err != nil { 289 return result, errors.Trace(err) 290 } 291 292 // Any user is able to create themselves an environment (until real fine 293 // grain permissions are available), and admins (the creator of the state 294 // server environment) are able to create environments for other people. 295 err = em.authCheck(ownerTag, adminUser) 296 if err != nil { 297 return result, errors.Trace(err) 298 } 299 300 newConfig, err := em.newEnvironmentConfig(args, stateServerEnv) 301 if err != nil { 302 return result, errors.Trace(err) 303 } 304 // NOTE: check the agent-version of the config, and if it is > the current 305 // version, it is not supported, also check existing tools, and if we don't 306 // have tools for that version, also die. 307 env, st, err := em.state.NewEnvironment(newConfig, ownerTag) 308 if err != nil { 309 return result, errors.Annotate(err, "failed to create new environment") 310 } 311 defer st.Close() 312 313 result.Name = env.Name() 314 result.UUID = env.UUID() 315 result.OwnerTag = env.Owner().String() 316 317 return result, nil 318 } 319 320 // ListEnvironments returns the environments that the specified user 321 // has access to in the current server. Only that state server owner 322 // can list environments for any user (at this stage). Other users 323 // can only ask about their own environments. 324 func (em *EnvironmentManagerAPI) ListEnvironments(user params.Entity) (params.EnvironmentList, error) { 325 result := params.EnvironmentList{} 326 327 stateServerEnv, err := em.state.StateServerEnvironment() 328 if err != nil { 329 return result, errors.Trace(err) 330 } 331 adminUser := stateServerEnv.Owner() 332 333 userTag, err := names.ParseUserTag(user.Tag) 334 if err != nil { 335 return result, errors.Trace(err) 336 } 337 338 err = em.authCheck(userTag, adminUser) 339 if err != nil { 340 return result, errors.Trace(err) 341 } 342 343 environments, err := em.state.EnvironmentsForUser(userTag) 344 if err != nil { 345 return result, errors.Trace(err) 346 } 347 348 for _, env := range environments { 349 result.Environments = append(result.Environments, params.Environment{ 350 Name: env.Name(), 351 UUID: env.UUID(), 352 OwnerTag: env.Owner().String(), 353 }) 354 logger.Debugf("list env: %s, %s, %s", env.Name(), env.UUID(), env.Owner()) 355 } 356 357 return result, nil 358 }