github.com/rigado/snapd@v2.42.5-go-mod+incompatible/cmd/snap/cmd_model.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package main 21 22 import ( 23 "errors" 24 "fmt" 25 "strings" 26 "time" 27 28 "github.com/jessevdk/go-flags" 29 30 "github.com/snapcore/snapd/asserts" 31 "github.com/snapcore/snapd/client" 32 "github.com/snapcore/snapd/i18n" 33 ) 34 35 var ( 36 shortModelHelp = i18n.G("Get the active model for this device") 37 longModelHelp = i18n.G(` 38 The model command returns the active model assertion information for this 39 device. 40 41 By default, only the essential model identification information is 42 included in the output, but this can be expanded to include all of an 43 assertion's non-meta headers. 44 45 The verbose output is presented in a structured, yaml-like format. 46 47 Similarly, the active serial assertion can be used for the output instead of the 48 model assertion. 49 `) 50 51 invalidTypeMessage = i18n.G("invalid type for %q header") 52 errNoMainAssertion = errors.New(i18n.G("device not ready yet (no assertions found)")) 53 errNoSerial = errors.New(i18n.G("device not registered yet (no serial assertion found)")) 54 errNoVerboseAssertion = errors.New(i18n.G("cannot use --verbose with --assertion")) 55 56 // this list is a "nice" "human" "readable" "ordering" of headers to print 57 // off, sorted in lexographical order with meta headers and primary key 58 // headers removed, and big nasty keys such as device-key-sha3-384 and 59 // device-key at the bottom 60 // it also contains both serial and model assertion headers, but we 61 // follow the same code path for both assertion types and some of the 62 // headers are shared between the two, so it still works out correctly 63 niceOrdering = [...]string{ 64 "architecture", 65 "base", 66 "classic", 67 "display-name", 68 "gadget", 69 "kernel", 70 "revision", 71 "timestamp", 72 "required-snaps", 73 "device-key-sha3-384", 74 "device-key", 75 } 76 ) 77 78 type cmdModel struct { 79 waitMixin 80 timeMixin 81 colorMixin 82 83 Serial bool `long:"serial"` 84 Verbose bool `long:"verbose"` 85 Assertion bool `long:"assertion"` 86 } 87 88 func init() { 89 addCommand("model", 90 shortModelHelp, 91 longModelHelp, 92 func() flags.Commander { 93 return &cmdModel{} 94 }, colorDescs.also(timeDescs).also(waitDescs).also(map[string]string{ 95 "assertion": i18n.G("Print the raw assertion."), 96 "verbose": i18n.G("Print all specific assertion fields."), 97 "serial": i18n.G( 98 "Print the serial assertion instead of the model assertion."), 99 }), 100 []argDesc{}, 101 ) 102 } 103 104 func (x *cmdModel) Execute(args []string) error { 105 if x.Verbose && x.Assertion { 106 // can't do a verbose mode for the assertion 107 return errNoVerboseAssertion 108 } 109 110 var mainAssertion asserts.Assertion 111 serialAssertion, serialErr := x.client.CurrentSerialAssertion() 112 modelAssertion, modelErr := x.client.CurrentModelAssertion() 113 114 // if we didn't get a model assertion bail early 115 if modelErr != nil { 116 if client.IsAssertionNotFoundError(modelErr) { 117 // device is not registered yet - use specific error message 118 return errNoMainAssertion 119 } 120 return modelErr 121 } 122 123 // if the serial assertion error is anything other than not found, also 124 // bail early 125 // the serial assertion not being found may not be fatal 126 if serialErr != nil && !client.IsAssertionNotFoundError(serialErr) { 127 return serialErr 128 } 129 130 if x.Serial { 131 mainAssertion = serialAssertion 132 } else { 133 mainAssertion = modelAssertion 134 } 135 136 if x.Assertion { 137 // if we are using the serial assertion and we specifically didn't find the 138 // serial assertion, bail with specific error 139 if x.Serial && client.IsAssertionNotFoundError(serialErr) { 140 return errNoMainAssertion 141 } 142 143 _, err := Stdout.Write(asserts.Encode(mainAssertion)) 144 return err 145 } 146 147 termWidth, _ := termSize() 148 termWidth -= 3 149 if termWidth > 100 { 150 // any wider than this and it gets hard to read 151 termWidth = 100 152 } 153 154 esc := x.getEscapes() 155 156 w := tabWriter() 157 158 if x.Serial && client.IsAssertionNotFoundError(serialErr) { 159 // for serial assertion, the primary keys are output (model and 160 // brand-id), but if we didn't find the serial assertion then we still 161 // output the brand-id and model from the model assertion, but also 162 // return a devNotReady error 163 fmt.Fprintf(w, "brand-id:\t%s\n", modelAssertion.HeaderString("brand-id")) 164 fmt.Fprintf(w, "model:\t%s\n", modelAssertion.HeaderString("model")) 165 w.Flush() 166 return errNoSerial 167 } 168 169 // the rest of this function is the main flow for outputting either the 170 // model or serial assertion in normal or verbose mode 171 172 // for the `snap model` case with no options, we don't want colons, we want 173 // to be like `snap version` 174 separator := ":" 175 if !x.Verbose && !x.Serial { 176 separator = "" 177 } 178 179 // ordering of the primary keys for model: brand, model, serial 180 // ordering of primary keys for serial is brand-id, model, serial 181 182 // output brand/brand-id 183 brandIDHeader := mainAssertion.HeaderString("brand-id") 184 modelHeader := mainAssertion.HeaderString("model") 185 // for the serial header, if there's no serial yet, it's not an error for 186 // model (and we already handled the serial error above) but need to add a 187 // parenthetical about the device not being registered yet 188 var serial string 189 if client.IsAssertionNotFoundError(serialErr) { 190 if x.Verbose || x.Serial { 191 // verbose and serial are yamlish, so we need to escape the dash 192 serial = esc.dash 193 } else { 194 serial = "-" 195 } 196 serial += " (device not registered yet)" 197 } else { 198 serial = serialAssertion.HeaderString("serial") 199 } 200 201 // handle brand/brand-id (the former is only on `snap model` w/o opts) 202 if x.Serial || x.Verbose { 203 fmt.Fprintf(w, "brand-id:\t%s\n", brandIDHeader) 204 } else { 205 // for the model command (not --serial) we want to show a publisher 206 // style display of "brand" instead of just "brand-id" 207 storeAccount, err := x.client.StoreAccount(brandIDHeader) 208 if err != nil { 209 return err 210 } 211 // use the longPublisher helper to format the brand store account 212 // like we do in `snap info` 213 fmt.Fprintf(w, "brand%s\t%s\n", separator, longPublisher(x.getEscapes(), storeAccount)) 214 } 215 216 // handle model, on `snap model` we try to add display-name if it exists 217 if x.Serial { 218 fmt.Fprintf(w, "model:\t%s\n", modelHeader) 219 } else { 220 // for model, if there's a display-name, we show that first with the 221 // real model in parenthesis 222 if displayName := modelAssertion.HeaderString("display-name"); displayName != "" { 223 modelHeader = fmt.Sprintf("%s (%s)", displayName, modelHeader) 224 } 225 fmt.Fprintf(w, "model%s\t%s\n", separator, modelHeader) 226 } 227 228 // serial is same for all variants 229 fmt.Fprintf(w, "serial%s\t%s\n", separator, serial) 230 231 // --verbose means output more information 232 if x.Verbose { 233 allHeadersMap := mainAssertion.Headers() 234 235 for _, headerName := range niceOrdering { 236 invalidTypeErr := fmt.Errorf(invalidTypeMessage, headerName) 237 238 headerValue, ok := allHeadersMap[headerName] 239 // make sure the header is in the map 240 if !ok { 241 continue 242 } 243 244 // switch on which header it is to handle some special cases 245 switch headerName { 246 // list of scalars 247 case "required-snaps": 248 headerIfaceList, ok := headerValue.([]interface{}) 249 if !ok { 250 return invalidTypeErr 251 } 252 if len(headerIfaceList) == 0 { 253 continue 254 } 255 fmt.Fprintf(w, "%s:\t\n", headerName) 256 for _, elem := range headerIfaceList { 257 headerStringElem, ok := elem.(string) 258 if !ok { 259 return invalidTypeErr 260 } 261 // note we don't wrap these, since for now this is 262 // specifically just required-snaps and so all of these 263 // will be snap names which are required to be short 264 fmt.Fprintf(w, " - %s\n", headerStringElem) 265 } 266 267 //timestamp needs to be formatted with fmtTime from the timeMixin 268 case "timestamp": 269 timestamp, ok := headerValue.(string) 270 if !ok { 271 return invalidTypeErr 272 } 273 274 // parse the time string as RFC3339, which is what the format is 275 // always in for assertions 276 t, err := time.Parse(time.RFC3339, timestamp) 277 if err != nil { 278 return err 279 } 280 fmt.Fprintf(w, "timestamp:\t%s\n", x.fmtTime(t)) 281 282 // long string key we don't want to rewrap but can safely handle 283 // on "reasonable" width terminals 284 case "device-key-sha3-384": 285 // also flush the writer before continuing so the previous keys 286 // don't try to align with this key 287 w.Flush() 288 headerString, ok := headerValue.(string) 289 if !ok { 290 return invalidTypeErr 291 } 292 293 switch { 294 case termWidth > 86: 295 fmt.Fprintf(w, "device-key-sha3-384: %s\n", headerString) 296 case termWidth <= 86 && termWidth > 66: 297 fmt.Fprintln(w, "device-key-sha3-384: |") 298 wrapLine(w, []rune(headerString), " ", termWidth) 299 } 300 301 // long base64 key we can rewrap safely 302 case "device-key": 303 headerString, ok := headerValue.(string) 304 if !ok { 305 return invalidTypeErr 306 } 307 // the string value here has newlines inserted as part of the 308 // raw assertion, but base64 doesn't care about whitespace, so 309 // it's safe to split by newlines and re-wrap to make it 310 // prettier 311 headerString = strings.Join( 312 strings.Split(headerString, "\n"), 313 "") 314 fmt.Fprintln(w, "device-key: |") 315 wrapLine(w, []rune(headerString), " ", termWidth) 316 317 // the default is all the rest of short scalar values, which all 318 // should be strings 319 default: 320 headerString, ok := headerValue.(string) 321 if !ok { 322 return invalidTypeErr 323 } 324 fmt.Fprintf(w, "%s:\t%s\n", headerName, headerString) 325 } 326 } 327 } 328 329 return w.Flush() 330 }