github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/snapstate/devicectx.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2018-2019 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 snapstate
    21  
    22  import (
    23  	"github.com/snapcore/snapd/asserts"
    24  	"github.com/snapcore/snapd/overlord/state"
    25  )
    26  
    27  // A DeviceContext provides for operating as a given device and with
    28  // its brand store either for normal operation or over a remodeling.
    29  type DeviceContext interface {
    30  	// Model returns the governing device model assertion for the context.
    31  	Model() *asserts.Model
    32  	// Store returns the store service to use under this context or nil if the snapstate store is appropriate.
    33  	Store() StoreService
    34  
    35  	// ForRemodeling returns whether this context is for use over a remodeling.
    36  	ForRemodeling() bool
    37  }
    38  
    39  // Hook setup by devicestate to pick a device context from state,
    40  // optional task or an optionally pre-provided one. It's expected to
    41  // return ErrNoState if a model assertion is not yet known.
    42  var (
    43  	DeviceCtx func(st *state.State, task *state.Task, providedDeviceCtx DeviceContext) (DeviceContext, error)
    44  )
    45  
    46  // Hook setup by devicestate to know whether a remodeling is in progress.
    47  var (
    48  	Remodeling func(st *state.State) bool
    49  )
    50  
    51  // ModelFromTask returns a model assertion through the device context for the task.
    52  func ModelFromTask(task *state.Task) (*asserts.Model, error) {
    53  	deviceCtx, err := DeviceCtx(task.State(), task, nil)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	return deviceCtx.Model(), nil
    58  }
    59  
    60  // DevicePastSeeding returns a device context if a model assertion is
    61  // available and the device is seeded, at that point the device store
    62  // is known and seeding done. Otherwise it returns a
    63  // ChangeConflictError about being too early unless a pre-provided
    64  // DeviceContext is passed in. It will again return a conflict error
    65  // during remodeling unless the providedDeviceCtx is for it.
    66  func DevicePastSeeding(st *state.State, providedDeviceCtx DeviceContext) (DeviceContext, error) {
    67  	var seeded bool
    68  	err := st.Get("seeded", &seeded)
    69  	if err != nil && err != state.ErrNoState {
    70  		return nil, err
    71  	}
    72  	if Remodeling(st) {
    73  		// a remodeling is in progress and this is not called
    74  		// as part of it. The 2nd check should not be needed
    75  		// in practice.
    76  		if providedDeviceCtx == nil || !providedDeviceCtx.ForRemodeling() {
    77  			return nil, &ChangeConflictError{
    78  				Message: "remodeling in progress, no other " +
    79  					"changes allowed until this is done",
    80  				ChangeKind: "remodel",
    81  			}
    82  		}
    83  	}
    84  	devCtx, err := DeviceCtx(st, nil, providedDeviceCtx)
    85  	if err != nil && err != state.ErrNoState {
    86  		return nil, err
    87  	}
    88  	// when seeded devCtx should not be nil except in the rare
    89  	// case of upgrades from a snapd before the introduction of
    90  	// the fallback generic/generic-classic model
    91  	if !seeded || devCtx == nil {
    92  		return nil, &ChangeConflictError{
    93  			Message: "too early for operation, device not yet" +
    94  				" seeded or device model not acknowledged",
    95  			ChangeKind: "seed",
    96  		}
    97  	}
    98  
    99  	return devCtx, nil
   100  }
   101  
   102  // DeviceCtxFromState returns a device context if a model assertion is
   103  // available. Otherwise it returns a ChangeConflictError about being
   104  // too early unless an pre-provided DeviceContext is passed in.
   105  func DeviceCtxFromState(st *state.State, providedDeviceCtx DeviceContext) (DeviceContext, error) {
   106  	deviceCtx, err := DeviceCtx(st, nil, providedDeviceCtx)
   107  	if err != nil {
   108  		if err == state.ErrNoState {
   109  			return nil, &ChangeConflictError{
   110  				Message:    "too early for operation, device model not yet acknowledged",
   111  				ChangeKind: "seed",
   112  			}
   113  		}
   114  		return nil, err
   115  	}
   116  	return deviceCtx, nil
   117  }