gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/seed/seedwriter/seed20.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-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 seedwriter 21 22 import ( 23 "encoding/json" 24 "errors" 25 "fmt" 26 "os" 27 "path/filepath" 28 29 "github.com/snapcore/snapd/asserts" 30 "github.com/snapcore/snapd/seed/internal" 31 "github.com/snapcore/snapd/snap" 32 "github.com/snapcore/snapd/snap/channel" 33 "github.com/snapcore/snapd/snap/naming" 34 ) 35 36 type policy20 struct { 37 model *asserts.Model 38 opts *Options 39 40 warningf func(format string, a ...interface{}) 41 } 42 43 var errNotAllowedExceptForDangerous = errors.New("cannot override channels, add devmode snaps, local snaps, or extra snaps with a model of grade higher than dangerous") 44 45 func (pol *policy20) checkAllowedDangerous() error { 46 if pol.model.Grade() != asserts.ModelDangerous { 47 return errNotAllowedExceptForDangerous 48 } 49 return nil 50 } 51 52 func (pol *policy20) allowsDangerousFeatures() error { 53 return pol.checkAllowedDangerous() 54 } 55 56 func (pol *policy20) checkDefaultChannel(channel.Channel) error { 57 return pol.checkAllowedDangerous() 58 } 59 60 func (pol *policy20) checkSnapChannel(ch channel.Channel, whichSnap string) error { 61 return pol.checkAllowedDangerous() 62 } 63 64 func (pol *policy20) systemSnap() *asserts.ModelSnap { 65 return internal.MakeSystemSnap("snapd", "latest/stable", []string{"run", "ephemeral"}) 66 } 67 68 func (pol *policy20) modelSnapDefaultChannel() string { 69 // We will use latest/stable as default, model that want something else 70 // will need to to speficy a default-channel 71 return "latest/stable" 72 } 73 74 func (pol *policy20) extraSnapDefaultChannel() string { 75 // We will use latest/stable as default for consistency with 76 // model snaps, this means not taking into account default-tracks 77 // by default 78 return "latest/stable" 79 } 80 81 func (pol *policy20) checkBase(info *snap.Info, modes []string, availableByMode map[string]*naming.SnapSet) error { 82 base := info.Base 83 if base == "" { 84 if info.Type() != snap.TypeGadget && info.Type() != snap.TypeApp { 85 return nil 86 } 87 base = "core" 88 } 89 90 if pol.checkAvailable(naming.Snap(base), modes, availableByMode) { 91 return nil 92 } 93 94 whichBase := fmt.Sprintf("its base %q", base) 95 if base == "core16" { 96 if pol.checkAvailable(naming.Snap("core"), modes, availableByMode) { 97 return nil 98 } 99 whichBase += ` (or "core")` 100 } 101 102 return fmt.Errorf("cannot add snap %q without also adding %s explicitly%s", info.SnapName(), whichBase, errorMsgForModesSuffix(modes)) 103 } 104 105 func (pol *policy20) checkAvailable(snapRef naming.SnapRef, modes []string, availableByMode map[string]*naming.SnapSet) bool { 106 // checks that snapRef is available in all modes 107 for _, mode := range modes { 108 byMode := availableByMode[mode] 109 if !byMode.Contains(snapRef) { 110 if mode == "run" || mode == "ephemeral" { 111 // no additional fallback for these 112 // cases: 113 // * run is not ephemeral, 114 // is covered only by run 115 // * ephemeral is only covered by ephemeral 116 return false 117 } 118 // all non-run modes (e.g. recover) are 119 // considered ephemeral, as a fallback check 120 // if the snap is listed under the ephemeral mode label 121 ephem := availableByMode["ephemeral"] 122 if ephem == nil || !ephem.Contains(snapRef) { 123 return false 124 } 125 } 126 } 127 return true 128 } 129 130 func (pol *policy20) needsImplicitSnaps(map[string]*naming.SnapSet) (bool, error) { 131 // no implicit snaps with Core 20 132 // TODO: unless we want to support them for extra snaps 133 return false, nil 134 } 135 136 func (pol *policy20) implicitSnaps(map[string]*naming.SnapSet) []*asserts.ModelSnap { 137 return nil 138 } 139 140 func (pol *policy20) implicitExtraSnaps(map[string]*naming.SnapSet) []*OptionsSnap { 141 return nil 142 } 143 144 type tree20 struct { 145 grade asserts.ModelGrade 146 opts *Options 147 148 snapsDirPath string 149 systemDir string 150 151 systemSnapsDirEnsured bool 152 } 153 154 func (tr *tree20) mkFixedDirs() error { 155 tr.snapsDirPath = filepath.Join(tr.opts.SeedDir, "snaps") 156 tr.systemDir = filepath.Join(tr.opts.SeedDir, "systems", tr.opts.Label) 157 158 if err := os.MkdirAll(tr.snapsDirPath, 0755); err != nil { 159 return err 160 } 161 162 if err := os.MkdirAll(filepath.Dir(tr.systemDir), 0755); err != nil { 163 return err 164 } 165 if err := os.Mkdir(tr.systemDir, 0755); err != nil { 166 if os.IsExist(err) { 167 return &SystemAlreadyExistsError{ 168 label: tr.opts.Label, 169 } 170 } 171 return err 172 } 173 return nil 174 } 175 176 func (tr *tree20) ensureSystemSnapsDir() (string, error) { 177 snapsDir := filepath.Join(tr.systemDir, "snaps") 178 if tr.systemSnapsDirEnsured { 179 return snapsDir, nil 180 } 181 if err := os.MkdirAll(snapsDir, 0755); err != nil { 182 return "", err 183 } 184 tr.systemSnapsDirEnsured = true 185 return snapsDir, nil 186 } 187 188 func (tr *tree20) snapPath(sn *SeedSnap) (string, error) { 189 var snapsDir string 190 if sn.modelSnap != nil { 191 snapsDir = tr.snapsDirPath 192 } else { 193 // extra snap 194 var err error 195 snapsDir, err = tr.ensureSystemSnapsDir() 196 if err != nil { 197 return "", err 198 } 199 } 200 return filepath.Join(snapsDir, sn.Info.Filename()), nil 201 } 202 203 func (tr *tree20) localSnapPath(sn *SeedSnap) (string, error) { 204 sysSnapsDir, err := tr.ensureSystemSnapsDir() 205 if err != nil { 206 return "", err 207 } 208 return filepath.Join(sysSnapsDir, fmt.Sprintf("%s_%s.snap", sn.SnapName(), sn.Info.Version)), nil 209 } 210 211 func (tr *tree20) writeAssertions(db asserts.RODatabase, modelRefs []*asserts.Ref, snapsFromModel []*SeedSnap, extraSnaps []*SeedSnap) error { 212 assertsDir := filepath.Join(tr.systemDir, "assertions") 213 if err := os.MkdirAll(assertsDir, 0755); err != nil { 214 return err 215 } 216 217 writeByRefs := func(fname string, refsGen func(stop <-chan struct{}) <-chan *asserts.Ref) error { 218 f, err := os.OpenFile(filepath.Join(assertsDir, fname), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) 219 if err != nil { 220 return err 221 } 222 defer f.Close() 223 224 stop := make(chan struct{}) 225 defer close(stop) 226 refs := refsGen(stop) 227 228 enc := asserts.NewEncoder(f) 229 for { 230 aRef := <-refs 231 if aRef == nil { 232 break 233 } 234 a, err := aRef.Resolve(db.Find) 235 if err != nil { 236 return fmt.Errorf("internal error: lost saved assertion") 237 } 238 if err := enc.Encode(a); err != nil { 239 return err 240 } 241 } 242 return nil 243 } 244 245 pushRef := func(refs chan<- *asserts.Ref, ref *asserts.Ref, stop <-chan struct{}) bool { 246 select { 247 case refs <- ref: 248 return true 249 case <-stop: 250 // get unstuck if we error early 251 return false 252 } 253 } 254 255 modelOnly := func(aRef *asserts.Ref) bool { return aRef.Type == asserts.ModelType } 256 excludeModel := func(aRef *asserts.Ref) bool { return aRef.Type != asserts.ModelType } 257 258 modelRefsGen := func(include func(*asserts.Ref) bool) func(stop <-chan struct{}) <-chan *asserts.Ref { 259 return func(stop <-chan struct{}) <-chan *asserts.Ref { 260 refs := make(chan *asserts.Ref) 261 go func() { 262 for _, aRef := range modelRefs { 263 if include(aRef) { 264 if !pushRef(refs, aRef, stop) { 265 return 266 } 267 } 268 } 269 close(refs) 270 }() 271 return refs 272 } 273 } 274 275 if err := writeByRefs("../model", modelRefsGen(modelOnly)); err != nil { 276 return err 277 } 278 279 if err := writeByRefs("model-etc", modelRefsGen(excludeModel)); err != nil { 280 return err 281 } 282 283 snapsRefGen := func(snaps []*SeedSnap) func(stop <-chan struct{}) <-chan *asserts.Ref { 284 return func(stop <-chan struct{}) <-chan *asserts.Ref { 285 refs := make(chan *asserts.Ref) 286 go func() { 287 for _, sn := range snaps { 288 for _, aRef := range sn.ARefs { 289 if !pushRef(refs, aRef, stop) { 290 return 291 } 292 } 293 } 294 close(refs) 295 }() 296 return refs 297 } 298 } 299 300 if err := writeByRefs("snaps", snapsRefGen(snapsFromModel)); err != nil { 301 return err 302 } 303 304 if len(extraSnaps) != 0 { 305 if err := writeByRefs("extra-snaps", snapsRefGen(extraSnaps)); err != nil { 306 return err 307 } 308 } 309 310 return nil 311 } 312 313 func (tr *tree20) writeMeta(snapsFromModel []*SeedSnap, extraSnaps []*SeedSnap) error { 314 var optionsSnaps []*internal.Snap20 315 316 for _, sn := range snapsFromModel { 317 channelOverride := "" 318 if sn.Channel != sn.modelSnap.DefaultChannel { 319 channelOverride = sn.Channel 320 } 321 if sn.Info.ID() != "" && channelOverride == "" { 322 continue 323 } 324 unasserted := "" 325 if sn.Info.ID() == "" { 326 unasserted = filepath.Base(sn.Path) 327 } 328 329 optionsSnaps = append(optionsSnaps, &internal.Snap20{ 330 Name: sn.SnapName(), 331 // even if unasserted != "" SnapID is useful 332 // to cross-ref the model entry 333 SnapID: sn.modelSnap.ID(), 334 Unasserted: unasserted, 335 Channel: channelOverride, 336 }) 337 } 338 339 for _, sn := range extraSnaps { 340 channel := sn.Channel 341 unasserted := "" 342 if sn.Info.ID() == "" { 343 unasserted = filepath.Base(sn.Path) 344 channel = "" 345 } 346 347 optionsSnaps = append(optionsSnaps, &internal.Snap20{ 348 Name: sn.SnapName(), 349 SnapID: sn.Info.ID(), 350 Unasserted: unasserted, 351 Channel: channel, 352 }) 353 } 354 355 if len(optionsSnaps) != 0 { 356 if tr.grade != asserts.ModelDangerous { 357 return fmt.Errorf("internal error: unexpected non-model snap overrides with grade %s", tr.grade) 358 } 359 options20 := &internal.Options20{Snaps: optionsSnaps} 360 if err := options20.Write(filepath.Join(tr.systemDir, "options.yaml")); err != nil { 361 return err 362 } 363 } 364 365 auxInfos := make(map[string]*internal.AuxInfo20) 366 367 addAuxInfos := func(seedSnaps []*SeedSnap) { 368 for _, sn := range seedSnaps { 369 if sn.Info.ID() != "" { 370 if sn.Info.EditedContact != "" || sn.Info.Private { 371 auxInfos[sn.Info.ID()] = &internal.AuxInfo20{ 372 Private: sn.Info.Private, 373 // TODO: set this only if the snap has no links 374 Contact: sn.Info.Contact(), 375 } 376 } 377 } 378 } 379 } 380 381 addAuxInfos(snapsFromModel) 382 addAuxInfos(extraSnaps) 383 384 if len(auxInfos) == 0 { 385 // nothing to do 386 return nil 387 } 388 389 if _, err := tr.ensureSystemSnapsDir(); err != nil { 390 return err 391 } 392 393 f, err := os.OpenFile(filepath.Join(tr.systemDir, "snaps", "aux-info.json"), os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) 394 if err != nil { 395 return err 396 } 397 defer f.Close() 398 enc := json.NewEncoder(f) 399 400 if err := enc.Encode(auxInfos); err != nil { 401 return err 402 } 403 404 return nil 405 }