package fs import ( "bytes" "io" "io/ioutil" "os" "gotest.tools/assert" ) // resourcePath is an adaptor for resources so they can be used as a Path // with PathOps. type resourcePath struct{} func (p *resourcePath) Path() string { return "manifest: not a filesystem path" } func (p *resourcePath) Remove() {} type filePath struct { resourcePath file *file } func (p *filePath) SetContent(content io.ReadCloser) { p.file.content = content } func (p *filePath) SetUID(uid uint32) { p.file.uid = uid } func (p *filePath) SetGID(gid uint32) { p.file.gid = gid } type directoryPath struct { resourcePath directory *directory } func (p *directoryPath) SetUID(uid uint32) { p.directory.uid = uid } func (p *directoryPath) SetGID(gid uint32) { p.directory.gid = gid } func (p *directoryPath) AddSymlink(path, target string) error { p.directory.items[path] = &symlink{ resource: newResource(defaultSymlinkMode), target: target, } return nil } func (p *directoryPath) AddFile(path string, ops ...PathOp) error { newFile := &file{resource: newResource(0)} p.directory.items[path] = newFile exp := &filePath{file: newFile} return applyPathOps(exp, ops) } func (p *directoryPath) AddDirectory(path string, ops ...PathOp) error { newDir := newDirectoryWithDefaults() p.directory.items[path] = newDir exp := &directoryPath{directory: newDir} return applyPathOps(exp, ops) } // Expected returns a Manifest with a directory structured created by ops. The // PathOp operations are applied to the manifest as expectations of the // filesystem structure and properties. func Expected(t assert.TestingT, ops ...PathOp) Manifest { if ht, ok := t.(helperT); ok { ht.Helper() } newDir := newDirectoryWithDefaults() e := &directoryPath{directory: newDir} assert.NilError(t, applyPathOps(e, ops)) return Manifest{root: newDir} } func newDirectoryWithDefaults() *directory { return &directory{ resource: newResource(defaultRootDirMode), items: make(map[string]dirEntry), } } func newResource(mode os.FileMode) resource { return resource{ mode: mode, uid: currentUID(), gid: currentGID(), } } func currentUID() uint32 { return normalizeID(os.Getuid()) } func currentGID() uint32 { return normalizeID(os.Getgid()) } func normalizeID(id int) uint32 { // ids will be -1 on windows if id < 0 { return 0 } return uint32(id) } var anyFileContent = ioutil.NopCloser(bytes.NewReader(nil)) // MatchAnyFileContent is a PathOp that updates a Manifest so that the file // at path may contain any content. func MatchAnyFileContent(path Path) error { if m, ok := path.(*filePath); ok { m.SetContent(anyFileContent) } return nil } const anyFile = "*" // MatchExtraFiles is a PathOp that updates a Manifest to allow a directory // to contain unspecified files. func MatchExtraFiles(path Path) error { if m, ok := path.(*directoryPath); ok { m.AddFile(anyFile) } return nil } // anyFileMode is represented by uint32_max const anyFileMode os.FileMode = 4294967295 // MatchAnyFileMode is a PathOp that updates a Manifest so that the resource at path // will match any file mode. func MatchAnyFileMode(path Path) error { if m, ok := path.(manifestResource); ok { m.SetMode(anyFileMode) } return nil }