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  }