github.com/ubuntu-core/snappy@v0.0.0-20210827154228-9e584df982bb/overlord/hookstate/hooks.go (about) 1 /* 2 * Copyright (C) 2017 Canonical Ltd 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License version 3 as 6 * published by the Free Software Foundation. 7 * 8 * This program is distributed in the hope that it will be useful, 9 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * GNU General Public License for more details. 12 * 13 * You should have received a copy of the GNU General Public License 14 * along with this program. If not, see <http://www.gnu.org/licenses/>. 15 * 16 */ 17 18 package hookstate 19 20 import ( 21 "fmt" 22 "regexp" 23 "time" 24 25 "github.com/snapcore/snapd/cmd/snaplock" 26 "github.com/snapcore/snapd/cmd/snaplock/runinhibit" 27 "github.com/snapcore/snapd/features" 28 "github.com/snapcore/snapd/i18n" 29 "github.com/snapcore/snapd/osutil" 30 "github.com/snapcore/snapd/overlord/configstate/config" 31 "github.com/snapcore/snapd/overlord/snapstate" 32 "github.com/snapcore/snapd/overlord/state" 33 ) 34 35 func init() { 36 snapstate.SetupInstallHook = SetupInstallHook 37 snapstate.SetupPreRefreshHook = SetupPreRefreshHook 38 snapstate.SetupPostRefreshHook = SetupPostRefreshHook 39 snapstate.SetupRemoveHook = SetupRemoveHook 40 snapstate.SetupGateAutoRefreshHook = SetupGateAutoRefreshHook 41 } 42 43 func SetupInstallHook(st *state.State, snapName string) *state.Task { 44 hooksup := &HookSetup{ 45 Snap: snapName, 46 Hook: "install", 47 Optional: true, 48 } 49 50 summary := fmt.Sprintf(i18n.G("Run install hook of %q snap if present"), hooksup.Snap) 51 task := HookTask(st, summary, hooksup, nil) 52 53 return task 54 } 55 56 func SetupPostRefreshHook(st *state.State, snapName string) *state.Task { 57 hooksup := &HookSetup{ 58 Snap: snapName, 59 Hook: "post-refresh", 60 Optional: true, 61 } 62 63 summary := fmt.Sprintf(i18n.G("Run post-refresh hook of %q snap if present"), hooksup.Snap) 64 return HookTask(st, summary, hooksup, nil) 65 } 66 67 func SetupPreRefreshHook(st *state.State, snapName string) *state.Task { 68 hooksup := &HookSetup{ 69 Snap: snapName, 70 Hook: "pre-refresh", 71 Optional: true, 72 } 73 74 summary := fmt.Sprintf(i18n.G("Run pre-refresh hook of %q snap if present"), hooksup.Snap) 75 task := HookTask(st, summary, hooksup, nil) 76 77 return task 78 } 79 80 type gateAutoRefreshHookHandler struct { 81 context *Context 82 refreshAppAwareness bool 83 } 84 85 func (h *gateAutoRefreshHookHandler) Before() error { 86 st := h.context.State() 87 st.Lock() 88 defer st.Unlock() 89 90 tr := config.NewTransaction(st) 91 experimentalRefreshAppAwareness, err := features.Flag(tr, features.RefreshAppAwareness) 92 if err != nil && !config.IsNoOption(err) { 93 return err 94 } 95 if !experimentalRefreshAppAwareness { 96 return nil 97 } 98 99 h.refreshAppAwareness = true 100 101 snapName := h.context.InstanceName() 102 103 // obtain snap lock before manipulating runinhibit lock. 104 lock, err := snaplock.OpenLock(snapName) 105 if err != nil { 106 return err 107 } 108 if err := lock.Lock(); err != nil { 109 return err 110 } 111 defer lock.Unlock() 112 113 if err := runinhibit.LockWithHint(snapName, runinhibit.HintInhibitedGateRefresh); err != nil { 114 return err 115 } 116 117 return nil 118 } 119 120 func (h *gateAutoRefreshHookHandler) Done() (err error) { 121 ctx := h.context 122 st := ctx.State() 123 ctx.Lock() 124 defer ctx.Unlock() 125 126 snapName := h.context.InstanceName() 127 128 var action snapstate.GateAutoRefreshAction 129 a := ctx.Cached("action") 130 131 // obtain snap lock before manipulating runinhibit lock. 132 var lock *osutil.FileLock 133 if h.refreshAppAwareness { 134 lock, err = snaplock.OpenLock(snapName) 135 if err != nil { 136 return err 137 } 138 if err := lock.Lock(); err != nil { 139 return err 140 } 141 defer lock.Unlock() 142 } 143 144 // default behavior if action is not set 145 if a == nil { 146 // action is not set if the gate-auto-refresh hook exits 0 without 147 // invoking --hold/--proceed; this means proceed (except for respecting 148 // refresh inhibit). 149 if h.refreshAppAwareness { 150 if err := runinhibit.Unlock(snapName); err != nil { 151 return fmt.Errorf("cannot unlock inhibit lock for snap %s: %v", snapName, err) 152 } 153 } 154 return snapstate.ProceedWithRefresh(st, snapName) 155 } else { 156 var ok bool 157 action, ok = a.(snapstate.GateAutoRefreshAction) 158 if !ok { 159 return fmt.Errorf("internal error: unexpected action type %T", a) 160 } 161 } 162 163 // action is set if snapctl refresh --hold/--proceed was called from the hook. 164 switch action { 165 case snapstate.GateAutoRefreshHold: 166 // for action=hold the ctlcmd calls HoldRefresh; only unlock runinhibit. 167 if h.refreshAppAwareness { 168 if err := runinhibit.Unlock(snapName); err != nil { 169 return fmt.Errorf("cannot unlock inhibit lock of snap %s: %v", snapName, err) 170 } 171 } 172 case snapstate.GateAutoRefreshProceed: 173 // for action=proceed the ctlcmd doesn't call ProceedWithRefresh 174 // immediately, do it here. 175 if err := snapstate.ProceedWithRefresh(st, snapName); err != nil { 176 return err 177 } 178 if h.refreshAppAwareness { 179 // we have HintInhibitedGateRefresh lock already when running the hook, 180 // change it to HintInhibitedForRefresh. 181 if err := runinhibit.LockWithHint(snapName, runinhibit.HintInhibitedForRefresh); err != nil { 182 return fmt.Errorf("cannot set inhibit lock for snap %s: %v", snapName, err) 183 } 184 } 185 default: 186 return fmt.Errorf("internal error: unexpected action %v", action) 187 } 188 189 return nil 190 } 191 192 // Error handles gate-auto-refresh hook failure; it assumes hold. 193 func (h *gateAutoRefreshHookHandler) Error(hookErr error) (ignoreHookErr bool, err error) { 194 ctx := h.context 195 st := h.context.State() 196 ctx.Lock() 197 defer ctx.Unlock() 198 199 snapName := h.context.InstanceName() 200 201 var lock *osutil.FileLock 202 203 // the refresh is going to be held, release runinhibit lock. 204 if h.refreshAppAwareness { 205 // obtain snap lock before manipulating runinhibit lock. 206 lock, err = snaplock.OpenLock(snapName) 207 if err != nil { 208 return false, err 209 } 210 if err := lock.Lock(); err != nil { 211 return false, err 212 } 213 defer lock.Unlock() 214 215 if err := runinhibit.Unlock(snapName); err != nil { 216 return false, fmt.Errorf("cannot release inhibit lock of snap %s: %v", snapName, err) 217 } 218 } 219 220 if a := ctx.Cached("action"); a != nil { 221 action, ok := a.(snapstate.GateAutoRefreshAction) 222 if !ok { 223 return false, fmt.Errorf("internal error: unexpected action type %T", a) 224 } 225 // nothing to do if the hook already requested hold. 226 if action == snapstate.GateAutoRefreshHold { 227 ctx.Errorf("ignoring hook error: %v", hookErr) 228 // tell hook manager to ignore hook error. 229 return true, nil 230 } 231 } 232 233 // the hook didn't request --hold, or it was --proceed. since the hook 234 // errored out, assume hold. 235 236 affecting, err := snapstate.AffectingSnapsForAffectedByRefreshCandidates(st, snapName) 237 if err != nil { 238 // becomes error of the handler 239 return false, err 240 } 241 242 // no duration specified, use maximum allowed for this gating snap. 243 var holdDuration time.Duration 244 if err := snapstate.HoldRefresh(st, snapName, holdDuration, affecting...); err != nil { 245 // log the original hook error as we either ignore it or error out from 246 // this handler, in both cases hookErr won't be logged by hook manager. 247 h.context.Errorf("error: %v (while handling previous hook error: %v)", err, hookErr) 248 if _, ok := err.(*snapstate.HoldError); ok { 249 // TODO: consider delaying for another hour. 250 return true, nil 251 } 252 // anything other than HoldError becomes an error of the handler. 253 return false, err 254 } 255 256 // TODO: consider assigning a special health state for the snap. 257 258 ctx.Errorf("ignoring hook error: %v", hookErr) 259 // tell hook manager to ignore hook error. 260 return true, nil 261 } 262 263 func NewGateAutoRefreshHookHandler(context *Context) *gateAutoRefreshHookHandler { 264 return &gateAutoRefreshHookHandler{ 265 context: context, 266 } 267 } 268 269 func SetupGateAutoRefreshHook(st *state.State, snapName string) *state.Task { 270 hookSup := &HookSetup{ 271 Snap: snapName, 272 Hook: "gate-auto-refresh", 273 Optional: true, 274 } 275 summary := fmt.Sprintf(i18n.G("Run hook %s of snap %q"), hookSup.Hook, hookSup.Snap) 276 task := HookTask(st, summary, hookSup, nil) 277 return task 278 } 279 280 type snapHookHandler struct { 281 } 282 283 func (h *snapHookHandler) Before() error { 284 return nil 285 } 286 287 func (h *snapHookHandler) Done() error { 288 return nil 289 } 290 291 func (h *snapHookHandler) Error(err error) (bool, error) { 292 return false, nil 293 } 294 295 func SetupRemoveHook(st *state.State, snapName string) *state.Task { 296 hooksup := &HookSetup{ 297 Snap: snapName, 298 Hook: "remove", 299 Optional: true, 300 IgnoreError: true, 301 } 302 303 summary := fmt.Sprintf(i18n.G("Run remove hook of %q snap if present"), hooksup.Snap) 304 task := HookTask(st, summary, hooksup, nil) 305 306 return task 307 } 308 309 func setupHooks(hookMgr *HookManager) { 310 handlerGenerator := func(context *Context) Handler { 311 return &snapHookHandler{} 312 } 313 gateAutoRefreshHandlerGenerator := func(context *Context) Handler { 314 return NewGateAutoRefreshHookHandler(context) 315 } 316 317 hookMgr.Register(regexp.MustCompile("^install$"), handlerGenerator) 318 hookMgr.Register(regexp.MustCompile("^post-refresh$"), handlerGenerator) 319 hookMgr.Register(regexp.MustCompile("^pre-refresh$"), handlerGenerator) 320 hookMgr.Register(regexp.MustCompile("^remove$"), handlerGenerator) 321 hookMgr.Register(regexp.MustCompile("^gate-auto-refresh$"), gateAutoRefreshHandlerGenerator) 322 }