Files
snapd/snap/snapshots_test.go
T
Alberto Mardegan 739033c238 overlord: snapshot exclusions
* o/snapshotstate: exclude patterns mentioned in snapshots.yaml

When creating a snapshot, read the meta/snapshots.yaml file, which
contains a list of exclusion patterns like this:

    exclude:
      - $SNAP_DATA/*.bak
      - $SNAP_COMMON/cache/
      - $SNAP_USER_DATA/.gnupg
      - $SNAP_USER_COMMON/cache/

Based on the contents of this file, adjust the options passed to `tar`
so that the given patterns are excluded by the snapshot.

* tests: add spread test for snapshot exclusions

* o/sss/backend: remove unneeded "omitempty" yaml flag

* snapshotstate: tweak readSnapshotYaml()

* snapshotstate: simplify firstComponent detection

* snapshotstate,snap: refactor to be able to use ReadSnapshotYaml in `snap.pack`

* o/snapshotstate/backend: remove unused type definition

* snap/snapshots: Only support "*" as special character

* snap: validate snapshot manifest on installation

* snap: add tests for ReadSnapshotYamlFromSnapFile()

* snap: add docstrings

* snap/snapshots: correct manifest file name

Co-authored-by: Michael Vogt <mvo@ubuntu.com>
2022-04-04 17:28:25 +02:00

165 lines
4.7 KiB
Go

// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2022 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 snap_test
import (
"errors"
"io/ioutil"
"os"
"path/filepath"
. "gopkg.in/check.v1"
"github.com/snapcore/snapd/snap"
"github.com/snapcore/snapd/snap/snapdir"
)
type FakeContainer struct {
*snapdir.SnapDir
readFileInput string
readFileOutput []byte
readFileError error
}
func (s *FakeContainer) ReadFile(file string) (content []byte, err error) {
s.readFileInput = file
return s.readFileOutput, s.readFileError
}
type snapshotSuite struct{}
var _ = Suite(&snapshotSuite{})
func (s *snapshotSuite) TestReadSnapshotYamlOpenFails(c *C) {
var returnedError error
defer snap.MockOsOpen(func(string) (*os.File, error) {
return nil, returnedError
})()
info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}}
// Try a generic error, this is reported as such
returnedError = errors.New("Some error")
_, err := snap.ReadSnapshotYaml(info)
c.Check(err, ErrorMatches, "Some error")
// But if the file is not found, that's just a nil error
returnedError = os.ErrNotExist
_, err = snap.ReadSnapshotYaml(info)
c.Check(err, IsNil)
}
func (s *snapshotSuite) TestReadSnapshotYamlFromSnapFileFails(c *C) {
container := &FakeContainer{
readFileError: errors.New("cannot do stuff"),
}
opts, err := snap.ReadSnapshotYamlFromSnapFile(container)
c.Check(container.readFileInput, Equals, "meta/snapshots.yaml")
c.Check(opts, IsNil)
c.Check(err, ErrorMatches, "cannot do stuff")
}
func (s *snapshotSuite) TestReadSnapshotYamlFromSnapFileHappy(c *C) {
container := &FakeContainer{
readFileOutput: []byte("exclude:\n - $SNAP_DATA/dir"),
}
opts, err := snap.ReadSnapshotYamlFromSnapFile(container)
c.Check(container.readFileInput, Equals, "meta/snapshots.yaml")
c.Check(err, IsNil)
c.Check(opts, DeepEquals, &snap.SnapshotOptions{
ExcludePaths: []string{"$SNAP_DATA/dir"},
})
}
func (s *snapshotSuite) TestReadSnapshotYamlFailures(c *C) {
info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}}
for _, testData := range []struct {
contents string
expectedError string
}{
{
"", "cannot read snapshot manifest: EOF",
},
{
"invalid", "cannot read snapshot manifest: yaml: unmarshal errors:\n.*",
},
{
"exclude:\n - /home/ubuntu", "snapshot exclude path must start with one of.*",
},
{
"exclude:\n - $SNAP_COMMON_STUFF", "snapshot exclude path must start with one of.*",
},
{
"exclude:\n - $SNAP_DATA/../../meh", "snapshot exclude path not clean.*",
},
{
"exclude:\n - $SNAP_DATA/{one,two}", "snapshot exclude path contains invalid characters.*",
},
{
"exclude:\n - $SNAP_DATA/tree**", "snapshot exclude path contains invalid characters.*",
},
{
"exclude:\n - $SNAP_DATA/foo[12]", "snapshot exclude path contains invalid characters.*",
},
{
"exclude:\n - $SNAP_DATA/bar?", "snapshot exclude path contains invalid characters.*",
},
} {
manifestFile := filepath.Join(c.MkDir(), "snapshots.yaml")
err := ioutil.WriteFile(manifestFile, []byte(testData.contents), 0644)
c.Assert(err, IsNil)
defer snap.MockOsOpen(func(string) (*os.File, error) {
return os.Open(manifestFile)
})()
_, err = snap.ReadSnapshotYaml(info)
c.Check(err, ErrorMatches, testData.expectedError, Commentf("%s", testData.contents))
}
}
var snapshotYamlHappy = []byte(`exclude:
- $SNAP_DATA/one
- $SNAP_COMMON/two
- $SNAP_USER_DATA/three*
- $SNAP_USER_COMMON/fo*ur`)
func (s *snapshotSuite) TestReadSnapshotYamlHappy(c *C) {
manifestFile := filepath.Join(c.MkDir(), "snapshots.yaml")
err := ioutil.WriteFile(manifestFile, []byte(snapshotYamlHappy), 0644)
c.Assert(err, IsNil)
defer snap.MockOsOpen(func(path string) (*os.File, error) {
return os.Open(manifestFile)
})()
info := &snap.Info{SideInfo: snap.SideInfo{RealName: "hello-snap", Revision: snap.R(42), SnapID: "hello-id"}}
opts, err := snap.ReadSnapshotYaml(info)
c.Check(err, IsNil)
c.Check(opts.ExcludePaths, DeepEquals, []string{
"$SNAP_DATA/one",
"$SNAP_COMMON/two",
"$SNAP_USER_DATA/three*",
"$SNAP_USER_COMMON/fo*ur",
})
}