github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/asserts/repair.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 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 asserts 21 22 import ( 23 "fmt" 24 "strings" 25 "time" 26 27 "github.com/snapcore/snapd/snap/naming" 28 "github.com/snapcore/snapd/strutil" 29 ) 30 31 // Repair holds an repair assertion which allows running repair 32 // code to fixup broken systems. It can be limited by series and models, as well 33 // as by bases and modes. 34 type Repair struct { 35 assertionBase 36 37 series []string 38 architectures []string 39 models []string 40 41 modes []string 42 bases []string 43 44 id int 45 46 disabled bool 47 timestamp time.Time 48 } 49 50 // BrandID returns the brand identifier that signed this assertion. 51 func (r *Repair) BrandID() string { 52 return r.HeaderString("brand-id") 53 } 54 55 // RepairID returns the sequential id of the repair. There 56 // should be a public place to look up details about the repair 57 // by brand-id and repair-id. 58 // (e.g. the snapcraft forum). 59 func (r *Repair) RepairID() int { 60 return r.id 61 } 62 63 // Sequence implements SequenceMember, it returns the same as RepairID. 64 func (r *Repair) Sequence() int { 65 return r.RepairID() 66 } 67 68 // Summary returns the mandatory summary description of the repair. 69 func (r *Repair) Summary() string { 70 return r.HeaderString("summary") 71 } 72 73 // Architectures returns the architectures that this assertions applies to. 74 func (r *Repair) Architectures() []string { 75 return r.architectures 76 } 77 78 // Series returns the series that this assertion is valid for. 79 func (r *Repair) Series() []string { 80 return r.series 81 } 82 83 // Modes returns the modes that this assertion is valid for. It is either a list 84 // of "run", "recover", or "install", or it is the empty list. The empty list 85 // is interpreted to mean only "run" mode. 86 func (r *Repair) Modes() []string { 87 return r.modes 88 } 89 90 // Bases returns the bases that this assertion is valid for. It is either a list 91 // of valid base snaps that Ubuntu Core systems can have or it is the empty 92 // list. The empty list effectively means all Ubuntu Core systems while "core" 93 // means Ubuntu Core 16, "core18" means Ubuntu Core 18, etc. 94 func (r *Repair) Bases() []string { 95 return r.bases 96 } 97 98 // Models returns the models that this assertion is valid for. 99 // It is a list of "brand-id/model-name" strings. 100 func (r *Repair) Models() []string { 101 return r.models 102 } 103 104 // Disabled returns true if the repair has been disabled. 105 func (r *Repair) Disabled() bool { 106 return r.disabled 107 } 108 109 // Timestamp returns the time when the repair was issued. 110 func (r *Repair) Timestamp() time.Time { 111 return r.timestamp 112 } 113 114 // Implement further consistency checks. 115 func (r *Repair) checkConsistency(db RODatabase, acck *AccountKey) error { 116 // Do the cross-checks when this assertion is actually used, 117 // i.e. in the future repair code 118 119 return nil 120 } 121 122 // sanity 123 var _ consistencyChecker = (*Repair)(nil) 124 125 func assembleRepair(assert assertionBase) (Assertion, error) { 126 err := checkAuthorityMatchesBrand(&assert) 127 if err != nil { 128 return nil, err 129 } 130 131 repairID, err := checkSequence(assert.headers, "repair-id") 132 if err != nil { 133 return nil, err 134 } 135 136 summary, err := checkNotEmptyString(assert.headers, "summary") 137 if err != nil { 138 return nil, err 139 } 140 if strings.ContainsAny(summary, "\n\r") { 141 return nil, fmt.Errorf(`"summary" header cannot have newlines`) 142 } 143 144 series, err := checkStringList(assert.headers, "series") 145 if err != nil { 146 return nil, err 147 } 148 models, err := checkStringList(assert.headers, "models") 149 if err != nil { 150 return nil, err 151 } 152 architectures, err := checkStringList(assert.headers, "architectures") 153 if err != nil { 154 return nil, err 155 } 156 modes, err := checkStringList(assert.headers, "modes") 157 if err != nil { 158 return nil, err 159 } 160 bases, err := checkStringList(assert.headers, "bases") 161 if err != nil { 162 return nil, err 163 } 164 165 // validate that all base snap names are valid snap names 166 for _, b := range bases { 167 if err := naming.ValidateSnap(b); err != nil { 168 return nil, fmt.Errorf("invalid snap name %q in \"bases\"", b) 169 } 170 } 171 172 // verify that modes is a list of only "run" and "recover" 173 if len(modes) != 0 { 174 for _, m := range modes { 175 // note that we could import boot here to use i.e. boot.ModeRun, but 176 // that is rather a heavy package considering that this package is 177 // used in many places, so instead just use the values directly, 178 // they're unlikely to change now 179 if !strutil.ListContains([]string{"run", "recover"}, m) { 180 return nil, fmt.Errorf("header \"modes\" contains an invalid element: %q (valid values are run and recover)", m) 181 } 182 } 183 184 // if modes is non-empty, then bases must be core2X, i.e. core20+ 185 // however, we don't know what future bases could be UC20-like and named 186 // differently yet, so we just fail on bases that we know as of today 187 // are _not_ UC20: core and core18 188 189 for _, b := range bases { 190 // fail on uc16 and uc18 base snaps 191 if b == "core" || b == "core18" || b == "core16" { 192 return nil, fmt.Errorf("in the presence of a non-empty \"modes\" header, \"bases\" must only contain base snaps supporting recovery modes") 193 } 194 } 195 } 196 197 disabled, err := checkOptionalBool(assert.headers, "disabled") 198 if err != nil { 199 return nil, err 200 } 201 202 timestamp, err := checkRFC3339Date(assert.headers, "timestamp") 203 if err != nil { 204 return nil, err 205 } 206 207 return &Repair{ 208 assertionBase: assert, 209 series: series, 210 architectures: architectures, 211 models: models, 212 modes: modes, 213 bases: bases, 214 id: repairID, 215 disabled: disabled, 216 timestamp: timestamp, 217 }, nil 218 }