/
devicectx.go
128 lines (114 loc) · 4.34 KB
/
devicectx.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2018-2019 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package snapstate
import (
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/boot"
"github.com/snapcore/snapd/overlord/state"
)
// A DeviceContext provides for operating as a given device and with
// its brand store either for normal operation or over a remodeling.
type DeviceContext interface {
// GroundContext returns a context corresponding to the
// original model of the device for a remodel, or a context
// equivalent to this one otherwise, except in both cases
// Store cannot be used and must panic.
GroundContext() DeviceContext
// Store returns the store service to use under this context or nil if the snapstate store is appropriate.
Store() StoreService
// ForRemodeling returns whether this context is for use over a remodeling.
ForRemodeling() bool
// SystemMode returns the system mode (run,install,recover,...).
SystemMode() string
// DeviceContext should be usable as boot.Device
boot.Device
}
// Hook setup by devicestate to pick a device context from state,
// optional task or an optionally pre-provided one. It's expected to
// return ErrNoState if a model assertion is not yet known.
var (
DeviceCtx func(st *state.State, task *state.Task, providedDeviceCtx DeviceContext) (DeviceContext, error)
)
// Hook setup by devicestate to know whether a remodeling is in progress.
var (
Remodeling func(st *state.State) bool
)
// ModelFromTask returns a model assertion through the device context for the task.
func ModelFromTask(task *state.Task) (*asserts.Model, error) {
deviceCtx, err := DeviceCtx(task.State(), task, nil)
if err != nil {
return nil, err
}
return deviceCtx.Model(), nil
}
// DevicePastSeeding returns a device context if a model assertion is
// available and the device is seeded, at that point the device store
// is known and seeding done. Otherwise it returns a
// ChangeConflictError about being too early unless a pre-provided
// DeviceContext is passed in. It will again return a conflict error
// during remodeling unless the providedDeviceCtx is for it.
func DevicePastSeeding(st *state.State, providedDeviceCtx DeviceContext) (DeviceContext, error) {
var seeded bool
err := st.Get("seeded", &seeded)
if err != nil && err != state.ErrNoState {
return nil, err
}
if Remodeling(st) {
// a remodeling is in progress and this is not called
// as part of it. The 2nd check should not be needed
// in practice.
if providedDeviceCtx == nil || !providedDeviceCtx.ForRemodeling() {
return nil, &ChangeConflictError{
Message: "remodeling in progress, no other " +
"changes allowed until this is done",
ChangeKind: "remodel",
}
}
}
devCtx, err := DeviceCtx(st, nil, providedDeviceCtx)
if err != nil && err != state.ErrNoState {
return nil, err
}
// when seeded devCtx should not be nil except in the rare
// case of upgrades from a snapd before the introduction of
// the fallback generic/generic-classic model
if !seeded || devCtx == nil {
return nil, &ChangeConflictError{
Message: "too early for operation, device not yet" +
" seeded or device model not acknowledged",
ChangeKind: "seed",
}
}
return devCtx, nil
}
// DeviceCtxFromState returns a device context if a model assertion is
// available. Otherwise it returns a ChangeConflictError about being
// too early unless an pre-provided DeviceContext is passed in.
func DeviceCtxFromState(st *state.State, providedDeviceCtx DeviceContext) (DeviceContext, error) {
deviceCtx, err := DeviceCtx(st, nil, providedDeviceCtx)
if err != nil {
if err == state.ErrNoState {
return nil, &ChangeConflictError{
Message: "too early for operation, device model not yet acknowledged",
ChangeKind: "seed",
}
}
return nil, err
}
return deviceCtx, nil
}