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{}) {}