1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
|
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2018 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package main
import (
"encoding/json"
"fmt"
"reflect"
"time"
"github.com/jessevdk/go-flags"
"github.com/snapcore/snapd/client"
"github.com/snapcore/snapd/i18n"
)
type cmdWait struct {
clientMixin
Positional struct {
Snap installedSnapName `required:"yes"`
Key string
} `positional-args:"yes"`
}
func init() {
addCommand("wait",
"Wait for configuration",
"The wait command waits until a configuration becomes true.",
func() flags.Commander {
return &cmdWait{}
}, nil, []argDesc{
{
name: "<snap>",
// TRANSLATORS: This should not start with a lowercase letter.
desc: i18n.G("The snap for which configuration will be checked"),
}, {
// TRANSLATORS: This needs to begin with < and end with >
name: i18n.G("<key>"),
// TRANSLATORS: This should not start with a lowercase letter.
desc: i18n.G("Key of interest within the configuration"),
},
})
}
var waitConfTimeout = 500 * time.Millisecond
func isNoOption(err error) bool {
if e, ok := err.(*client.Error); ok && e.Kind == client.ErrorKindConfigNoSuchOption {
return true
}
return false
}
// trueishJSON takes an any and returns true if the interface value
// looks "true". For strings thats if len(string) > 0 for numbers that
// they are != 0 and for maps/slices/arrays that they have elements.
//
// Note that *only* types that the json package decode with the
// "UseNumber()" options turned on are handled here. If this ever
// needs to becomes a generic "trueish" helper we need to resurrect
// the code in 306ba60edfba8d6501060c6f773235d8c994a319 (and add nil
// to it).
func trueishJSON(vi any) (bool, error) {
switch v := vi.(type) {
// limited to the types that json unmarhal can produce
case nil:
return false, nil
case bool:
return v, nil
case json.Number:
if i, err := v.Int64(); err == nil {
return i != 0, nil
}
if f, err := v.Float64(); err == nil {
return f != 0.0, nil
}
case string:
return v != "", nil
}
// arrays/slices/maps
typ := reflect.TypeOf(vi)
switch typ.Kind() {
case reflect.Array, reflect.Slice, reflect.Map:
s := reflect.ValueOf(vi)
switch s.Kind() {
case reflect.Array, reflect.Slice, reflect.Map:
return s.Len() > 0, nil
}
}
return false, fmt.Errorf("cannot test type %T for truth", vi)
}
func (x *cmdWait) Execute(args []string) error {
if len(args) > 0 {
return ErrExtraArgs
}
snapName := string(x.Positional.Snap)
confKey := x.Positional.Key
if confKey == "" {
return fmt.Errorf("the required argument `<key>` was not provided")
}
for {
conf, err := x.client.Conf(snapName, []string{confKey})
if err != nil && !isNoOption(err) {
return err
}
res, err := trueishJSON(conf[confKey])
if err != nil {
return err
}
if res {
break
}
time.Sleep(waitConfTimeout)
}
return nil
}
|