diff --git a/.github/dependabot.yml b/.github/dependabot.yml index be5e9233..975c9586 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -68,6 +68,12 @@ updates: - "๐Ÿค– Dependencies" schedule: interval: "daily" + - package-ecosystem: "gomod" + directory: "/minio/" # Location of package manifests + labels: + - "๐Ÿค– Dependencies" + schedule: + interval: "daily" - package-ecosystem: "gomod" directory: "/mongodb/" # Location of package manifests labels: diff --git a/.github/release-drafter-minio.yml b/.github/release-drafter-minio.yml new file mode 100644 index 00000000..39f0e75e --- /dev/null +++ b/.github/release-drafter-minio.yml @@ -0,0 +1,50 @@ +name-template: 'Minio - v$RESOLVED_VERSION' +tag-template: 'minio/v$RESOLVED_VERSION' +tag-prefix: minio/v +include-paths: + - minio +categories: + - title: 'โ— Breaking Changes' + labels: + - 'โ— BreakingChange' + - title: '๐Ÿš€ New' + labels: + - 'โœ๏ธ Feature' + - title: '๐Ÿงน Updates' + labels: + - '๐Ÿงน Updates' + - '๐Ÿค– Dependencies' + - title: '๐Ÿ› Fixes' + labels: + - 'โ˜ข๏ธ Bug' + - title: '๐Ÿ“š Documentation' + labels: + - '๐Ÿ“’ Documentation' +change-template: '- $TITLE (#$NUMBER)' +change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. +exclude-contributors: + - dependabot + - dependabot[bot] +version-resolver: + major: + labels: + - 'major' + - 'โ— BreakingChange' + minor: + labels: + - 'minor' + - 'โœ๏ธ Feature' + patch: + labels: + - 'patch' + - '๐Ÿ“’ Documentation' + - 'โ˜ข๏ธ Bug' + - '๐Ÿค– Dependencies' + - '๐Ÿงน Updates' + default: patch +template: | + $CHANGES + + **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...minio/v$RESOLVED_VERSION + + Thank you $CONTRIBUTORS for making this update possible. diff --git a/.github/workflows/gosec.yml b/.github/workflows/gosec.yml index 25929f03..db587765 100644 --- a/.github/workflows/gosec.yml +++ b/.github/workflows/gosec.yml @@ -72,6 +72,10 @@ jobs: working-directory: ./memory run: gosec ./... # ----- + - name: Run Gosec (minio) + working-directory: ./minio + run: gosec ./... + # ----- - name: Run Gosec (mongodb) working-directory: ./mongodb run: gosec ./... diff --git a/.github/workflows/release-drafter-minio.yml b/.github/workflows/release-drafter-minio.yml new file mode 100644 index 00000000..6cddd599 --- /dev/null +++ b/.github/workflows/release-drafter-minio.yml @@ -0,0 +1,19 @@ +name: Release Drafter Minio +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - master + - main + paths: + - 'minio/**' +jobs: + draft_release_minio: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: release-drafter/release-drafter@v5 + with: + config-name: release-drafter-minio.yml + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-minio.yml b/.github/workflows/test-minio.yml new file mode 100644 index 00000000..dd3b03ed --- /dev/null +++ b/.github/workflows/test-minio.yml @@ -0,0 +1,33 @@ +on: + push: + branches: + - master + - main + paths: + - 'minio/**' + pull_request: + paths: + - 'minio/**' +name: "Tests Minio" +jobs: + Tests: + runs-on: ubuntu-latest + strategy: + matrix: + go-version: + - 1.19.x + - 1.20.x + - 1.21.x + steps: + - name: Install MinIO + run: | + docker run -d --restart always -p 9000:9000 --name storage-minio -e MINIO_ROOT_USER='minio-user' -e MINIO_ROOT_PASSWORD='minio-password' minio/minio server /data + + - name: Fetch Repository + uses: actions/checkout@v3 + - name: Install Go + uses: actions/setup-go@v4 + with: + go-version: '${{ matrix.go-version }}' + - name: Run Test + run: cd ./minio && go test ./... -v -race diff --git a/README.md b/README.md index 402621fd..8416e603 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ type Storage interface { - [Etcd](./etcd/README.md) - [Memcache](./memcache/README.md) - [Memory](./memory/README.md) +- [Minio](./minio/README.md) - [MongoDB](./mongodb/README.md) - [MSSQL](./mssql/README.md) - [MySQL](./mysql/README.md) diff --git a/minio/README.md b/minio/README.md new file mode 100644 index 00000000..7fd0adf8 --- /dev/null +++ b/minio/README.md @@ -0,0 +1,126 @@ +# Minio + +A Minio storage driver using [minio/minio-go](https://github.com/minio/minio-go). + +**Note: Requires Go 1.19 and above** + +### Table of Contents +- [Signatures](#signatures) +- [Installation](#installation) +- [Examples](#examples) +- [Config](#config) +- [Default Config](#default-config) + +### Signatures +```go +func New(config ...Config) Storage +func (s *Storage) Get(key string) ([]byte, error) +func (s *Storage) Set(key string, val []byte, exp time.Duration) error +func (s *Storage) Delete(key string) error +func (s *Storage) Reset() error +func (s *Storage) Close() error +func (s *Storage) CheckBucket() error +func (s *Storage) CreateBucket() error +func (s *Storage) RemoveBucket() error +func (s *Storage) Conn() *minio.Client +``` +### Installation +Install the Minio implementation: +```bash +go get github.com/gofiber/storage/minio +``` +And then run minio on Docker +```bash +docker run -d --restart always -p 9000:9000 -p 9001:9001 --name storage-minio --volume=minio:/var/lib/minio -e MINIO_ROOT_USER='minio-user' -e MINIO_ROOT_PASSWORD='minio-password' minio/minio server --console-address ":9001" /var/lib/minio +``` + +### Examples +Import the storage package. +```go +import "github.com/gofiber/storage/minio" +``` + +You can use the following possibilities to create a storage: +```go +// Initialize default config +store := minio.New() + +// Initialize custom config +store := minio.New(minio.Config{ + Bucket: "fiber-bucket", + Endpoint: "localhost:9000", + Credentials: Credentials{ + accessKeyID: "minio-user", + secretAccessKey: "minio-password", + }, +}) +``` + +### Config +```go +// Config defines the config for storage. +type Config struct { + // Bucket + // Default fiber-bucket + Bucket string + + // Endpoint is a host name or an IP address + Endpoint string + + // Region Set this value to override region cache + // Optional + Region string + + // Token Set this value to provide x-amz-security-token (AWS S3 specific) + // Optional, Default is false + Token string + + // Secure If set to true, https is used instead of http. + // Default is false + Secure bool + + // Reset clears any existing keys in existing Bucket + // Optional. Default is false + Reset bool + + // Credentials Minio access key and Minio secret key. + // Need to be defined + Credentials Credentials + + // GetObjectOptions Options for GET requests specifying additional options like encryption, If-Match + GetObjectOptions minio.GetObjectOptions + + // PutObjectOptions + // Allows user to set optional custom metadata, content headers, encryption keys and number of threads for multipart upload operation. + PutObjectOptions minio.PutObjectOptions + + // ListObjectsOptions Options per to list objects + ListObjectsOptions minio.ListObjectsOptions + + // RemoveObjectOptions Allows user to set options + RemoveObjectOptions minio.RemoveObjectOptions +} +``` + +### Default Config +The default configuration lacks Bucket, Region, and Endpoint which are all required and must be overwritten: +```go +// ConfigDefault is the default config +var ConfigDefault = Config{ + Bucket: "fiber-bucket", + Endpoint: "", + Region: "", + Token: "", + Secure: false, + Reset: false, + Credentials: Credentials{}, + GetObjectOptions: minio.GetObjectOptions{}, + PutObjectOptions: minio.PutObjectOptions{}, + ListObjectsOptions: minio.ListObjectsOptions{}, + RemoveObjectOptions: minio.RemoveObjectOptions{}, +} +type Credentials struct { + AccessKeyID string + SecretAccessKey string +} +``` diff --git a/minio/config.go b/minio/config.go new file mode 100644 index 00000000..d1976d7c --- /dev/null +++ b/minio/config.go @@ -0,0 +1,88 @@ +package minio + +import ( + "github.com/minio/minio-go/v7" +) + +// Config defines the config for storage. +type Config struct { + // Bucket + // Default fiber-bucket + Bucket string + + // Endpoint is a host name or an IP address + Endpoint string + + // Region Set this value to override region cache + // Optional + Region string + + // Token Set this value to provide x-amz-security-token (AWS S3 specific) + // Optional, Default is false + Token string + + // Secure If set to true, https is used instead of http. + // Default is false + Secure bool + + // Reset clears any existing keys in existing Bucket + // Optional. Default is false + Reset bool + + // Credentials Minio access key and Minio secret key. + // Need to be defined + Credentials Credentials + + // GetObjectOptions Options for GET requests specifying additional options like encryption, If-Match + GetObjectOptions minio.GetObjectOptions + + // PutObjectOptions + // Allows user to set optional custom metadata, content headers, encryption keys and number of threads for multipart upload operation. + PutObjectOptions minio.PutObjectOptions + + // ListObjectsOptions Options per to list objects + ListObjectsOptions minio.ListObjectsOptions + + // RemoveObjectOptions Allows user to set options + RemoveObjectOptions minio.RemoveObjectOptions +} + +type Credentials struct { + // AccessKeyID is like user-id that uniquely identifies your account. + AccessKeyID string + // SecretAccessKey is the password to your account. + SecretAccessKey string +} + +// ConfigDefault is the default config +var ConfigDefault = Config{ + Bucket: "fiber-bucket", + Endpoint: "", + Region: "", + Token: "", + Secure: false, + Reset: false, + Credentials: Credentials{}, + GetObjectOptions: minio.GetObjectOptions{}, + PutObjectOptions: minio.PutObjectOptions{}, + ListObjectsOptions: minio.ListObjectsOptions{}, + RemoveObjectOptions: minio.RemoveObjectOptions{}, +} + +// Helper function to set default values +func configDefault(config ...Config) Config { + // Return default config if nothing provided + if len(config) < 1 { + return ConfigDefault + } + + // Override default config + cfg := config[0] + + // Set default values + if cfg.Bucket == "" { + cfg.Bucket = ConfigDefault.Bucket + } + + return cfg +} diff --git a/minio/go.mod b/minio/go.mod new file mode 100644 index 00000000..2e86fc8b --- /dev/null +++ b/minio/go.mod @@ -0,0 +1,31 @@ +module github.com/gofiber/storage/minio + +go 1.19 + +require ( + github.com/minio/minio-go/v7 v7.0.63 + github.com/stretchr/testify v1.8.4 + github.com/valyala/bytebufferpool v1.0.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/minio/md5-simd v1.1.2 // indirect + github.com/minio/sha256-simd v1.0.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rs/xid v1.5.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + golang.org/x/crypto v0.12.0 // indirect + golang.org/x/net v0.14.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/minio/go.sum b/minio/go.sum new file mode 100644 index 00000000..63183408 --- /dev/null +++ b/minio/go.sum @@ -0,0 +1,56 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= +github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= +github.com/minio/minio-go/v7 v7.0.63 h1:GbZ2oCvaUdgT5640WJOpyDhhDxvknAJU2/T3yurwcbQ= +github.com/minio/minio-go/v7 v7.0.63/go.mod h1:Q6X7Qjb7WMhvG65qKf4gUgA5XaiSox74kR1uAEjxRS4= +github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= +github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/minio/minio.go b/minio/minio.go new file mode 100644 index 00000000..ebf65847 --- /dev/null +++ b/minio/minio.go @@ -0,0 +1,171 @@ +package minio + +import ( + "bytes" + "context" + "errors" + "log" + "net/http" + "time" + + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" + "github.com/valyala/bytebufferpool" +) + +// Storage interface that is implemented by storage providers +type Storage struct { + minio *minio.Client + cfg Config + ctx context.Context +} + +// New creates a new storage +func New(config ...Config) *Storage { + + // Set default config + cfg := configDefault(config...) + + // Minio instance + minioClient, err := minio.New(cfg.Endpoint, &minio.Options{ + Creds: credentials.NewStaticV4(cfg.Credentials.AccessKeyID, cfg.Credentials.SecretAccessKey, cfg.Token), + Secure: cfg.Secure, + Region: cfg.Region, + }) + if err != nil { + panic(err) + } + + storage := &Storage{minio: minioClient, cfg: cfg, ctx: context.Background()} + + // Reset all entries if set to true + if cfg.Reset { + if err = storage.Reset(); err != nil { + panic(err) + } + } + + // check bucket + err = storage.CheckBucket() + if err != nil { + // create bucket + err = storage.CreateBucket() + if err != nil { + panic(err) + } + } + + return storage +} + +// Get value by key +func (s *Storage) Get(key string) ([]byte, error) { + + if len(key) <= 0 { + return nil, errors.New("the key value is required") + } + + // get object + object, err := s.minio.GetObject(s.ctx, s.cfg.Bucket, key, s.cfg.GetObjectOptions) + if err != nil { + return nil, err + } + + // convert to byte + bb := bytebufferpool.Get() + defer bytebufferpool.Put(bb) + _, err = bb.ReadFrom(object) + if err != nil { + return nil, err + } + return bb.Bytes(), nil +} + +// Set key with value +func (s *Storage) Set(key string, val []byte, exp time.Duration) error { + + if len(key) <= 0 { + return errors.New("the key value is required") + } + + // create Reader + file := bytes.NewReader(val) + + // set content type + s.cfg.PutObjectOptions.ContentType = http.DetectContentType(val) + + // put object + _, err := s.minio.PutObject(s.ctx, s.cfg.Bucket, key, file, file.Size(), s.cfg.PutObjectOptions) + + return err +} + +// Delete entry by key +func (s *Storage) Delete(key string) error { + + if len(key) <= 0 { + return errors.New("the key value is required") + } + + // remove + err := s.minio.RemoveObject(s.ctx, s.cfg.Bucket, key, s.cfg.RemoveObjectOptions) + + return err +} + +// Reset all entries, including unexpired +func (s *Storage) Reset() error { + + objectsCh := make(chan minio.ObjectInfo) + + // Send object names that are needed to be removed to objectsCh + go func() { + defer close(objectsCh) + // List all objects from a bucket-name with a matching prefix. + for object := range s.minio.ListObjects(s.ctx, s.cfg.Bucket, s.cfg.ListObjectsOptions) { + if object.Err != nil { + log.Println(object.Err) + } + objectsCh <- object + } + }() + + opts := minio.RemoveObjectsOptions{ + GovernanceBypass: true, + } + + for err := range s.minio.RemoveObjects(s.ctx, s.cfg.Bucket, objectsCh, opts) { + log.Println("Error detected during deletion: ", err) + } + + return nil +} + +// Close the storage +func (s *Storage) Close() error { + return nil +} + +// CheckBucket Check to see if bucket already exists +func (s *Storage) CheckBucket() error { + exists, err := s.minio.BucketExists(s.ctx, s.cfg.Bucket) + if !exists || err != nil { + return errors.New("the specified bucket does not exist") + } + return nil +} + +// CreateBucket Bucket not found so Make a new bucket +func (s *Storage) CreateBucket() error { + return s.minio.MakeBucket(s.ctx, s.cfg.Bucket, minio.MakeBucketOptions{Region: s.cfg.Region}) +} + +// RemoveBucket Bucket remove if bucket is empty +func (s *Storage) RemoveBucket() error { + return s.minio.RemoveBucket(s.ctx, s.cfg.Bucket) +} + +// Conn Return minio client +func (s *Storage) Conn() *minio.Client { + return s.minio +} diff --git a/minio/minio_test.go b/minio/minio_test.go new file mode 100644 index 00000000..07fb0724 --- /dev/null +++ b/minio/minio_test.go @@ -0,0 +1,184 @@ +package minio + +import ( + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +var testStore = New( + Config{ + Bucket: "fiber-bucket", + Endpoint: "localhost:9000", + Credentials: Credentials{ + AccessKeyID: "minio-user", + SecretAccessKey: "minio-password", + }, + }, +) + +func Test_Get(t *testing.T) { + var ( + key = "john" + val = []byte("doe") + ) + + err := testStore.Set(key, val, 0) + require.NoError(t, err) + + result, err := testStore.Get(key) + require.NoError(t, err) + require.Equal(t, val, result) + + result, err = testStore.Get("doe") + require.Error(t, err) + require.Zero(t, len(result)) +} + +func Test_Get_Empty_Key(t *testing.T) { + var ( + key = "" + ) + + _, err := testStore.Get(key) + require.Error(t, err) + require.EqualError(t, err, "the key value is required") + +} + +func Test_Get_Not_Exists_Key(t *testing.T) { + var ( + key = "not-exists" + ) + + _, err := testStore.Get(key) + require.Error(t, err) + require.EqualError(t, err, "The specified key does not exist.") + +} + +func Test_Get_Not_Exists_Bucket(t *testing.T) { + var ( + key = "john" + ) + + // random bucket name + testStore.cfg.Bucket = strconv.FormatInt(time.Now().UnixMicro(), 10) + + result, err := testStore.Get(key) + require.Error(t, err) + require.Zero(t, len(result)) + require.EqualError(t, err, "The specified bucket does not exist") + + testStore.cfg.Bucket = "fiber-bucket" + +} + +func Test_Set(t *testing.T) { + var ( + key = "john" + val = []byte("doe") + ) + + err := testStore.Set(key, val, 0) + require.NoError(t, err) +} + +func Test_Set_Empty_Key(t *testing.T) { + var ( + key = "" + val = []byte("doe") + ) + + err := testStore.Set(key, val, 0) + + require.Error(t, err) + require.EqualError(t, err, "the key value is required") + +} + +func Test_Set_Not_Exists_Bucket(t *testing.T) { + var ( + key = "john" + val = []byte("doe") + ) + + // random bucket name + testStore.cfg.Bucket = strconv.FormatInt(time.Now().UnixMicro(), 10) + + err := testStore.Set(key, val, 0) + require.Error(t, err) + require.EqualError(t, err, "The specified bucket does not exist") + + testStore.cfg.Bucket = "fiber-bucket" +} + +func Test_Delete(t *testing.T) { + var ( + key = "john" + val = []byte("doe") + ) + + err := testStore.Set(key, val, 0) + require.NoError(t, err) + + err = testStore.Delete(key) + require.NoError(t, err) + +} + +func Test_Delete_Empty_Key(t *testing.T) { + var ( + key = "" + val = []byte("doe") + ) + + err := testStore.Set(key, val, 0) + + require.Error(t, err) + require.EqualError(t, err, "the key value is required") + +} + +func Test_Delete_Not_Exists_Bucket(t *testing.T) { + var ( + key = "john" + ) + + // random bucket name + testStore.cfg.Bucket = strconv.FormatInt(time.Now().UnixMicro(), 10) + + err := testStore.Delete(key) + + require.Error(t, err) + require.EqualError(t, err, "The specified bucket does not exist") + + testStore.cfg.Bucket = "fiber-bucket" + +} + +func Test_Reset(t *testing.T) { + var ( + val = []byte("doe") + ) + + err := testStore.Set("john1", val, 0) + require.NoError(t, err) + + err = testStore.Set("john2", val, 0) + require.NoError(t, err) + + err = testStore.Reset() + require.NoError(t, err) + + result, err := testStore.Get("john1") + require.Error(t, err) + require.Zero(t, len(result)) + +} + +func Test_Close(t *testing.T) { + require.NoError(t, testStore.Close()) +}