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 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") 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, 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 { 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()) }