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  }