github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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.MESS) 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 } 93 94 // ConfigSkeleton returns config values to be used as a starting point for the 95 // API caller to construct a valid environment specific config. The provider 96 // and region params are there for future use, and current behaviour expects 97 // both of these to be empty. 98 func (em *EnvironmentManagerAPI) ConfigSkeleton(args params.EnvironmentSkeletonConfigArgs) (params.EnvironConfigResult, error) { 99 var result params.EnvironConfigResult 100 if args.Provider != "" { 101 return result, errors.NotValidf("provider value %q", args.Provider) 102 } 103 if args.Region != "" { 104 return result, errors.NotValidf("region value %q", args.Region) 105 } 106 107 stateServerEnv, err := em.state.StateServerEnvironment() 108 if err != nil { 109 return result, errors.Trace(err) 110 } 111 112 config, err := em.configSkeleton(stateServerEnv) 113 if err != nil { 114 return result, errors.Trace(err) 115 } 116 117 result.Config = config 118 return result, nil 119 } 120 121 func (em *EnvironmentManagerAPI) restrictedProviderFields(providerType string) ([]string, error) { 122 provider, err := environs.Provider(providerType) 123 if err != nil { 124 return nil, errors.Trace(err) 125 } 126 127 var fields []string 128 fields = append(fields, configValuesFromStateServer...) 129 fields = append(fields, provider.RestrictedConfigAttributes()...) 130 return fields, nil 131 } 132 133 func (em *EnvironmentManagerAPI) configSkeleton(source ConfigSource) (map[string]interface{}, error) { 134 baseConfig, err := source.Config() 135 if err != nil { 136 return nil, errors.Trace(err) 137 } 138 baseMap := baseConfig.AllAttrs() 139 140 fields, err := em.restrictedProviderFields(baseConfig.Type()) 141 if err != nil { 142 return nil, errors.Trace(err) 143 } 144 145 var result = make(map[string]interface{}) 146 for _, field := range fields { 147 if value, found := baseMap[field]; found { 148 result[field] = value 149 } 150 } 151 return result, nil 152 } 153 154 func (em *EnvironmentManagerAPI) checkVersion(cfg map[string]interface{}) error { 155 // If there is no agent-version specified, use the current version. 156 // otherwise we need to check for tools 157 value, found := cfg["agent-version"] 158 if !found { 159 cfg["agent-version"] = version.Current.Number.String() 160 return nil 161 } 162 valuestr, ok := value.(string) 163 if !ok { 164 return errors.Errorf("agent-version must be a string but has type '%T'", value) 165 } 166 num, err := version.Parse(valuestr) 167 if err != nil { 168 return errors.Trace(err) 169 } 170 if comp := num.Compare(version.Current.Number); comp > 0 { 171 return errors.Errorf("agent-version cannot be greater than the server: %s", version.Current.Number) 172 } else if comp < 0 { 173 // Look to see if we have tools available for that version. 174 // Obviously if the version is the same, we have the tools available. 175 list, err := em.toolsFinder.FindTools(params.FindToolsParams{ 176 Number: num, 177 }) 178 if err != nil { 179 return errors.Trace(err) 180 } 181 logger.Tracef("found tools: %#v", list) 182 if len(list.List) == 0 { 183 return errors.Errorf("no tools found for version %s", num) 184 } 185 } 186 return nil 187 } 188 189 func (em *EnvironmentManagerAPI) newEnvironmentConfig(args params.EnvironmentCreateArgs, source ConfigSource) (*config.Config, error) { 190 // For now, we just smash to the two maps together as we store 191 // the account values and the environment config together in the 192 // *config.Config instance. 193 joint := make(map[string]interface{}) 194 for key, value := range args.Config { 195 joint[key] = value 196 } 197 // Account info overrides any config values. 198 for key, value := range args.Account { 199 joint[key] = value 200 } 201 if _, found := joint["uuid"]; found { 202 return nil, errors.New("uuid is generated, you cannot specify one") 203 } 204 baseConfig, err := source.Config() 205 if err != nil { 206 return nil, errors.Trace(err) 207 } 208 baseMap := baseConfig.AllAttrs() 209 fields, err := em.restrictedProviderFields(baseConfig.Type()) 210 if err != nil { 211 return nil, errors.Trace(err) 212 } 213 214 // Any values that would normally be copied from the state server 215 // config can also be defined, but if they differ from the state server 216 // values, an error is returned. 217 for _, field := range fields { 218 if value, found := joint[field]; found { 219 if serverValue := baseMap[field]; value != serverValue { 220 return nil, errors.Errorf( 221 "specified %s \"%v\" does not match apiserver \"%v\"", 222 field, value, serverValue) 223 } 224 } else { 225 if value, found := baseMap[field]; found { 226 joint[field] = value 227 } 228 } 229 } 230 if err := em.checkVersion(joint); err != nil { 231 return nil, errors.Trace(err) 232 } 233 234 // Generate the UUID for the server. 235 uuid, err := utils.NewUUID() 236 if err != nil { 237 return nil, errors.Annotate(err, "failed to generate environment uuid") 238 } 239 joint["uuid"] = uuid.String() 240 cfg, err := config.New(config.UseDefaults, joint) 241 if err != nil { 242 return nil, errors.Trace(err) 243 } 244 provider, err := environs.Provider(cfg.Type()) 245 if err != nil { 246 return nil, errors.Trace(err) 247 } 248 return provider.Validate(cfg, nil) 249 } 250 251 // CreateEnvironment creates a new environment using the account and 252 // environment config specified in the args. 253 func (em *EnvironmentManagerAPI) CreateEnvironment(args params.EnvironmentCreateArgs) (params.Environment, error) { 254 result := params.Environment{} 255 // Get the state server environment first. We need it both for the state 256 // server owner and the ability to get the config. 257 stateServerEnv, err := em.state.StateServerEnvironment() 258 if err != nil { 259 return result, errors.Trace(err) 260 } 261 adminUser := stateServerEnv.Owner() 262 263 ownerTag, err := names.ParseUserTag(args.OwnerTag) 264 if err != nil { 265 return result, errors.Trace(err) 266 } 267 268 // Any user is able to create themselves an environment (until real fine 269 // grain permissions are available), and admins (the creator of the state 270 // server environment) are able to create environments for other people. 271 err = em.authCheck(ownerTag, adminUser) 272 if err != nil { 273 return result, errors.Trace(err) 274 } 275 276 newConfig, err := em.newEnvironmentConfig(args, stateServerEnv) 277 if err != nil { 278 return result, errors.Trace(err) 279 } 280 // NOTE: check the agent-version of the config, and if it is > the current 281 // version, it is not supported, also check existing tools, and if we don't 282 // have tools for that version, also die. 283 env, st, err := em.state.NewEnvironment(newConfig, ownerTag) 284 if err != nil { 285 return result, errors.Annotate(err, "failed to create new environment") 286 } 287 defer st.Close() 288 289 result.Name = env.Name() 290 result.UUID = env.UUID() 291 result.OwnerTag = env.Owner().String() 292 293 return result, nil 294 } 295 296 // ListEnvironments returns the environments that the specified user 297 // has access to in the current server. Only that state server owner 298 // can list environments for any user (at this stage). Other users 299 // can only ask about their own environments. 300 func (em *EnvironmentManagerAPI) ListEnvironments(user params.Entity) (params.EnvironmentList, error) { 301 result := params.EnvironmentList{} 302 303 stateServerEnv, err := em.state.StateServerEnvironment() 304 if err != nil { 305 return result, errors.Trace(err) 306 } 307 adminUser := stateServerEnv.Owner() 308 309 userTag, err := names.ParseUserTag(user.Tag) 310 if err != nil { 311 return result, errors.Trace(err) 312 } 313 314 err = em.authCheck(userTag, adminUser) 315 if err != nil { 316 return result, errors.Trace(err) 317 } 318 319 environments, err := em.state.EnvironmentsForUser(userTag) 320 if err != nil { 321 return result, errors.Trace(err) 322 } 323 324 for _, env := range environments { 325 result.Environments = append(result.Environments, params.Environment{ 326 Name: env.Name(), 327 UUID: env.UUID(), 328 OwnerTag: env.Owner().String(), 329 }) 330 logger.Debugf("list env: %s, %s, %s", env.Name(), env.UUID(), env.Owner()) 331 } 332 333 return result, nil 334 }