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  }