github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/cmd/snap/cmd_wait.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2018 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 "encoding/json" 24 "fmt" 25 "reflect" 26 "time" 27 28 "github.com/jessevdk/go-flags" 29 30 "github.com/snapcore/snapd/client" 31 "github.com/snapcore/snapd/i18n" 32 ) 33 34 type cmdWait struct { 35 clientMixin 36 Positional struct { 37 Snap installedSnapName `required:"yes"` 38 Key string 39 } `positional-args:"yes"` 40 } 41 42 func init() { 43 addCommand("wait", 44 "Wait for configuration", 45 "The wait command waits until a configuration becomes true.", 46 func() flags.Commander { 47 return &cmdWait{} 48 }, nil, []argDesc{ 49 { 50 name: "<snap>", 51 // TRANSLATORS: This should not start with a lowercase letter. 52 desc: i18n.G("The snap for which configuration will be checked"), 53 }, { 54 // TRANSLATORS: This needs to begin with < and end with > 55 name: i18n.G("<key>"), 56 // TRANSLATORS: This should not start with a lowercase letter. 57 desc: i18n.G("Key of interest within the configuration"), 58 }, 59 }) 60 } 61 62 var waitConfTimeout = 500 * time.Millisecond 63 64 func isNoOption(err error) bool { 65 if e, ok := err.(*client.Error); ok && e.Kind == client.ErrorKindConfigNoSuchOption { 66 return true 67 } 68 return false 69 } 70 71 // trueishJSON takes an interface{} and returns true if the interface value 72 // looks "true". For strings thats if len(string) > 0 for numbers that 73 // they are != 0 and for maps/slices/arrays that they have elements. 74 // 75 // Note that *only* types that the json package decode with the 76 // "UseNumber()" options turned on are handled here. If this ever 77 // needs to becomes a generic "trueish" helper we need to resurrect 78 // the code in 306ba60edfba8d6501060c6f773235d8c994a319 (and add nil 79 // to it). 80 func trueishJSON(vi interface{}) (bool, error) { 81 switch v := vi.(type) { 82 // limited to the types that json unmarhal can produce 83 case nil: 84 return false, nil 85 case bool: 86 return v, nil 87 case json.Number: 88 if i, err := v.Int64(); err == nil { 89 return i != 0, nil 90 } 91 if f, err := v.Float64(); err == nil { 92 return f != 0.0, nil 93 } 94 case string: 95 return v != "", nil 96 } 97 // arrays/slices/maps 98 typ := reflect.TypeOf(vi) 99 switch typ.Kind() { 100 case reflect.Array, reflect.Slice, reflect.Map: 101 s := reflect.ValueOf(vi) 102 switch s.Kind() { 103 case reflect.Array, reflect.Slice, reflect.Map: 104 return s.Len() > 0, nil 105 } 106 } 107 108 return false, fmt.Errorf("cannot test type %T for truth", vi) 109 } 110 111 func (x *cmdWait) Execute(args []string) error { 112 if len(args) > 0 { 113 return ErrExtraArgs 114 } 115 116 snapName := string(x.Positional.Snap) 117 confKey := x.Positional.Key 118 119 if confKey == "" { 120 return fmt.Errorf("the required argument `<key>` was not provided") 121 } 122 123 for { 124 conf, err := x.client.Conf(snapName, []string{confKey}) 125 if err != nil && !isNoOption(err) { 126 return err 127 } 128 res, err := trueishJSON(conf[confKey]) 129 if err != nil { 130 return err 131 } 132 if res { 133 break 134 } 135 time.Sleep(waitConfTimeout) 136 } 137 138 return nil 139 }