talos/pkg/archiver/walker.go
Andrey Smirnov bc9e0c0dba fix: re-implement upgrade (install) with preserve
For 0.6 -> 0.7 upgrade, in any case config.yaml is preserved and moved
from `/boot` to `/system/state`.

For single node upgrade, `EPHEMERAL` partition is not touched and other
partitions are re-created as needed.

Bump provision tests to 0.6/0.7 upgrades as we get closer to the new
release.

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
2020-10-28 07:25:26 -07:00

160 lines
3.4 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package archiver
import (
"context"
"os"
"path/filepath"
"strings"
)
// FileItem is unit of work for archive.
type FileItem struct {
FullPath string
RelPath string
FileInfo os.FileInfo
Link string
Error error
}
type walkerOptions struct {
skipRoot bool
maxRecurseDepth int
fnmatchPatterns []string
}
// WalkerOption configures Walker.
type WalkerOption func(*walkerOptions)
// WithSkipRoot skips root path if it's a directory.
func WithSkipRoot() WalkerOption {
return func(o *walkerOptions) {
o.skipRoot = true
}
}
// WithMaxRecurseDepth controls maximum recursion depth while walking file tree.
//
// Value of -1 disables depth control.
func WithMaxRecurseDepth(maxDepth int) WalkerOption {
return func(o *walkerOptions) {
o.maxRecurseDepth = maxDepth
}
}
// WithFnmatchPatterns filters results to match the patterns.
//
// Default is not to do any filtering.
func WithFnmatchPatterns(patterns ...string) WalkerOption {
return func(o *walkerOptions) {
o.fnmatchPatterns = append(o.fnmatchPatterns, patterns...)
}
}
// Walker provides a channel of file info/paths for archival
//
//nolint: gocyclo
func Walker(ctx context.Context, rootPath string, options ...WalkerOption) (<-chan FileItem, error) {
var opts walkerOptions
opts.maxRecurseDepth = -1
for _, o := range options {
o(&opts)
}
info, err := os.Lstat(rootPath)
if err != nil {
return nil, err
}
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
rootPath, err = filepath.EvalSymlinks(rootPath)
if err != nil {
return nil, err
}
}
ch := make(chan FileItem)
go func() {
defer close(ch)
err := filepath.Walk(rootPath, func(path string, fileInfo os.FileInfo, walkErr error) error {
item := FileItem{
FullPath: path,
FileInfo: fileInfo,
Error: walkErr,
}
if path == rootPath && !fileInfo.IsDir() {
// only one file
item.RelPath = filepath.Base(path)
} else if item.Error == nil {
item.RelPath, item.Error = filepath.Rel(rootPath, path)
}
if item.Error == nil && path == rootPath && opts.skipRoot && fileInfo.IsDir() {
// skip containing directory
return nil
}
if item.Error == nil && fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink {
item.Link, item.Error = os.Readlink(path)
}
if item.Error == nil && len(opts.fnmatchPatterns) > 0 {
matches := false
for _, pattern := range opts.fnmatchPatterns {
if matches, _ = filepath.Match(pattern, item.RelPath); matches { //nolint: errcheck
break
}
}
if !matches {
return nil
}
}
select {
case <-ctx.Done():
return ctx.Err()
case ch <- item:
}
if item.Error == nil && fileInfo.IsDir() && atMaxDepth(opts.maxRecurseDepth, rootPath, path) {
return filepath.SkipDir
}
return nil
})
if err != nil {
select {
case <-ctx.Done():
case ch <- FileItem{Error: err}:
}
}
}()
return ch, nil
}
// OSPathSeparator is the string version of the os.PathSeparator.
const OSPathSeparator = string(os.PathSeparator)
func atMaxDepth(max int, root, cur string) bool {
if max < 0 {
return false
}
if root == cur {
// always recurse the root directory
return false
}
return (strings.Count(cur, OSPathSeparator) - strings.Count(root, OSPathSeparator)) >= max
}