github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snap/cmd_routine_console_conf.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 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  	"fmt"
    24  	"sort"
    25  	"strings"
    26  	"sync"
    27  	"time"
    28  
    29  	"github.com/jessevdk/go-flags"
    30  
    31  	"github.com/snapcore/snapd/client"
    32  	"github.com/snapcore/snapd/i18n"
    33  )
    34  
    35  type cmdRoutineConsoleConfStart struct {
    36  	clientMixin
    37  }
    38  
    39  var shortRoutineConsoleConfStartHelp = i18n.G("Start console-conf snapd routine")
    40  var longRoutineConsoleConfStartHelp = i18n.G(`
    41  The console-conf-start command starts synchronization with console-conf
    42  
    43  This command is used by console-conf when it starts up. It delays refreshes if
    44  there are none currently ongoing, and exits with a specific error code if there
    45  are ongoing refreshes which console-conf should wait for before prompting the 
    46  user to begin configuring the device.
    47  `)
    48  
    49  // TODO: move these to their own package for unified time constants for how
    50  // often or long we do things like waiting for a reboot, etc. ?
    51  var snapdAPIInterval = 2 * time.Second
    52  var snapdWaitForFullSystemReboot = 10 * time.Minute
    53  
    54  func init() {
    55  	c := addRoutineCommand("console-conf-start", shortRoutineConsoleConfStartHelp, longRoutineConsoleConfStartHelp, func() flags.Commander {
    56  		return &cmdRoutineConsoleConfStart{}
    57  	}, nil, nil)
    58  	c.hidden = true
    59  }
    60  
    61  func printfFunc(msg string, format ...interface{}) func() {
    62  	return func() {
    63  		fmt.Fprintf(Stderr, msg, format...)
    64  	}
    65  }
    66  
    67  func (x *cmdRoutineConsoleConfStart) Execute(args []string) error {
    68  	var snapdReloadMsgOnce, systemReloadMsgOnce, snapRefreshMsgOnce sync.Once
    69  
    70  	for {
    71  		chgs, snaps, err := x.client.InternalConsoleConfStart()
    72  		if err != nil {
    73  			// snapd may be under maintenance right now, either for base/kernel
    74  			// snap refreshes which result in a reboot, or for snapd itself
    75  			// which just results in a restart of the daemon
    76  			maybeMaintErr := x.client.Maintenance()
    77  			if maybeMaintErr == nil {
    78  				// not a maintenance error, give up
    79  				return err
    80  			}
    81  
    82  			maintErr, ok := maybeMaintErr.(*client.Error)
    83  			if !ok {
    84  				// if cli.Maintenance() didn't return a client.Error we have very weird
    85  				// problems
    86  				return fmt.Errorf("internal error: client.Maintenance() didn't return a client.Error")
    87  			}
    88  
    89  			if maintErr.Kind == client.ErrorKindDaemonRestart {
    90  				// then we need to wait for snapd to restart, so keep trying
    91  				// the console-conf-start endpoint until it works
    92  				snapdReloadMsgOnce.Do(printfFunc("Snapd is reloading, please wait...\n"))
    93  
    94  				// we know that snapd isn't available because it is in
    95  				// maintenance so we don't gain anything by hitting it
    96  				// more frequently except for perhaps a quicker latency
    97  				// for the user when it comes back, but it will be busy
    98  				// doing things when it starts up anyways so it won't be
    99  				// able to respond immediately
   100  				time.Sleep(snapdAPIInterval)
   101  				continue
   102  			} else if maintErr.Kind == client.ErrorKindSystemRestart {
   103  				// system is rebooting, just wait for the reboot
   104  				systemReloadMsgOnce.Do(printfFunc("System is rebooting, please wait for reboot...\n"))
   105  				time.Sleep(snapdWaitForFullSystemReboot)
   106  				// if we didn't reboot after 10 minutes something's probably broken
   107  				return fmt.Errorf("system didn't reboot after 10 minutes even though snapd daemon is in maintenance")
   108  			}
   109  		}
   110  
   111  		if len(chgs) == 0 {
   112  			return nil
   113  		}
   114  
   115  		if len(snaps) == 0 {
   116  			// internal error if we have chg id's, but no snaps
   117  			return fmt.Errorf("internal error: returned changes (%v) but no snap names", chgs)
   118  		}
   119  
   120  		snapRefreshMsgOnce.Do(func() {
   121  			sort.Strings(snaps)
   122  
   123  			var snapNameList string
   124  			switch len(snaps) {
   125  			case 1:
   126  				snapNameList = snaps[0]
   127  			case 2:
   128  				snapNameList = fmt.Sprintf("%s and %s", snaps[0], snaps[1])
   129  			default:
   130  				// don't forget the oxford comma!
   131  				snapNameList = fmt.Sprintf("%s, and %s", strings.Join(snaps[:len(snaps)-1], ", "), snaps[len(snaps)-1])
   132  			}
   133  
   134  			fmt.Fprintf(Stderr, "Snaps (%s) are refreshing, please wait...\n", snapNameList)
   135  		})
   136  
   137  		// don't DDOS snapd by hitting it's API too often
   138  		time.Sleep(snapdAPIInterval)
   139  	}
   140  }