github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/seed/seedtest/seedtest.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-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 seedtest 21 22 import ( 23 "fmt" 24 "io" 25 "os" 26 "path/filepath" 27 "time" 28 29 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/asserts" 32 "github.com/snapcore/snapd/asserts/assertstest" 33 "github.com/snapcore/snapd/osutil" 34 "github.com/snapcore/snapd/seed/seedwriter" 35 "github.com/snapcore/snapd/snap" 36 "github.com/snapcore/snapd/snap/naming" 37 "github.com/snapcore/snapd/snap/snapfile" 38 "github.com/snapcore/snapd/snap/snaptest" 39 ) 40 41 // SeedSnaps helps creating snaps for a seed. 42 type SeedSnaps struct { 43 StoreSigning *assertstest.StoreStack 44 Brands *assertstest.SigningAccounts 45 46 snaps map[string]string 47 infos map[string]*snap.Info 48 49 snapAssertNow time.Time 50 51 snapRevs map[string]*asserts.SnapRevision 52 } 53 54 // SetupAssertSigning initializes StoreSigning for storeBrandID and Brands. 55 func (ss *SeedSnaps) SetupAssertSigning(storeBrandID string) { 56 ss.StoreSigning = assertstest.NewStoreStack(storeBrandID, nil) 57 ss.Brands = assertstest.NewSigningAccounts(ss.StoreSigning) 58 } 59 60 func (ss *SeedSnaps) AssertedSnapID(snapName string) string { 61 snapID := naming.WellKnownSnapID(snapName) 62 if snapID != "" { 63 return snapID 64 } 65 return snaptest.AssertedSnapID(snapName) 66 } 67 68 func (ss *SeedSnaps) snapAssertionNow() time.Time { 69 if ss.snapAssertNow.IsZero() { 70 return time.Now() 71 } 72 return ss.snapAssertNow 73 } 74 75 func (ss *SeedSnaps) SetSnapAssertionNow(t time.Time) { 76 ss.snapAssertNow = t 77 } 78 79 func (ss *SeedSnaps) MakeAssertedSnap(c *C, snapYaml string, files [][]string, revision snap.Revision, developerID string, dbs ...*asserts.Database) (*asserts.SnapDeclaration, *asserts.SnapRevision) { 80 info, err := snap.InfoFromSnapYaml([]byte(snapYaml)) 81 c.Assert(err, IsNil) 82 snapName := info.SnapName() 83 84 snapFile := snaptest.MakeTestSnapWithFiles(c, snapYaml, files) 85 86 snapID := ss.AssertedSnapID(snapName) 87 declA, err := ss.StoreSigning.Sign(asserts.SnapDeclarationType, map[string]interface{}{ 88 "series": "16", 89 "snap-id": snapID, 90 "publisher-id": developerID, 91 "snap-name": snapName, 92 "timestamp": ss.snapAssertionNow().UTC().Format(time.RFC3339), 93 }, nil, "") 94 c.Assert(err, IsNil) 95 96 sha3_384, size, err := asserts.SnapFileSHA3_384(snapFile) 97 c.Assert(err, IsNil) 98 99 revA, err := ss.StoreSigning.Sign(asserts.SnapRevisionType, map[string]interface{}{ 100 "snap-sha3-384": sha3_384, 101 "snap-size": fmt.Sprintf("%d", size), 102 "snap-id": snapID, 103 "developer-id": developerID, 104 "snap-revision": revision.String(), 105 "timestamp": ss.snapAssertionNow().UTC().Format(time.RFC3339), 106 }, nil, "") 107 c.Assert(err, IsNil) 108 109 if !revision.Unset() { 110 info.SnapID = snapID 111 info.Revision = revision 112 } 113 114 for _, db := range dbs { 115 err := db.Add(declA) 116 c.Assert(err, IsNil) 117 err = db.Add(revA) 118 c.Assert(err, IsNil) 119 } 120 121 if ss.snaps == nil { 122 ss.snaps = make(map[string]string) 123 ss.infos = make(map[string]*snap.Info) 124 ss.snapRevs = make(map[string]*asserts.SnapRevision) 125 } 126 127 ss.snaps[snapName] = snapFile 128 info.SideInfo.RealName = snapName 129 ss.infos[snapName] = info 130 snapDecl := declA.(*asserts.SnapDeclaration) 131 snapRev := revA.(*asserts.SnapRevision) 132 ss.snapRevs[snapName] = snapRev 133 134 return snapDecl, snapRev 135 } 136 137 func (ss *SeedSnaps) AssertedSnap(snapName string) (snapFile string) { 138 return ss.snaps[snapName] 139 } 140 141 func (ss *SeedSnaps) AssertedSnapInfo(snapName string) *snap.Info { 142 return ss.infos[snapName] 143 } 144 145 func (ss *SeedSnaps) AssertedSnapRevision(snapName string) *asserts.SnapRevision { 146 return ss.snapRevs[snapName] 147 } 148 149 // TestingSeed16 helps setting up a populated Core 16/18 testing seed. 150 type TestingSeed16 struct { 151 SeedSnaps 152 153 SeedDir string 154 } 155 156 func (s *TestingSeed16) SnapsDir() string { 157 return filepath.Join(s.SeedDir, "snaps") 158 } 159 160 func (s *TestingSeed16) AssertsDir() string { 161 return filepath.Join(s.SeedDir, "assertions") 162 } 163 164 func (s *TestingSeed16) MakeAssertedSnap(c *C, snapYaml string, files [][]string, revision snap.Revision, developerID string) (snapFname string, snapDecl *asserts.SnapDeclaration, snapRev *asserts.SnapRevision) { 165 decl, rev := s.SeedSnaps.MakeAssertedSnap(c, snapYaml, files, revision, developerID) 166 167 snapFile := s.snaps[decl.SnapName()] 168 169 snapFname = filepath.Base(snapFile) 170 targetFile := filepath.Join(s.SnapsDir(), snapFname) 171 err := os.Rename(snapFile, targetFile) 172 c.Assert(err, IsNil) 173 174 return snapFname, decl, rev 175 } 176 177 func (s *TestingSeed16) MakeModelAssertionChain(brandID, model string, extras ...map[string]interface{}) []asserts.Assertion { 178 assertChain := []asserts.Assertion{} 179 modelA := s.Brands.Model(brandID, model, extras...) 180 181 assertChain = append(assertChain, s.Brands.Account(modelA.BrandID())) 182 assertChain = append(assertChain, s.Brands.AccountKey(modelA.BrandID())) 183 assertChain = append(assertChain, modelA) 184 185 storeAccountKey := s.StoreSigning.StoreAccountKey("") 186 assertChain = append(assertChain, storeAccountKey) 187 return assertChain 188 } 189 190 func (s *TestingSeed16) WriteAssertions(fn string, assertions ...asserts.Assertion) { 191 fn = filepath.Join(s.AssertsDir(), fn) 192 WriteAssertions(fn, assertions...) 193 } 194 195 func WriteAssertions(fn string, assertions ...asserts.Assertion) { 196 f, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY, 0644) 197 if err != nil { 198 panic(err) 199 } 200 defer f.Close() 201 enc := asserts.NewEncoder(f) 202 for _, a := range assertions { 203 err := enc.Encode(a) 204 if err != nil { 205 panic(err) 206 } 207 } 208 } 209 210 func ReadAssertions(c *C, fn string) []asserts.Assertion { 211 f, err := os.Open(fn) 212 c.Assert(err, IsNil) 213 214 var as []asserts.Assertion 215 dec := asserts.NewDecoder(f) 216 for { 217 a, err := dec.Decode() 218 if err == io.EOF { 219 break 220 } 221 c.Assert(err, IsNil) 222 as = append(as, a) 223 } 224 225 return as 226 } 227 228 // TestingSeed20 helps setting up a populated Core 20 testing seed directory. 229 type TestingSeed20 struct { 230 SeedSnaps 231 232 SeedDir string 233 } 234 235 func (s *TestingSeed20) MakeSeed(c *C, label, brandID, modelID string, modelHeaders map[string]interface{}, optSnaps []*seedwriter.OptionsSnap) *asserts.Model { 236 model := s.Brands.Model(brandID, modelID, modelHeaders) 237 238 db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{ 239 Backstore: asserts.NewMemoryBackstore(), 240 Trusted: s.StoreSigning.Trusted, 241 }) 242 c.Assert(err, IsNil) 243 244 retrieve := func(ref *asserts.Ref) (asserts.Assertion, error) { 245 return ref.Resolve(s.StoreSigning.Find) 246 } 247 newFetcher := func(save func(asserts.Assertion) error) asserts.Fetcher { 248 save2 := func(a asserts.Assertion) error { 249 // for checking 250 err := db.Add(a) 251 if err != nil { 252 if _, ok := err.(*asserts.RevisionError); ok { 253 return nil 254 } 255 return err 256 } 257 return save(a) 258 } 259 return asserts.NewFetcher(db, retrieve, save2) 260 } 261 assertstest.AddMany(s.StoreSigning, s.Brands.AccountsAndKeys(brandID)...) 262 263 opts := seedwriter.Options{ 264 SeedDir: s.SeedDir, 265 Label: label, 266 } 267 w, err := seedwriter.New(model, &opts) 268 c.Assert(err, IsNil) 269 270 err = w.SetOptionsSnaps(optSnaps) 271 c.Assert(err, IsNil) 272 273 rf, err := w.Start(db, newFetcher) 274 c.Assert(err, IsNil) 275 276 localSnaps, err := w.LocalSnaps() 277 c.Assert(err, IsNil) 278 279 for _, sn := range localSnaps { 280 si, aRefs, err := seedwriter.DeriveSideInfo(sn.Path, rf, db) 281 if !asserts.IsNotFound(err) { 282 c.Assert(err, IsNil) 283 } 284 f, err := snapfile.Open(sn.Path) 285 c.Assert(err, IsNil) 286 info, err := snap.ReadInfoFromSnapFile(f, si) 287 c.Assert(err, IsNil) 288 w.SetInfo(sn, info) 289 sn.ARefs = aRefs 290 } 291 292 err = w.InfoDerived() 293 c.Assert(err, IsNil) 294 295 for { 296 snaps, err := w.SnapsToDownload() 297 c.Assert(err, IsNil) 298 299 for _, sn := range snaps { 300 name := sn.SnapName() 301 302 info := s.AssertedSnapInfo(name) 303 c.Assert(info, NotNil, Commentf("no snap info for %q", name)) 304 err := w.SetInfo(sn, info) 305 c.Assert(err, IsNil) 306 307 prev := len(rf.Refs()) 308 err = rf.Save(s.snapRevs[name]) 309 c.Assert(err, IsNil) 310 sn.ARefs = rf.Refs()[prev:] 311 312 if _, err := os.Stat(sn.Path); err == nil { 313 // snap is already present 314 continue 315 } 316 317 err = os.Rename(s.AssertedSnap(name), sn.Path) 318 c.Assert(err, IsNil) 319 } 320 321 complete, err := w.Downloaded() 322 c.Assert(err, IsNil) 323 if complete { 324 break 325 } 326 } 327 328 copySnap := func(name, src, dst string) error { 329 return osutil.CopyFile(src, dst, 0) 330 } 331 332 err = w.SeedSnaps(copySnap) 333 c.Assert(err, IsNil) 334 335 err = w.WriteMeta() 336 c.Assert(err, IsNil) 337 338 return model 339 }