e2etest/newe2e_resource_managers_local.go (305 lines of code) (raw):

package e2etest import ( "bytes" "fmt" "github.com/Azure/azure-storage-azcopy/v10/cmd" "github.com/Azure/azure-storage-azcopy/v10/common" "github.com/Azure/azure-storage-azcopy/v10/ste" "github.com/google/uuid" "io" "io/fs" "os" "path/filepath" "time" ) // enforce interface compliance at compile time func init() { void := func(_ ...any) {} void( ContainerResourceManager(&LocalContainerResourceManager{}), ObjectResourceManager(&LocalObjectResourceManager{}), ) } func NewLocalContainer(a Asserter) ContainerResourceManager { a.HelperMarker().Helper() if d, ok := a.(DryrunAsserter); ok && d.Dryrun() { return &MockContainerResourceManager{ containerName: "mockContainer", overrideLocation: common.ELocation.Local(), } } tmp := os.TempDir() dirName := uuid.NewString() return &LocalContainerResourceManager{ RootPath: filepath.Join(tmp, dirName), } } // LocalContainerResourceManager is effectively just the root temp folder for a transfer. type LocalContainerResourceManager struct { RootPath string } func (l *LocalContainerResourceManager) Location() common.Location { return common.ELocation.Local() } func (l *LocalContainerResourceManager) Level() cmd.LocationLevel { return cmd.ELocationLevel.Container() } func (l *LocalContainerResourceManager) URI(o ...GetURIOptions) string { base := l.RootPath base = addWildCard(base, o...) opts := FirstOrZero(o) if opts.LocalOpts.PreferUNCPath { base = common.ToExtendedPath(base) } return base } func (l *LocalContainerResourceManager) Parent() ResourceManager { return nil } func (l *LocalContainerResourceManager) Account() AccountResourceManager { return nil } func (l *LocalContainerResourceManager) Canon() string { return fmt.Sprintf("accountless/local/%s", l.ContainerName()) } func (l *LocalContainerResourceManager) ContainerName() string { return filepath.Base(l.RootPath) } func (l *LocalContainerResourceManager) Create(a Asserter, props ContainerProperties) { a.HelperMarker().Helper() err := os.Mkdir(l.RootPath, 0777) if !os.IsExist(err) { a.NoError("Create local root directory", err) } TrackResourceCreation(a, l) } func (l *LocalContainerResourceManager) GetProperties(a Asserter) ContainerProperties { return ContainerProperties{} } func (l *LocalContainerResourceManager) Delete(a Asserter) { a.HelperMarker().Helper() time.Sleep(time.Second) err := os.RemoveAll(l.RootPath) if !os.IsNotExist(err) { a.NoError("Delete local root directory", err) } } func (l *LocalContainerResourceManager) ListObjects(a Asserter, prefixOrDirectory string, recursive bool) map[string]ObjectProperties { a.HelperMarker().Helper() out := make(map[string]ObjectProperties) root := l.GetObject(a, prefixOrDirectory, common.EEntityType.Folder()).(*LocalObjectResourceManager) err := filepath.WalkDir(l.RootPath, func(path string, d fs.DirEntry, err error) error { relPath, err := root.getRelPath(path) if err != nil { return err } out[relPath] = root.getChildObject(relPath, common.Iff(d.IsDir(), common.EEntityType.Folder(), common.EEntityType.File())).GetProperties(a) return common.Iff(d.IsDir() && !recursive, filepath.SkipDir, nil) }) a.NoError("failed to walk", err) return out } func (l *LocalContainerResourceManager) GetObject(a Asserter, path string, eType common.EntityType) ObjectResourceManager { return &LocalObjectResourceManager{ container: l, entityType: eType, objectPath: path, } } func (l *LocalContainerResourceManager) Exists() bool { _, err := os.Stat(l.RootPath) return err == nil } type LocalObjectResourceManager struct { container *LocalContainerResourceManager entityType common.EntityType // objectPath and rawPath are mutually exclusive fields. objectPath string rawPath string } func (l *LocalObjectResourceManager) ContainerName() string { if l.container != nil { return filepath.Base(l.container.RootPath) } return "containerless" } func (l *LocalObjectResourceManager) ObjectName() string { if l.objectPath != "" { return l.objectPath } else { return filepath.Base(l.rawPath) } } type localSMBPropertiesManager interface { GetSDDL(Asserter) string PutSDDL(sddlstr string, a Asserter) GetSMBProperties(Asserter) ste.TypedSMBPropertyHolder PutSMBProperties(Asserter, ste.TypedSMBPropertyHolder) } func (l *LocalObjectResourceManager) getChildObject(relPath string, entityType common.EntityType) *LocalObjectResourceManager { if l.objectPath != "" { // We have a "container", this is relative newPath := filepath.Join(l.objectPath, relPath) return &LocalObjectResourceManager{ container: l.container, entityType: entityType, objectPath: newPath, } } else { newPath := filepath.Join(l.rawPath, relPath) return &LocalObjectResourceManager{ entityType: entityType, rawPath: newPath, } } } func (l *LocalObjectResourceManager) getWorkingPath() string { if l.objectPath != "" && l.rawPath != "" { panic("objectPath and rawPath are mutually exclusive fields, and should never be filled at the same time.") } if l.rawPath != "" { return l.rawPath } if l.container == nil { panic("objectPath (relative) must have a container as a parent.") } // l.objectPath can be "", indicating it is the folder at the root of the container. return filepath.Join(l.container.RootPath, l.objectPath) } func (l *LocalObjectResourceManager) getRelPath(fullPath string) (string, error) { rootPath := "" if l.objectPath != "" { rootPath = filepath.Join(l.container.RootPath, l.objectPath) } else { rootPath = l.rawPath } return filepath.Rel(rootPath, fullPath) } func (l *LocalObjectResourceManager) Location() common.Location { return common.ELocation.Local() } func (l *LocalObjectResourceManager) Level() cmd.LocationLevel { return cmd.ELocationLevel.Object() } func (l *LocalObjectResourceManager) URI(o ...GetURIOptions) string { base := filepath.Join(l.container.RootPath, l.objectPath) base = addWildCard(base, o...) opts := FirstOrZero(o) if opts.LocalOpts.PreferUNCPath { base = common.ToExtendedPath(base) } return base } func (l *LocalObjectResourceManager) Parent() ResourceManager { return l.container } func (l *LocalObjectResourceManager) Account() AccountResourceManager { return nil } func (l *LocalObjectResourceManager) Canon() string { if l.container != nil { return l.container.Canon() + "/" + l.objectPath } else { return fmt.Sprintf("accountless/local/containerless/%s", l.rawPath) } } func (l *LocalObjectResourceManager) EntityType() common.EntityType { return l.entityType } func (l *LocalObjectResourceManager) CreateParents(a Asserter) { if l.container != nil { l.container.Create(a, ContainerProperties{}) } err := os.MkdirAll(filepath.Dir(l.getWorkingPath()), 0775) a.NoError("mkdirall", err) } func (l *LocalObjectResourceManager) Create(a Asserter, body ObjectContentContainer, properties ObjectProperties) { a.HelperMarker().Helper() a.AssertNow("Object must be file to have content", Equal{}) l.CreateParents(a) if l.entityType == common.EEntityType.File() { f, err := os.OpenFile(l.getWorkingPath(), os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0774) a.NoError("Open file", err) defer func(f *os.File) { err := f.Close() a.NoError("Close file", err) }(f) _, err = io.Copy(f, body.Reader()) a.NoError("Write file", err) } else if l.entityType == common.EEntityType.Folder() { err := os.Mkdir(l.getWorkingPath(), 0775) a.NoError("Mkdir", err) } l.SetObjectProperties(a, properties) TrackResourceCreation(a, l) } func (l *LocalObjectResourceManager) Delete(a Asserter) { a.HelperMarker().Helper() err := os.RemoveAll(l.getWorkingPath()) if !os.IsNotExist(err) { a.NoError("Could not remove local object "+l.getWorkingPath(), err) } } func (l *LocalObjectResourceManager) ListChildren(a Asserter, recursive bool) map[string]ObjectProperties { a.HelperMarker().Helper() a.AssertNow("Entity type must be folder to have children", Equal{}, l.entityType, common.EEntityType.Folder()) out := make(map[string]ObjectProperties) err := filepath.WalkDir(l.getWorkingPath(), func(path string, d fs.DirEntry, err error) error { relPath, err := l.getRelPath(path) if err != nil { return err } out[relPath] = l.getChildObject(relPath, common.Iff(d.IsDir(), common.EEntityType.Folder(), common.EEntityType.File())).GetProperties(a) return common.Iff(d.IsDir() && !recursive, filepath.SkipDir, nil) }) a.NoError("failed to walk", err) return out } func (l *LocalObjectResourceManager) GetProperties(a Asserter) ObjectProperties { a.HelperMarker().Helper() stats, err := os.Stat(l.getWorkingPath()) if err != nil { // Prevent nil dereferences a.NoError("failed to get stat", err) return ObjectProperties{} } lmt := common.Iff(stats == nil, nil, PtrOf(stats.ModTime())) out := ObjectProperties{ LastModifiedTime: lmt, } // OS-triggered code, implemented in newe2e_resource_managers_local_windows.go if smb, ok := any(l).(localSMBPropertiesManager); ok { props := smb.GetSMBProperties(a) attr, err := props.FileAttributes() a.NoError("get attributes", err) perms := smb.GetSDDL(a) out.FileProperties.FileAttributes = PtrOf(attr.String()) out.FileProperties.FileCreationTime = PtrOf(props.FileCreationTime()) out.FileProperties.FileLastWriteTime = PtrOf(props.FileLastWriteTime()) out.FileProperties.FilePermissions = common.Iff(perms == "", nil, &perms) } return out } func (l *LocalObjectResourceManager) SetHTTPHeaders(a Asserter, h contentHeaders) { // no-op on local } func (l *LocalObjectResourceManager) SetMetadata(a Asserter, metadata common.Metadata) { // no-op on local // todo: we could worry about xAttr on Linux, etc. but we don't officially support any of that outside of specific features (e.g. hash based sync) } func (l *LocalObjectResourceManager) SetObjectProperties(a Asserter, props ObjectProperties) { a.HelperMarker().Helper() // todo: set SMB properties if smb, ok := any(l).(localSMBPropertiesManager); ok { if props.FileProperties.FilePermissions != nil { smb.PutSDDL(*props.FileProperties.FilePermissions, a) } } } func (l *LocalObjectResourceManager) Download(a Asserter) io.ReadSeeker { a.HelperMarker().Helper() a.AssertNow("Entity type must be file to have content to download", Equal{}, l.entityType, common.EEntityType.File()) f, err := os.Open(l.getWorkingPath()) a.NoError("open file", err) defer func(f *os.File) { err = f.Close() a.NoError("Close file", err) }(f) buf := &bytes.Buffer{} _, err = io.Copy(buf, f) a.NoError("read file", err) return bytes.NewReader(buf.Bytes()) } func (l *LocalObjectResourceManager) Exists() bool { _, err := os.Stat(l.getWorkingPath()) return err == nil }