Full Mattermost server source with integrated Community Enterprise features. Includes vendor directory for offline/air-gapped builds. Structure: - enterprise-impl/: Enterprise feature implementations - enterprise-community/: Init files that register implementations - enterprise/: Bridge imports (community_imports.go) - vendor/: All dependencies for offline builds Build (online): go build ./cmd/mattermost Build (offline/air-gapped): go build -mod=vendor ./cmd/mattermost 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
180 lines
7.0 KiB
Go
180 lines
7.0 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package filestore
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
const (
|
|
driverS3 = "amazons3"
|
|
driverLocal = "local"
|
|
)
|
|
|
|
type ReadCloseSeeker interface {
|
|
io.ReadCloser
|
|
io.Seeker
|
|
}
|
|
|
|
type FileBackend interface {
|
|
DriverName() string
|
|
TestConnection() error
|
|
|
|
Reader(path string) (ReadCloseSeeker, error)
|
|
ReadFile(path string) ([]byte, error)
|
|
FileExists(path string) (bool, error)
|
|
FileSize(path string) (int64, error)
|
|
CopyFile(oldPath, newPath string) error
|
|
MoveFile(oldPath, newPath string) error
|
|
WriteFile(fr io.Reader, path string) (int64, error)
|
|
AppendFile(fr io.Reader, path string) (int64, error)
|
|
RemoveFile(path string) error
|
|
FileModTime(path string) (time.Time, error)
|
|
|
|
ListDirectory(path string) ([]string, error)
|
|
ListDirectoryRecursively(path string) ([]string, error)
|
|
RemoveDirectory(path string) error
|
|
ZipReader(path string, deflate bool) (io.ReadCloser, error)
|
|
}
|
|
|
|
type FileBackendWithLinkGenerator interface {
|
|
GeneratePublicLink(path string) (string, time.Duration, error)
|
|
}
|
|
|
|
type FileBackendSettings struct {
|
|
DriverName string
|
|
Directory string
|
|
AmazonS3AccessKeyId string
|
|
AmazonS3SecretAccessKey string
|
|
AmazonS3Bucket string
|
|
AmazonS3PathPrefix string
|
|
AmazonS3Region string
|
|
AmazonS3Endpoint string
|
|
AmazonS3SSL bool
|
|
AmazonS3SignV2 bool
|
|
AmazonS3SSE bool
|
|
AmazonS3Trace bool
|
|
SkipVerify bool
|
|
AmazonS3RequestTimeoutMilliseconds int64
|
|
AmazonS3PresignExpiresSeconds int64
|
|
AmazonS3UploadPartSizeBytes int64
|
|
AmazonS3StorageClass string
|
|
}
|
|
|
|
func NewFileBackendSettingsFromConfig(fileSettings *model.FileSettings, enableComplianceFeature bool, skipVerify bool) FileBackendSettings {
|
|
if *fileSettings.DriverName == model.ImageDriverLocal {
|
|
return FileBackendSettings{
|
|
DriverName: *fileSettings.DriverName,
|
|
Directory: *fileSettings.Directory,
|
|
}
|
|
}
|
|
return FileBackendSettings{
|
|
DriverName: *fileSettings.DriverName,
|
|
AmazonS3AccessKeyId: *fileSettings.AmazonS3AccessKeyId,
|
|
AmazonS3SecretAccessKey: *fileSettings.AmazonS3SecretAccessKey,
|
|
AmazonS3Bucket: *fileSettings.AmazonS3Bucket,
|
|
AmazonS3PathPrefix: *fileSettings.AmazonS3PathPrefix,
|
|
AmazonS3Region: *fileSettings.AmazonS3Region,
|
|
AmazonS3Endpoint: *fileSettings.AmazonS3Endpoint,
|
|
AmazonS3SSL: fileSettings.AmazonS3SSL == nil || *fileSettings.AmazonS3SSL,
|
|
AmazonS3SignV2: fileSettings.AmazonS3SignV2 != nil && *fileSettings.AmazonS3SignV2,
|
|
AmazonS3SSE: fileSettings.AmazonS3SSE != nil && *fileSettings.AmazonS3SSE && enableComplianceFeature,
|
|
AmazonS3Trace: fileSettings.AmazonS3Trace != nil && *fileSettings.AmazonS3Trace,
|
|
AmazonS3RequestTimeoutMilliseconds: *fileSettings.AmazonS3RequestTimeoutMilliseconds,
|
|
SkipVerify: skipVerify,
|
|
AmazonS3UploadPartSizeBytes: *fileSettings.AmazonS3UploadPartSizeBytes,
|
|
AmazonS3StorageClass: *fileSettings.AmazonS3StorageClass,
|
|
}
|
|
}
|
|
|
|
func NewExportFileBackendSettingsFromConfig(fileSettings *model.FileSettings, enableComplianceFeature bool, skipVerify bool) FileBackendSettings {
|
|
if *fileSettings.ExportDriverName == model.ImageDriverLocal {
|
|
return FileBackendSettings{
|
|
DriverName: *fileSettings.ExportDriverName,
|
|
Directory: *fileSettings.ExportDirectory,
|
|
}
|
|
}
|
|
return FileBackendSettings{
|
|
DriverName: *fileSettings.ExportDriverName,
|
|
AmazonS3AccessKeyId: *fileSettings.ExportAmazonS3AccessKeyId,
|
|
AmazonS3SecretAccessKey: *fileSettings.ExportAmazonS3SecretAccessKey,
|
|
AmazonS3Bucket: *fileSettings.ExportAmazonS3Bucket,
|
|
AmazonS3PathPrefix: *fileSettings.ExportAmazonS3PathPrefix,
|
|
AmazonS3Region: *fileSettings.ExportAmazonS3Region,
|
|
AmazonS3Endpoint: *fileSettings.ExportAmazonS3Endpoint,
|
|
AmazonS3SSL: fileSettings.ExportAmazonS3SSL == nil || *fileSettings.ExportAmazonS3SSL,
|
|
AmazonS3SignV2: fileSettings.ExportAmazonS3SignV2 != nil && *fileSettings.ExportAmazonS3SignV2,
|
|
AmazonS3SSE: fileSettings.ExportAmazonS3SSE != nil && *fileSettings.ExportAmazonS3SSE && enableComplianceFeature,
|
|
AmazonS3Trace: fileSettings.ExportAmazonS3Trace != nil && *fileSettings.ExportAmazonS3Trace,
|
|
AmazonS3RequestTimeoutMilliseconds: *fileSettings.ExportAmazonS3RequestTimeoutMilliseconds,
|
|
AmazonS3PresignExpiresSeconds: *fileSettings.ExportAmazonS3PresignExpiresSeconds,
|
|
AmazonS3UploadPartSizeBytes: *fileSettings.ExportAmazonS3UploadPartSizeBytes,
|
|
AmazonS3StorageClass: *fileSettings.ExportAmazonS3StorageClass,
|
|
SkipVerify: skipVerify,
|
|
}
|
|
}
|
|
|
|
func (settings *FileBackendSettings) CheckMandatoryS3Fields() error {
|
|
if settings.AmazonS3Bucket == "" {
|
|
return errors.New("missing s3 bucket settings")
|
|
}
|
|
|
|
// if S3 endpoint is not set call the set defaults to set that
|
|
if settings.AmazonS3Endpoint == "" {
|
|
settings.AmazonS3Endpoint = "s3.amazonaws.com"
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// NewFileBackend creates a new file backend
|
|
func NewFileBackend(settings FileBackendSettings) (FileBackend, error) {
|
|
return newFileBackend(settings, true)
|
|
}
|
|
|
|
// NewExportFileBackend creates a new file backend for exports, that will not attempt to use bifrost.
|
|
func NewExportFileBackend(settings FileBackendSettings) (FileBackend, error) {
|
|
return newFileBackend(settings, false)
|
|
}
|
|
|
|
func newFileBackend(settings FileBackendSettings, canBeCloud bool) (FileBackend, error) {
|
|
switch settings.DriverName {
|
|
case driverS3:
|
|
newBackendFn := NewS3FileBackend
|
|
if !canBeCloud {
|
|
newBackendFn = NewS3FileBackendWithoutBifrost
|
|
}
|
|
backend, err := newBackendFn(settings)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "unable to connect to the s3 backend")
|
|
}
|
|
return backend, nil
|
|
case driverLocal:
|
|
return &LocalFileBackend{
|
|
directory: settings.Directory,
|
|
}, nil
|
|
}
|
|
return nil, errors.New("no valid filestorage driver found")
|
|
}
|
|
|
|
// TryWriteFileContext checks if the file backend supports context writes and passes the context in that case.
|
|
// Should the file backend not support contexts, it just calls WriteFile instead. This can be used to disable
|
|
// the timeouts for long writes (like exports).
|
|
func TryWriteFileContext(ctx context.Context, fb FileBackend, fr io.Reader, path string) (int64, error) {
|
|
type ContextWriter interface {
|
|
WriteFileContext(context.Context, io.Reader, string) (int64, error)
|
|
}
|
|
|
|
if cw, ok := fb.(ContextWriter); ok {
|
|
return cw.WriteFileContext(ctx, fr, path)
|
|
}
|
|
|
|
return fb.WriteFile(fr, path)
|
|
}
|