github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/client/modelconfig/modelconfig.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package modelconfig
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"github.com/juju/loggo"
     9  
    10  	"github.com/juju/juju/apiserver/common"
    11  	"github.com/juju/juju/apiserver/facade"
    12  	"github.com/juju/juju/apiserver/params"
    13  	"github.com/juju/juju/environs/config"
    14  	"github.com/juju/juju/permission"
    15  )
    16  
    17  // NewFacade is used for API registration.
    18  func NewFacadeV2(ctx facade.Context) (*ModelConfigAPIV2, error) {
    19  	auth := ctx.Auth()
    20  
    21  	model, err := ctx.State().Model()
    22  	if err != nil {
    23  		return nil, errors.Trace(err)
    24  	}
    25  	return NewModelConfigAPI(NewStateBackend(model), auth)
    26  }
    27  
    28  // NewFacadeV1 is used for API registration.
    29  func NewFacadeV1(ctx facade.Context) (*ModelConfigAPIV1, error) {
    30  	api, err := NewFacadeV2(ctx)
    31  	if err != nil {
    32  		return nil, errors.Trace(err)
    33  	}
    34  	return &ModelConfigAPIV1{api}, nil
    35  }
    36  
    37  // ModelConfigAPI provides the base implementation of the methods
    38  // for the V2 and V1 api calls.
    39  type ModelConfigAPI struct {
    40  	backend Backend
    41  	auth    facade.Authorizer
    42  	check   *common.BlockChecker
    43  }
    44  
    45  // ModelConfigAPIV2 is currently the latest.
    46  type ModelConfigAPIV2 struct {
    47  	*ModelConfigAPI
    48  }
    49  
    50  // ModelConfigAPIV1 hides V2 functionality
    51  type ModelConfigAPIV1 struct {
    52  	*ModelConfigAPIV2
    53  }
    54  
    55  // NewModelConfigAPI creates a new instance of the ModelConfig Facade.
    56  func NewModelConfigAPI(backend Backend, authorizer facade.Authorizer) (*ModelConfigAPIV2, error) {
    57  	if !authorizer.AuthClient() {
    58  		return nil, common.ErrPerm
    59  	}
    60  	client := &ModelConfigAPI{
    61  		backend: backend,
    62  		auth:    authorizer,
    63  		check:   common.NewBlockChecker(backend),
    64  	}
    65  	return &ModelConfigAPIV2{client}, nil
    66  }
    67  
    68  func (c *ModelConfigAPI) checkCanWrite() error {
    69  	canWrite, err := c.auth.HasPermission(permission.WriteAccess, c.backend.ModelTag())
    70  	if err != nil {
    71  		return errors.Trace(err)
    72  	}
    73  	if !canWrite {
    74  		return common.ErrPerm
    75  	}
    76  	return nil
    77  }
    78  
    79  func (c *ModelConfigAPI) isControllerAdmin() error {
    80  	hasAccess, err := c.auth.HasPermission(permission.SuperuserAccess, c.backend.ControllerTag())
    81  	if err != nil {
    82  		return errors.Trace(err)
    83  	}
    84  	if !hasAccess {
    85  		return common.ErrPerm
    86  	}
    87  	return nil
    88  }
    89  
    90  func (c *ModelConfigAPI) canReadModel() error {
    91  	isAdmin, err := c.auth.HasPermission(permission.SuperuserAccess, c.backend.ControllerTag())
    92  	if err != nil {
    93  		return errors.Trace(err)
    94  	}
    95  	canRead, err := c.auth.HasPermission(permission.ReadAccess, c.backend.ModelTag())
    96  	if err != nil {
    97  		return errors.Trace(err)
    98  	}
    99  	if !isAdmin && !canRead {
   100  		return common.ErrPerm
   101  	}
   102  	return nil
   103  }
   104  
   105  // ModelGet implements the server-side part of the
   106  // model-config CLI command.
   107  func (c *ModelConfigAPI) ModelGet() (params.ModelConfigResults, error) {
   108  	result := params.ModelConfigResults{}
   109  	if err := c.canReadModel(); err != nil {
   110  		return result, errors.Trace(err)
   111  	}
   112  
   113  	values, err := c.backend.ModelConfigValues()
   114  	if err != nil {
   115  		return result, errors.Trace(err)
   116  	}
   117  
   118  	result.Config = make(map[string]params.ConfigValue)
   119  	for attr, val := range values {
   120  		// Authorized keys are able to be listed using
   121  		// juju ssh-keys and including them here just
   122  		// clutters everything.
   123  		if attr == config.AuthorizedKeysKey {
   124  			continue
   125  		}
   126  		result.Config[attr] = params.ConfigValue{
   127  			Value:  val.Value,
   128  			Source: val.Source,
   129  		}
   130  	}
   131  	return result, nil
   132  }
   133  
   134  // ModelSet implements the server-side part of the
   135  // set-model-config CLI command.
   136  func (c *ModelConfigAPI) ModelSet(args params.ModelSet) error {
   137  	if err := c.checkCanWrite(); err != nil {
   138  		return err
   139  	}
   140  
   141  	if err := c.check.ChangeAllowed(); err != nil {
   142  		return errors.Trace(err)
   143  	}
   144  	// Make sure we don't allow changing agent-version.
   145  	checkAgentVersion := func(updateAttrs map[string]interface{}, removeAttrs []string, oldConfig *config.Config) error {
   146  		if v, found := updateAttrs["agent-version"]; found {
   147  			oldVersion, _ := oldConfig.AgentVersion()
   148  			if v != oldVersion.String() {
   149  				return errors.New("agent-version cannot be changed")
   150  			}
   151  		}
   152  		return nil
   153  	}
   154  	// Only controller admins can set trace level debugging on a model.
   155  	checkLogTrace := func(updateAttrs map[string]interface{}, removeAttrs []string, oldConfig *config.Config) error {
   156  		spec, ok := updateAttrs["logging-config"]
   157  		if !ok {
   158  			return nil
   159  		}
   160  		logCfg, err := loggo.ParseConfigString(spec.(string))
   161  		if err != nil {
   162  			return errors.Trace(err)
   163  		}
   164  		// Does at least one package have TRACE level logging requested.
   165  		haveTrace := false
   166  		for _, level := range logCfg {
   167  			haveTrace = level == loggo.TRACE
   168  			if haveTrace {
   169  				break
   170  			}
   171  		}
   172  		// No TRACE level requested, so no need to check for admin.
   173  		if !haveTrace {
   174  			return nil
   175  		}
   176  		if err := c.isControllerAdmin(); err != nil {
   177  			if errors.Cause(err) != common.ErrPerm {
   178  				return errors.Trace(err)
   179  			}
   180  			return errors.New("only controller admins can set a model's logging level to TRACE")
   181  		}
   182  		return nil
   183  	}
   184  
   185  	// Replace any deprecated attributes with their new values.
   186  	attrs := config.ProcessDeprecatedAttributes(args.Config)
   187  	return c.backend.UpdateModelConfig(attrs, nil, checkAgentVersion, checkLogTrace)
   188  }
   189  
   190  // ModelUnset implements the server-side part of the
   191  // set-model-config CLI command.
   192  func (c *ModelConfigAPI) ModelUnset(args params.ModelUnset) error {
   193  	if err := c.checkCanWrite(); err != nil {
   194  		return err
   195  	}
   196  	if err := c.check.ChangeAllowed(); err != nil {
   197  		return errors.Trace(err)
   198  	}
   199  	return c.backend.UpdateModelConfig(nil, args.Keys)
   200  }
   201  
   202  // SetSLALevel sets the sla level on the model.
   203  func (c *ModelConfigAPI) SetSLALevel(args params.ModelSLA) error {
   204  	if err := c.checkCanWrite(); err != nil {
   205  		return err
   206  	}
   207  	return c.backend.SetSLA(args.Level, args.Owner, args.Credentials)
   208  
   209  }
   210  
   211  // SLALevel returns the current sla level for the model.
   212  func (c *ModelConfigAPI) SLALevel() (params.StringResult, error) {
   213  	result := params.StringResult{}
   214  	level, err := c.backend.SLALevel()
   215  	if err != nil {
   216  		return result, errors.Trace(err)
   217  	}
   218  	result.Result = level
   219  	return result, nil
   220  }
   221  
   222  // Sequences returns the model's sequence names and next values.
   223  func (c *ModelConfigAPI) Sequences() (params.ModelSequencesResult, error) {
   224  	result := params.ModelSequencesResult{}
   225  	if err := c.canReadModel(); err != nil {
   226  		return result, errors.Trace(err)
   227  	}
   228  
   229  	values, err := c.backend.Sequences()
   230  	if err != nil {
   231  		return result, errors.Trace(err)
   232  	}
   233  
   234  	result.Sequences = values
   235  	return result, nil
   236  }
   237  
   238  // Mask the new methods from the V1 API. The API reflection code in
   239  // rpc/rpcreflect/type.go:newMethod skips 2-argument methods, so this
   240  // removes the method as far as the RPC machinery is concerned.
   241  
   242  // Sequences isn't on the V1 API.
   243  func (a *ModelConfigAPIV1) Sequences(_, _ struct{}) {}