github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/romulus/sla/sla.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // The sla package contains the implementation of the juju sla 5 // command. 6 package sla 7 8 import ( 9 "encoding/json" 10 "fmt" 11 "io" 12 13 "github.com/gosuri/uitable" 14 "github.com/juju/cmd" 15 "github.com/juju/errors" 16 "github.com/juju/gnuflag" 17 "github.com/juju/loggo" 18 "github.com/juju/romulus/api/sla" 19 slawire "github.com/juju/romulus/wireformat/sla" 20 "gopkg.in/macaroon.v2-unstable" 21 22 "github.com/juju/juju/api" 23 "github.com/juju/juju/api/modelconfig" 24 jujucmd "github.com/juju/juju/cmd" 25 "github.com/juju/juju/cmd/juju/common" 26 rcmd "github.com/juju/juju/cmd/juju/romulus" 27 "github.com/juju/juju/cmd/modelcmd" 28 "github.com/juju/juju/jujuclient" 29 ) 30 31 var logger = loggo.GetLogger("romulus.cmd.sla") 32 33 // authorizationClient defines the interface of an api client that 34 // the command uses to create an sla authorization macaroon. 35 type authorizationClient interface { 36 // Authorize returns the sla authorization macaroon for the specified model, 37 Authorize(modelUUID, supportLevel, wallet string) (*slawire.SLAResponse, error) 38 } 39 40 type slaClient interface { 41 SetSLALevel(level, owner string, creds []byte) error 42 SLALevel() (string, error) 43 } 44 45 type slaLevel struct { 46 Model string `json:"model,omitempty" yaml:"model,omitempty"` 47 SLA string `json:"sla,omitempty" yaml:"sla,omitempty"` 48 Message string `json:"message,omitempty" yaml:"message,omitempty"` 49 } 50 51 var newSLAClient = func(conn api.Connection) slaClient { 52 return modelconfig.NewClient(conn) 53 } 54 55 var newAuthorizationClient = func(options ...sla.ClientOption) (authorizationClient, error) { 56 return sla.NewClient(options...) 57 } 58 59 var modelId = func(conn api.Connection) string { 60 // Our connection is model based so ignore the returned bool. 61 tag, _ := conn.ModelTag() 62 return tag.Id() 63 } 64 65 var newJujuClientStore = jujuclient.NewFileClientStore 66 67 // NewSLACommand returns a new command that is used to set SLA credentials for a 68 // deployed application. 69 func NewSLACommand() cmd.Command { 70 slaCommand := &slaCommand{ 71 newSLAClient: newSLAClient, 72 newAuthorizationClient: newAuthorizationClient, 73 } 74 slaCommand.newAPIRoot = slaCommand.NewAPIRoot 75 return modelcmd.Wrap(slaCommand) 76 } 77 78 // slaCommand is a command-line tool for setting 79 // Model.SLACredential for development & demonstration purposes. 80 type slaCommand struct { 81 modelcmd.ModelCommandBase 82 out cmd.Output 83 84 newAPIRoot func() (api.Connection, error) 85 newSLAClient func(api.Connection) slaClient 86 newAuthorizationClient func(options ...sla.ClientOption) (authorizationClient, error) 87 88 Level string 89 Budget string 90 } 91 92 // SetFlags sets additional flags for the support command. 93 func (c *slaCommand) SetFlags(f *gnuflag.FlagSet) { 94 c.ModelCommandBase.SetFlags(f) 95 c.out.AddFlags(f, "tabular", map[string]cmd.Formatter{ 96 "tabular": formatTabular, 97 "json": cmd.FormatJson, 98 "yaml": cmd.FormatYaml, 99 }) 100 f.StringVar(&c.Budget, "budget", "", "the maximum spend for the model") 101 } 102 103 // Info implements cmd.Command. 104 func (c *slaCommand) Info() *cmd.Info { 105 return jujucmd.Info(&cmd.Info{ 106 Name: "sla", 107 Args: "<level>", 108 Purpose: "Set the SLA level for a model.", 109 Doc: ` 110 Set the support level for the model, effective immediately. 111 Examples: 112 # set the support level to essential 113 juju sla essential 114 115 # set the support level to essential with a maximum budget of $1000 in wallet 'personal' 116 juju sla standard --budget personal:1000 117 118 # display the current support level for the model. 119 juju sla 120 `, 121 }) 122 } 123 124 // Init implements cmd.Command. 125 func (c *slaCommand) Init(args []string) error { 126 if len(args) < 1 { 127 return nil 128 } 129 c.Level = args[0] 130 return c.ModelCommandBase.Init(args[1:]) 131 } 132 133 func (c *slaCommand) requestSupportCredentials(modelUUID string) (string, string, []byte, error) { 134 fail := func(err error) (string, string, []byte, error) { 135 return "", "", nil, err 136 } 137 hc, err := c.BakeryClient() 138 if err != nil { 139 return fail(errors.Trace(err)) 140 } 141 apiRoot, err := rcmd.GetMeteringURLForModelCmd(&c.ModelCommandBase) 142 if err != nil { 143 return fail(errors.Trace(err)) 144 } 145 authClient, err := c.newAuthorizationClient(sla.APIRoot(apiRoot), sla.HTTPClient(hc)) 146 if err != nil { 147 return fail(errors.Trace(err)) 148 } 149 slaResp, err := authClient.Authorize(modelUUID, c.Level, c.Budget) 150 if err != nil { 151 err = common.MaybeTermsAgreementError(err) 152 if termErr, ok := errors.Cause(err).(*common.TermsRequiredError); ok { 153 return fail(errors.Trace(termErr.UserErr())) 154 } 155 return fail(errors.Trace(err)) 156 } 157 ms := macaroon.Slice{slaResp.Credentials} 158 mbuf, err := json.Marshal(ms) 159 if err != nil { 160 return fail(errors.Trace(err)) 161 } 162 return slaResp.Owner, slaResp.Message, mbuf, nil 163 } 164 165 func (c *slaCommand) displayCurrentLevel(client slaClient, modelName string, ctx *cmd.Context) error { 166 level, err := client.SLALevel() 167 if err != nil { 168 return errors.Trace(err) 169 } 170 return errors.Trace(c.out.Write(ctx, &slaLevel{ 171 Model: modelName, 172 SLA: level, 173 })) 174 } 175 176 // Run implements cmd.Command. 177 func (c *slaCommand) Run(ctx *cmd.Context) error { 178 root, err := c.newAPIRoot() 179 if err != nil { 180 return errors.Trace(err) 181 } 182 client := c.newSLAClient(root) 183 modelId := modelId(root) 184 modelNameMap := modelNameMap() 185 modelName := modelId 186 if name, ok := modelNameMap[modelId]; ok { 187 modelName = name 188 } 189 190 if c.Level == "" { 191 return c.displayCurrentLevel(client, modelName, ctx) 192 } 193 owner, message, credentials, err := c.requestSupportCredentials(modelId) 194 if err != nil { 195 return errors.Trace(err) 196 } 197 if message != "" { 198 err = c.out.Write(ctx, &slaLevel{ 199 Model: modelName, 200 SLA: c.Level, 201 Message: message, 202 }) 203 if err != nil { 204 return errors.Trace(err) 205 } 206 } 207 err = client.SetSLALevel(c.Level, owner, credentials) 208 if err != nil { 209 return errors.Trace(err) 210 } 211 return nil 212 } 213 214 func formatTabular(writer io.Writer, value interface{}) error { 215 l, ok := value.(*slaLevel) 216 if !ok { 217 return errors.Errorf("expected value of type %T, got %T", l, value) 218 } 219 table := uitable.New() 220 table.MaxColWidth = 50 221 table.Wrap = true 222 for _, col := range []int{2, 3, 5} { 223 table.RightAlign(col) 224 } 225 table.AddRow("Model", "SLA", "Message") 226 table.AddRow(l.Model, l.SLA, l.Message) 227 fmt.Fprint(writer, table) 228 return nil 229 } 230 231 func modelNameMap() map[string]string { 232 store := newJujuClientStore() 233 uuidToName := map[string]string{} 234 controllers, err := store.AllControllers() 235 if err != nil { 236 logger.Warningf("failed to read juju client controller names") 237 return map[string]string{} 238 } 239 for cname := range controllers { 240 models, err := store.AllModels(cname) 241 if err != nil { 242 logger.Warningf("failed to read juju client model names") 243 return map[string]string{} 244 } 245 for mname, mdetails := range models { 246 uuidToName[mdetails.ModelUUID] = cname + ":" + mname 247 } 248 } 249 return uuidToName 250 }