package s3 import ( "context" "errors" "fmt" neturl "net/url" "os" "time" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" "github.com/spf13/afero" ) var ( ErrUnsupported = errors.New("unsupported operation") ErrInvalidProtocol = errors.New("invalid protocol") ) type Fs struct { client *minio.Client bucket string // Configurables Timeout time.Duration } func NewFs(endpoint, bucket, accessKeyID, secretAccessKey string, useSSL bool) (*Fs, error) { // Initialize minio client object. client, err := minio.New(endpoint, &minio.Options{ Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), Secure: useSSL, }) if err != nil { return nil, err } return &Fs{ client: client, bucket: bucket, Timeout: 10 * time.Second, }, nil } func NewFsFromURL(url string) (*Fs, error) { r, err := neturl.ParseRequestURI(url) if err != nil { return nil, fmt.Errorf("failed to parse url: %w", err) } useSSL := false switch r.Scheme { case "http": case "https": useSSL = true default: return nil, ErrInvalidProtocol } endpoint := r.Host bucket := r.Path[1:] accessKeyId := r.User.Username() secretAccessKey, _ := r.User.Password() return NewFs(endpoint, bucket, accessKeyId, secretAccessKey, useSSL) } // The name of this FileSystem func (fs *Fs) Name() string { return "s3" } // Create creates a file in the filesystem, returning the file and an // error, if any happens. func (fs *Fs) Create(name string) (File, error) { panic("not implemented") } // Mkdir creates a directory in the filesystem, return an error if any // happens. func (fs *Fs) Mkdir(name string, perm os.FileMode) error { return ErrUnsupported } // MkdirAll creates a directory path and all parents that does not exist // yet. func (fs *Fs) MkdirAll(path string, perm os.FileMode) error { return ErrUnsupported } // Open opens a file, returning it or an error, if any happens. func (fs *Fs) Open(name string) (File, error) { panic("not implemented") } // OpenFile opens a file using the given flags and the given mode. func (fs *Fs) OpenFile(name string, flag int, perm os.FileMode) (File, error) { panic("not implemented") } // Remove removes a file identified by name, returning an error, if any // happens. func (fs *Fs) Remove(name string) error { panic("not implemented") } // RemoveAll removes a directory path and any children it contains. It // does not fail if the path does not exist (return nil). func (fs *Fs) RemoveAll(path string) error { panic("not implemented") } // Rename renames a file. func (fs *Fs) Rename(oldname, newname string) error { panic("not implemented") } // Stat returns a FileInfo describing the named file, or an error, if any // happens. func (fs *Fs) Stat(name string) (os.FileInfo, error) { ctx, cancel := fs.contextWithTimeout() defer cancel() info, err := fs.client.StatObject(ctx, fs.bucket, name, minio.GetObjectOptions{}) if err != nil { return nil, transformError(err) } return transformObjectInfo(info), nil } // Chmod changes the mode of the named file to mode. func (fs *Fs) Chmod(name string, mode os.FileMode) error { return ErrUnsupported } // Chown changes the uid and gid of the named file. func (fs *Fs) Chown(name string, uid, gid int) error { return ErrUnsupported } // Chtimes changes the access and modification times of the named file func (fs *Fs) Chtimes(name string, atime time.Time, mtime time.Time) error { return ErrUnsupported } func (fs *Fs) contextWithTimeout() (context.Context, context.CancelFunc) { return context.WithTimeout(context.Background(), fs.Timeout) } func transformError(err error) error { resp, ok := err.(minio.ErrorResponse) if !ok { return err } switch resp.Code { case "NoSuchKey": return afero.ErrFileNotFound case "EntityTooLarge": return afero.ErrTooLarge } return err }