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 }