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  }