package main import ( "crypto/tls" "embed" "fmt" "io/fs" "log" "net/http" "os" "time" "github.com/cloudlena/adapters/logging" "github.com/cloudlena/s3manager/internal/app/s3manager" "github.com/gorilla/mux" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" "github.com/spf13/viper" ) //go:embed web/template var templateFS embed.FS //go:embed web/static var staticFS embed.FS type configuration struct { Endpoint string UseIam bool IamEndpoint string AccessKeyID string SecretAccessKey string Region string AllowDelete bool ForceDownload bool UseSSL bool SkipSSLVerification bool SkipSignature bool ListRecursive bool Port string Timeout int32 SseType string SseKey string } func parseConfiguration() configuration { var accessKeyID, secretAccessKey, iamEndpoint string viper.AutomaticEnv() viper.SetDefault("ENDPOINT", "s3.amazonaws.com") endpoint := viper.GetString("ENDPOINT") useIam := viper.GetBool("USE_IAM") if useIam { iamEndpoint = viper.GetString("IAM_ENDPOINT") } else { accessKeyID = viper.GetString("ACCESS_KEY_ID") if len(accessKeyID) == 0 { log.Fatal("please provide ACCESS_KEY_ID") } secretAccessKey = viper.GetString("SECRET_ACCESS_KEY") if len(secretAccessKey) == 0 { log.Fatal("please provide SECRET_ACCESS_KEY") } } region := viper.GetString("REGION") viper.SetDefault("ALLOW_DELETE", true) allowDelete := viper.GetBool("ALLOW_DELETE") viper.SetDefault("FORCE_DOWNLOAD", true) forceDownload := viper.GetBool("FORCE_DOWNLOAD") viper.SetDefault("USE_SSL", true) useSSL := viper.GetBool("USE_SSL") viper.SetDefault("SKIP_SSL_VERIFICATION", false) skipSSLVerification := viper.GetBool("SKIP_SSL_VERIFICATION") viper.SetDefault("SKIP_SIGNATURE", false) skipSignature := viper.GetBool("SKIP_SIGNATURE") listRecursive := viper.GetBool("LIST_RECURSIVE") viper.SetDefault("PORT", "8080") port := viper.GetString("PORT") viper.SetDefault("TIMEOUT", 600) timeout := viper.GetInt32("TIMEOUT") viper.SetDefault("SSE_TYPE", "") sseType := viper.GetString("SSE_TYPE") viper.SetDefault("SSE_KEY", "") sseKey := viper.GetString("SSE_KEY") return configuration{ Endpoint: endpoint, UseIam: useIam, IamEndpoint: iamEndpoint, AccessKeyID: accessKeyID, SecretAccessKey: secretAccessKey, Region: region, AllowDelete: allowDelete, ForceDownload: forceDownload, UseSSL: useSSL, SkipSSLVerification: skipSSLVerification, SkipSignature: skipSignature, ListRecursive: listRecursive, Port: port, Timeout: timeout, SseType: sseType, SseKey: sseKey, } } func main() { configuration := parseConfiguration() sseType := s3manager.SSEType{Type: configuration.SseType, Key: configuration.SseKey} serverTimeout := time.Duration(configuration.Timeout) * time.Second // Set up templates templates, err := fs.Sub(templateFS, "web/template") if err != nil { log.Fatal(err) } // Set up statics statics, err := fs.Sub(staticFS, "web/static") if err != nil { log.Fatal(err) } // Set up S3 client opts := &minio.Options{ Secure: configuration.UseSSL, } if configuration.UseIam { opts.Creds = credentials.NewIAM(configuration.IamEndpoint) } else { if (configuration.SkipSignature) { opts.Creds = credentials.NewStatic(configuration.AccessKeyID, configuration.SecretAccessKey, "", credentials.SignatureAnonymous) } else { opts.Creds = credentials.NewStaticV4(configuration.AccessKeyID, configuration.SecretAccessKey, "") } } if configuration.Region != "" { opts.Region = configuration.Region } if configuration.UseSSL && configuration.SkipSSLVerification { opts.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} //nolint:gosec } s3, err := minio.New(configuration.Endpoint, opts) if err != nil { log.Fatalln(fmt.Errorf("error creating s3 client: %w", err)) } // Set up router r := mux.NewRouter() r.Handle("/", http.RedirectHandler("/buckets", http.StatusPermanentRedirect)).Methods(http.MethodGet) r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.FS(statics)))).Methods(http.MethodGet) r.Handle("/buckets", s3manager.HandleBucketsView(s3, templates, configuration.AllowDelete)).Methods(http.MethodGet) r.PathPrefix("/buckets/").Handler(s3manager.HandleBucketView(s3, templates, configuration.AllowDelete, configuration.ListRecursive)).Methods(http.MethodGet) r.Handle("/api/buckets", s3manager.HandleCreateBucket(s3)).Methods(http.MethodPost) if configuration.AllowDelete { r.Handle("/api/buckets/{bucketName}", s3manager.HandleDeleteBucket(s3)).Methods(http.MethodDelete) } r.Handle("/api/buckets/{bucketName}/objects", s3manager.HandleCreateObject(s3, sseType)).Methods(http.MethodPost) r.Handle("/api/buckets/{bucketName}/objects/{objectName:.*}/url", s3manager.HandleGenerateUrl(s3)).Methods(http.MethodGet) r.Handle("/api/buckets/{bucketName}/objects/{objectName:.*}", s3manager.HandleGetObject(s3, configuration.ForceDownload)).Methods(http.MethodGet) if configuration.AllowDelete { r.Handle("/api/buckets/{bucketName}/objects/{objectName:.*}", s3manager.HandleDeleteObject(s3)).Methods(http.MethodDelete) } lr := logging.Handler(os.Stdout)(r) srv := &http.Server{ Addr: ":" + configuration.Port, Handler: lr, ReadTimeout: serverTimeout, WriteTimeout: serverTimeout, } log.Fatal(srv.ListenAndServe()) }