github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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  	"sort"
    24  	"time"
    25  
    26  	"github.com/snapcore/snapd/cmd/snaplock"
    27  	"github.com/snapcore/snapd/cmd/snaplock/runinhibit"
    28  	"github.com/snapcore/snapd/features"
    29  	"github.com/snapcore/snapd/i18n"
    30  	"github.com/snapcore/snapd/osutil"
    31  	"github.com/snapcore/snapd/overlord/configstate/config"
    32  	"github.com/snapcore/snapd/overlord/snapstate"
    33  	"github.com/snapcore/snapd/overlord/state"
    34  )
    35  
    36  func init() {
    37  	snapstate.SetupInstallHook = SetupInstallHook
    38  	snapstate.SetupPreRefreshHook = SetupPreRefreshHook
    39  	snapstate.SetupPostRefreshHook = SetupPostRefreshHook
    40  	snapstate.SetupRemoveHook = SetupRemoveHook
    41  	snapstate.SetupGateAutoRefreshHook = SetupGateAutoRefreshHook
    42  }
    43  
    44  func SetupInstallHook(st *state.State, snapName string) *state.Task {
    45  	hooksup := &HookSetup{
    46  		Snap:     snapName,
    47  		Hook:     "install",
    48  		Optional: true,
    49  	}
    50  
    51  	summary := fmt.Sprintf(i18n.G("Run install hook of %q snap if present"), hooksup.Snap)
    52  	task := HookTask(st, summary, hooksup, nil)
    53  
    54  	return task
    55  }
    56  
    57  func SetupPostRefreshHook(st *state.State, snapName string) *state.Task {
    58  	hooksup := &HookSetup{
    59  		Snap:     snapName,
    60  		Hook:     "post-refresh",
    61  		Optional: true,
    62  	}
    63  
    64  	summary := fmt.Sprintf(i18n.G("Run post-refresh hook of %q snap if present"), hooksup.Snap)
    65  	return HookTask(st, summary, hooksup, nil)
    66  }
    67  
    68  func SetupPreRefreshHook(st *state.State, snapName string) *state.Task {
    69  	hooksup := &HookSetup{
    70  		Snap:     snapName,
    71  		Hook:     "pre-refresh",
    72  		Optional: true,
    73  	}
    74  
    75  	summary := fmt.Sprintf(i18n.G("Run pre-refresh hook of %q snap if present"), hooksup.Snap)
    76  	task := HookTask(st, summary, hooksup, nil)
    77  
    78  	return task
    79  }
    80  
    81  type gateAutoRefreshHookHandler struct {
    82  	context             *Context
    83  	refreshAppAwareness bool
    84  }
    85  
    86  func (h *gateAutoRefreshHookHandler) Before() error {
    87  	st := h.context.State()
    88  	st.Lock()
    89  	defer st.Unlock()
    90  
    91  	tr := config.NewTransaction(st)
    92  	experimentalRefreshAppAwareness, err := features.Flag(tr, features.RefreshAppAwareness)
    93  	if err != nil && !config.IsNoOption(err) {
    94  		return err
    95  	}
    96  	if !experimentalRefreshAppAwareness {
    97  		return nil
    98  	}
    99  
   100  	h.refreshAppAwareness = true
   101  
   102  	snapName := h.context.InstanceName()
   103  
   104  	// obtain snap lock before manipulating runinhibit lock.
   105  	lock, err := snaplock.OpenLock(snapName)
   106  	if err != nil {
   107  		return err
   108  	}
   109  	if err := lock.Lock(); err != nil {
   110  		return err
   111  	}
   112  	defer lock.Unlock()
   113  
   114  	if err := runinhibit.LockWithHint(snapName, runinhibit.HintInhibitedGateRefresh); err != nil {
   115  		return err
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  func (h *gateAutoRefreshHookHandler) Done() (err error) {
   122  	ctx := h.context
   123  	st := ctx.State()
   124  	ctx.Lock()
   125  	defer ctx.Unlock()
   126  
   127  	snapName := h.context.InstanceName()
   128  
   129  	var action snapstate.GateAutoRefreshAction
   130  	a := ctx.Cached("action")
   131  
   132  	// obtain snap lock before manipulating runinhibit lock.
   133  	var lock *osutil.FileLock
   134  	if h.refreshAppAwareness {
   135  		lock, err = snaplock.OpenLock(snapName)
   136  		if err != nil {
   137  			return err
   138  		}
   139  		if err := lock.Lock(); err != nil {
   140  			return err
   141  		}
   142  		defer lock.Unlock()
   143  	}
   144  
   145  	// default behavior if action is not set
   146  	if a == nil {
   147  		// action is not set if the gate-auto-refresh hook exits 0 without
   148  		// invoking --hold/--proceed; this means proceed (except for respecting
   149  		// refresh inhibit).
   150  		if h.refreshAppAwareness {
   151  			if err := runinhibit.Unlock(snapName); err != nil {
   152  				return fmt.Errorf("cannot unlock inhibit lock for snap %s: %v", snapName, err)
   153  			}
   154  		}
   155  		return snapstate.ProceedWithRefresh(st, snapName)
   156  	} else {
   157  		var ok bool
   158  		action, ok = a.(snapstate.GateAutoRefreshAction)
   159  		if !ok {
   160  			return fmt.Errorf("internal error: unexpected action type %T", a)
   161  		}
   162  	}
   163  
   164  	// action is set if snapctl refresh --hold/--proceed was called from the hook.
   165  	switch action {
   166  	case snapstate.GateAutoRefreshHold:
   167  		// for action=hold the ctlcmd calls HoldRefresh; only unlock runinhibit.
   168  		if h.refreshAppAwareness {
   169  			if err := runinhibit.Unlock(snapName); err != nil {
   170  				return fmt.Errorf("cannot unlock inhibit lock of snap %s: %v", snapName, err)
   171  			}
   172  		}
   173  	case snapstate.GateAutoRefreshProceed:
   174  		// for action=proceed the ctlcmd doesn't call ProceedWithRefresh
   175  		// immediately, do it here.
   176  		if err := snapstate.ProceedWithRefresh(st, snapName); err != nil {
   177  			return err
   178  		}
   179  		if h.refreshAppAwareness {
   180  			// we have HintInhibitedGateRefresh lock already when running the hook,
   181  			// change it to HintInhibitedForRefresh.
   182  			if err := runinhibit.LockWithHint(snapName, runinhibit.HintInhibitedForRefresh); err != nil {
   183  				return fmt.Errorf("cannot set inhibit lock for snap %s: %v", snapName, err)
   184  			}
   185  		}
   186  	default:
   187  		return fmt.Errorf("internal error: unexpected action %v", action)
   188  	}
   189  
   190  	return nil
   191  }
   192  
   193  // Error handles gate-auto-refresh hook failure; it assumes hold.
   194  func (h *gateAutoRefreshHookHandler) Error(hookErr error) (ignoreHookErr bool, err error) {
   195  	ctx := h.context
   196  	st := h.context.State()
   197  	ctx.Lock()
   198  	defer ctx.Unlock()
   199  
   200  	snapName := h.context.InstanceName()
   201  
   202  	var lock *osutil.FileLock
   203  
   204  	// the refresh is going to be held, release runinhibit lock.
   205  	if h.refreshAppAwareness {
   206  		// obtain snap lock before manipulating runinhibit lock.
   207  		lock, err = snaplock.OpenLock(snapName)
   208  		if err != nil {
   209  			return false, err
   210  		}
   211  		if err := lock.Lock(); err != nil {
   212  			return false, err
   213  		}
   214  		defer lock.Unlock()
   215  
   216  		if err := runinhibit.Unlock(snapName); err != nil {
   217  			return false, fmt.Errorf("cannot release inhibit lock of snap %s: %v", snapName, err)
   218  		}
   219  	}
   220  
   221  	if a := ctx.Cached("action"); a != nil {
   222  		action, ok := a.(snapstate.GateAutoRefreshAction)
   223  		if !ok {
   224  			return false, fmt.Errorf("internal error: unexpected action type %T", a)
   225  		}
   226  		// nothing to do if the hook already requested hold.
   227  		if action == snapstate.GateAutoRefreshHold {
   228  			ctx.Errorf("ignoring hook error: %v", hookErr)
   229  			// tell hook manager to ignore hook error.
   230  			return true, nil
   231  		}
   232  	}
   233  
   234  	// the hook didn't request --hold, or it was --proceed. since the hook
   235  	// errored out, assume hold.
   236  
   237  	var affecting []string
   238  	if err := ctx.Get("affecting-snaps", &affecting); err != nil {
   239  		return false, fmt.Errorf("internal error: cannot get affecting-snaps")
   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, base, restart bool, affectingSnaps map[string]bool) *state.Task {
   270  	hookSup := &HookSetup{
   271  		Snap:     snapName,
   272  		Hook:     "gate-auto-refresh",
   273  		Optional: true,
   274  	}
   275  	affecting := make([]string, 0, len(affectingSnaps))
   276  	for sn := range affectingSnaps {
   277  		affecting = append(affecting, sn)
   278  	}
   279  	sort.Strings(affecting)
   280  	summary := fmt.Sprintf(i18n.G("Run hook %s of snap %q"), hookSup.Hook, hookSup.Snap)
   281  	hookCtx := map[string]interface{}{
   282  		"base":            base,
   283  		"restart":         restart,
   284  		"affecting-snaps": affecting,
   285  	}
   286  	task := HookTask(st, summary, hookSup, hookCtx)
   287  	return task
   288  }
   289  
   290  type snapHookHandler struct {
   291  }
   292  
   293  func (h *snapHookHandler) Before() error {
   294  	return nil
   295  }
   296  
   297  func (h *snapHookHandler) Done() error {
   298  	return nil
   299  }
   300  
   301  func (h *snapHookHandler) Error(err error) (bool, error) {
   302  	return false, nil
   303  }
   304  
   305  func SetupRemoveHook(st *state.State, snapName string) *state.Task {
   306  	hooksup := &HookSetup{
   307  		Snap:        snapName,
   308  		Hook:        "remove",
   309  		Optional:    true,
   310  		IgnoreError: true,
   311  	}
   312  
   313  	summary := fmt.Sprintf(i18n.G("Run remove hook of %q snap if present"), hooksup.Snap)
   314  	task := HookTask(st, summary, hooksup, nil)
   315  
   316  	return task
   317  }
   318  
   319  func setupHooks(hookMgr *HookManager) {
   320  	handlerGenerator := func(context *Context) Handler {
   321  		return &snapHookHandler{}
   322  	}
   323  	gateAutoRefreshHandlerGenerator := func(context *Context) Handler {
   324  		return NewGateAutoRefreshHookHandler(context)
   325  	}
   326  
   327  	hookMgr.Register(regexp.MustCompile("^install$"), handlerGenerator)
   328  	hookMgr.Register(regexp.MustCompile("^post-refresh$"), handlerGenerator)
   329  	hookMgr.Register(regexp.MustCompile("^pre-refresh$"), handlerGenerator)
   330  	hookMgr.Register(regexp.MustCompile("^remove$"), handlerGenerator)
   331  	hookMgr.Register(regexp.MustCompile("^gate-auto-refresh$"), gateAutoRefreshHandlerGenerator)
   332  }